本文共 9985 字,大约阅读时间需要 33 分钟。
在很多应用场合,我们是需要实现日志文件滚动的,特别是在一些长期运行的服务器程序中,如果把所有的日志都记录在一个文件之中,势必会造成日志文件越来越大。当日志内容很多的时候,万一哪天突然需要查询某个日志信息就会显得十分不便。所以,支持日志文件滚动是很多日志库都支持的功能,而文件滚动又可以分为按大小滚动和按时间滚动。
按大小滚动文件
在 Easylogging++ 中,已经实现了按照日志文件大小来滚动日志记录。在前面《》一文中介绍配置文件时,有一个配置项: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;
- }
#include "easylogging++.h"INITIALIZE_EASYLOGGINGPPstatic 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"表示按分钟滚动日志文件。
- 在按大小滚动日志文件中有标记 LoggingFlag::StrictLogFileSizeCheck 来激活滚动功能,所以我们在实现按时间滚动日志文件的功能中也增加一个标记 LoggingFlag::StrictLogFileTimeCheck 来激活滚动功能。
- 在按大小滚动日志文件中,允许我们在清空文件重新写入之前通过回调函数对日志文件进行处理,所以我们在按时间滚动日志文件的功能实现中,也同样保留该回调函数的功能,但是在回调函数中增加了一个参数,用来区分是按大小滚动日志文件还是按时间滚动日志文件。
- 本文的最后提供了实现按时间滚动日志文件功能的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;
- }
#include "easylogging++.h"INITIALIZE_EASYLOGGINGPPvoid 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;
- }
#define ELPP_NAME_LOG_FILE_AFTER_TIME#define ELPP_NO_DEFAULT_LOG_FILE#include "easylogging++.h"INITIALIZE_EASYLOGGINGPPint 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;}
其中的配置文件 log.conf 内容如下:
- * 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"
* 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"
利用上述演示代码,可以完全自动地按照每分钟的间隔创建如下格式的日志文件:
按时间滚动日志文件之所以写了这么多,最主要的原因就是为了说明在开源的日志库中,我们可以完全自主地按照自己的想法来实现一些符合自己需求的功能。比如上面介绍的宏定义ELPP_NAME_LOG_FILE_AFTER_TIME功能,虽然还不完善,但是只要严格按照限制条件来使用,完全可以达到我们想要的效果。对于开源代码,能够直接使用还并不是我们最终的目的,能够在开源的基础上加以修改完善并应用于实际编程当中才是我们学习开源代码的初衷。