日志库EasyLogging++学习系列(10)—— 日志文件滚动
转载自:
http://blog.csdn.net/Fish_55_66/article/details/49617403
末尾有源码下载链接。
2021年4月14日更新:在windows的qt编译运行,没有问题,在中标麒麟的龙芯(mips架构)使用,出现一些问题。与系统相关的,在windows平台,如果记录日志需要exe创建文件夹,应该使用'\\',如log\\%Y%M..log;在其他平台,用‘/’。但是如果你一直在使用Qt,无论什么平台都可以用‘/’。还有一个问题(中标麒麟下),可能跟我的qt版本有关(qt5.6.2),在easylogging++.h的3189行附近,
1 struct::tm modifyTime;
2 struct::_stat buf;
3 _stat(file.c_str(), &buf);
4 localtime_s(&modifyTime, &buf.st_mtime)
5
6 改为
7 struct::tm* modifyTime,
8 struct::stat buf;
9 stat(file.c_str(), &buf);
10 modifyTime = localtime(&buf.st_mtime)
以上就是我遇到的问题。
注意:我上传例子中的easylogging++.h没有改变。
在很多应用场合,我们是需要实现日志文件滚动的,特别是在一些长期运行的服务器程序中,如果把所有的日志都记录在一个文件之中,势必会造成日志文件越来越大。当日志内容很多的时候,万一哪天突然需要查询某个日志信息就会显得十分不便。所以,支持日志文件滚动是很多日志库都支持的功能,而文件滚动又可以分为按大小滚动和按时间滚动。
按大小滚动文件
在 Easylogging++ 中,已经实现了按照日志文件大小来滚动日志记录。在前面《日志库EasyLogging++学习系列(3)—— 配置功能》一文中介绍配置文件时,有一个配置项:MAX_LOG_FILE_SIZE,这个配置项的值(以字节为单位)表示的就是日志文件的最大大小。一旦日志文件的大小达到这个配置项设置的值,日志文件就会自动清空文件中所有的日志记录,并重新开始写入。不过配置项 MAX_LOG_FILE_SIZE 在默认情况下是不生效的,需要设置标记:LoggingFlag::StrictLogFileSizeCheck 来激活。另外,如果我们想要保留之前的日志记录,那么我们可以注册一个回调函数,这个回调函数将会允许我们在清空日志文件之前对日志文件进行一次处理。下面的代码演示了按大小滚动日志文件,并通过回调函数保留了所有的日志记录:
#include "easylogging++.h" INITIALIZE_EASYLOGGINGPP static unsigned int idx; void rolloutHandler(const char* filename, std::size_t size) { /// 备份日志 system("mkdir bin"); std::stringstream ss; ss << "move " << filename << " bin\\log_backup_" << ++idx; system(ss.str().c_str()); } int main(int, char**) { idx = 0; el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck); el::Loggers::reconfigureAllLoggers(el::ConfigurationType::MaxLogFileSize, "100"); /// 注册回调函数 el::Helpers::installPreRollOutCallback(rolloutHandler); for (int i = 0; i < 100; ++i) { LOG(INFO) << "Test"; } /// 注销回调函数 el::Helpers::uninstallPreRollOutCallback(); return 0; }
通过配置文件来设置配置项 MAX_LOG_FILE_SIZE 的大小也可以实现上述演示代码的效果,另外我们还可以设置不同级别的日志文件按照不同的文件大小来滚动。如果不小心忘记了设置标记:LoggingFlag::StrictLogFileSizeCheck ,我们还可以通过调用函数 el::Helpers::validateFileRolling(el::Logger*, const el::Level&) 以手动的方式来检查日志滚动,建议各位小伙伴可以自己尝试一下。
按时间滚动文件
在 Easylogging++ 中是没有实现按时间滚动日志文件的,不过既然是开源的日志库,我们可以参考着按大小滚动日志文件的实现方式,根据自己的需求去实现一个按时间滚动日志文件的功能。下面简单地说明一下实现步骤:
- 在按大小滚动日志文件中有配置项 MAX_LOG_FILE_SIZE,所以我们也增加一个配置项 LOG_FILE_ROLLING_TIME ,新增配置项的值类型为 char* 型,其值只能是以下四个:"MONTH" 、"DAY"、"HOUR"、"MINUTE",其中"MONTH"表示按月份滚动日志文件,"DAY"表示按天数滚动日志文件,"HOUR"表示按小时滚动日志文件,"MINUTE"表示按分钟滚动日志文件。
- 标记
- 在按大小滚动日志文件中,允许我们在清空文件重新写入之前通过回调函数对日志文件进行处理,所以我们在按时间滚动日志文件的功能实现中,也同样保留该回调函数的功能,但是在回调函数中增加了一个参数,用来区分是按大小滚动日志文件还是按时间滚动日志文件。
- 本文的最后提供了实现按时间滚动日志文件功能的Easylogging++ 源码,实现的细节可在源码中搜索“modify by Fish”来查看。 因本功能目前只限于本人在使用,如有错误,欢迎指正。
#include "easylogging++.h" INITIALIZE_EASYLOGGINGPP void rolloutHandler(const char* filename, std::size_t size, el::base::RollingLogFileBasis rollingbasis) { switch (rollingbasis) { case el::base::RollingLogFileBasis::RollLog_FileSize: /// 按大小滚动日志文件 break; case el::base::RollingLogFileBasis::RollLog_DateTime: /// 按时间滚动日志文件 { time_t cuurenttime = time(NULL); cuurenttime -= 60; struct::tm oneMinuteAgo; localtime_s(&oneMinuteAgo, &cuurenttime); std::string filenameTemp = filename; int pos = filenameTemp.rfind('.'); filenameTemp = filenameTemp.substr(0, pos); char backupFile[MAX_PATH] = { 0 }; sprintf_s(backupFile, MAX_PATH, "%s_%04d%02d%02d%02d%02d.log", filenameTemp.c_str(), oneMinuteAgo.tm_year + 1900 , oneMinuteAgo.tm_mon + 1, oneMinuteAgo.tm_mday, oneMinuteAgo.tm_hour, oneMinuteAgo.tm_min); /// 自定义日志备份 std::stringstream ss; ss << "move " << filename << " " << backupFile; system(ss.str().c_str()); } break; default: break; } } int main(int, char**) { el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog); el::Loggers::addFlag(el::LoggingFlag::StrictLogFileTimeCheck); el::Loggers::reconfigureAllLoggers(el::ConfigurationType::LogFileRollingTime, "minute"); /// 按分钟滚动日志文件 /// 注册回调函数 el::Helpers::installPreRollOutCallback(rolloutHandler); for (int i = 0; i < 100000; ++i) { LOG(DEBUG) << "DEBUG"; LOG(INFO) << "INFO"; DLOG(INFO) << "DEBUG"; LOG(WARNING) << "WARNING"; LOG(ERROR) << "ERROR"; LOG(FATAL) << "FATAL"; LOG(TRACE) << "TRACE"; VLOG(0) << "VERBOSE"; Sleep(1000); } /// 注销回调函数 el::Helpers::uninstallPreRollOutCallback(); return 0; }
- 因为在新增按时间滚动日志文件的功能中修改了回调函数,所以如果使用按大小滚动日志文件功能,也需要使用修改后的回调函数。
- 因为只有在有日志写入的时候才判断是否需要更新文件,所以如果无日志记录,日志文件是无法按时间滚动的。
在实际应用中,如果日志按时间滚动,我们的日志文件基本上都会以时间来命名,所以为了更加方便地使用,我们可以在实现了按时间滚动功能的代码上再增加一个宏定义ELPP_NAME_LOG_FILE_AFTER_TIME。通过定义这个宏,我们实现了这样一个功能:当按时间滚动日志时,可以自动地创建新的日志文件,并且会以滚动时间命名新建文件。不过这个功能目前并不是很完善,使用起来有以下几个限制条件:
- 不同级别的日志必须保存在不同的日志文件中,否则无法实现日志滚动。
- 按月份滚动的日志文件名中日期格式须配置:%datetime{%Y%M},如FILENAME = "log\\test_%datetime{%Y%M}.log"。
- 按天数滚动的日志文件名中日期格式须配置:%datetime{%Y%M%d},如FILENAME = "log\\test_%datetime{%Y%M%d}.log"。
- 按小时滚动的日志文件名中日期格式须配置:%datetime{%Y%M%d%H},如FILENAME = "log\\test_%datetime{%Y%M%d%H}.log"。
- 按分钟滚动的日志文件名中日期格式须配置:%datetime{%Y%M%d%H%m},如FILENAM="log\\test_%datetime{%Y%M%d%H%m}.log"。
#define ELPP_NAME_LOG_FILE_AFTER_TIME #define ELPP_NO_DEFAULT_LOG_FILE #include "easylogging++.h" INITIALIZE_EASYLOGGINGPP int main(int, char**) { el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog); el::Loggers::addFlag(el::LoggingFlag::StrictLogFileTimeCheck); el::Configurations conf("log.conf"); el::Loggers::reconfigureAllLoggers(conf); for (int i = 0; i < 100000; ++i) { LOG(DEBUG) << "DEBUG"; LOG(INFO) << "INFO"; LOG(WARNING) << "WARNING"; LOG(ERROR) << "ERROR"; LOG(FATAL) << "FATAL"; LOG(TRACE) << "TRACE"; VLOG(0) << "VERBOSE"; Sleep(1000); } return 0; }
* GLOBAL: FORMAT = "[%level | %datetime] | %msg" ENABLED = true TO_FILE = true TO_STANDARD_OUTPUT = true LOG_FLUSH_THRESHOLD = 0 MILLISECONDS_WIDTH = 3 PERFORMANCE_TRACKING = false MAX_LOG_FILE_SIZE = 2097152 ## Throw log files away after 2097152 2MB / 209715200 200MB / 4398046511104 1GB LOG_FILE_ROLLING_TIME = minute * INFO: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_info.log" * DEBUG: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_debug.log" * WARNING: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_warning.log" * TRACE: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_trace.log" * VERBOSE: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_verbose.log" * ERROR: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_error.log" * FATAL: FILENAME = "log\\test_%datetime{%Y%M%d%H%m}_fatal.log"