log4cxx配置日期回滚策略中增加MaxFileSize属性

Posted Sherlock的程序人生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了log4cxx配置日期回滚策略中增加MaxFileSize属性相关的知识,希望对你有一定的参考价值。

在log4cxx配置日期回滚策略中增加MaxFileSize属性,实现日志按照日期备份和大小备份

1、背景

C++ 项目,使用 log4cxx 日志库,需要按照日期生成日志目录,目录下存放日志文件,且日志文件不能过大,过大需要分片,例如,文件目录可以是:

.
|-- log
|---|-- 2022-10-02
|---|---|-- log.log
|---|---|-- log.log.1
|---|---|-- log.log.2
|---|---|-- log.log.3
|---|-- 2022-10-03
|---|---|-- log.log
|---|---|-- log.log.1
|---|---|-- log.log.2

log4cxx 库针对该需要有两个问题:

  1. DailyRollingFileAppender 只能针对文件进行回滚,效果是在文件名后增加日期后缀,不能实现日期创建目录;
  2. RollingFileAppender 支持文件过大分片(MaxFileSize 属性),但是 DailyRollingFileAppender 不支持;

针对问题一,已有解决方案,见文章:按照日期回滚不创建新目录的BUG

问题二,log4cxx 库确实不支持,只能自定义实现了

2、实现方式

先使用配置文件如下:

log4j.rootLogger=INFO,R

log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.ImmediateFlush=true
log4j.appender.R.Append=true
log4j.appender.R.MaxFileSize=1MB
log4j.appender.R.DatePattern=\'$LOG_HOMR_DIR/$LOG_DIR/\'yyyy-MM-dd\'/log.log\'
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%dyyyy-MM-dd hh:mm:ss.SSS %-5p [%c] %m %n

正常情况下,MaxFileSize 因为不是 DailyRollingFileAppender 的属性,所以不会生效,我们需要进行如下修改

2.1、DailyRollingFileAppender新增MaxFileSize属性

配置中的属性直接对应 appender 的属性,所以添加属性的最外层应该是 appender

DailyRollingFileAppender 类是配置文件中指定的 appender,所以属性先加在这里

代码文件 dailyrollingfileappender.h,增加属性和getter/setter

namespace log4cxx 
...

  class LOG4CXX_EXPORT DailyRollingFileAppender : public log4cxx::rolling::RollingFileAppenderSkeleton 
  DECLARE_LOG4CXX_OBJECT(DailyRollingFileAppender)
  BEGIN_LOG4CXX_CAST_MAP()
          LOG4CXX_CAST_ENTRY(DailyRollingFileAppender)
          LOG4CXX_CAST_ENTRY_CHAIN(FileAppender)
  END_LOG4CXX_CAST_MAP()

  /**
     The date pattern used to initiate rollover.
  */
  LogString datePattern;
  int maxFileSize;//新增属性,用于控制单个文件大小

public:
  DailyRollingFileAppender();

  //属性的getter/setter
  void setMaxFileSize( const LogString & value);
  long getMaxFileSize() const;

...
;
LOG4CXX_PTR_DEF(DailyRollingFileAppender);

...

dailyrollingfileappender.cpp 实现属性的获取和设置

DailyRollingFileAppender::DailyRollingFileAppender(
  const LayoutPtr& layout,
  const LogString& filename,
  const LogString& datePattern1)
  : datePattern(datePattern1), maxFileSize(0) //构造函数进行maxFileSize属性初始化
    setLayout(layout);
    setFile(filename);
    Pool p;
    activateOptions(p);

//getter/setter
void DailyRollingFileAppender::setMaxFileSize( const LogString & value) 
    //这里调用log4cxx的接口,进行单位转换
    maxFileSize = OptionConverter::toFileSize(value, maxFileSize + 1);

long DailyRollingFileAppender::getMaxFileSize() const 
    return maxFileSize;

//activateOptions方法作用是激活配置,即将配置塞到policy中
void DailyRollingFileAppender::activateOptions(log4cxx::helpers::Pool& pool) 
  TimeBasedRollingPolicyPtr policy = new TimeBasedRollingPolicy();
  LogString pattern(getFile());
  bool inLiteral = false;
  bool inPattern = false;

  for (size_t i = 0; i < datePattern.length(); i++) 
    if (datePattern[i] == 0x27 /* \'\\\'\' */) 
      inLiteral = !inLiteral;

      if (inLiteral && inPattern) 
        pattern.append(1, (logchar) 0x7D /* \'\' */);
        inPattern = false;
      
     else 
      if (!inLiteral && !inPattern) 
        const logchar dbrace[] =  0x25, 0x64, 0x7B, 0 ; // "%d"
        pattern.append(dbrace);
        inPattern = true;
      

      pattern.append(1, datePattern[i]);
    
  

  if (inPattern) 
    pattern.append(1, (logchar) 0x7D /* \'\' */);
  

  policy->setFileNamePattern(pattern);
  policy->setMaxFileSize(maxFileSize);//新增,将maxFileSize属性塞给policy
  policy->activateOptions(pool);
  setTriggeringPolicy(policy);
  setRollingPolicy(policy);

  RollingFileAppenderSkeleton::activateOptions(pool);

2.2、TimeBasedRollingPolicy策略新增maxFileSize的判断

appender 把 maxFileSize 属性塞给了 policy,那么 policy 需要新增对应接口来接收,并做策略的判断

同时 policy 只是策略,roll 的具体操作在 action 中,policy 根据需要创建 action,然后由 action 来执行,具体见下面代码

DailyRollingFileAppender 只使用了 TimeBasedRollingPolicy ,所以我们只需要看 TimeBasedRollingPolicy 的代码

timebasedrollingpolicy.h 修改如下:

namespace log4cxx 
    namespace rolling 
        class LOG4CXX_EXPORT TimeBasedRollingPolicy : public RollingPolicyBase,
             public TriggeringPolicy 
          DECLARE_LOG4CXX_OBJECT(TimeBasedRollingPolicy)
          BEGIN_LOG4CXX_CAST_MAP()
                  LOG4CXX_CAST_ENTRY(TimeBasedRollingPolicy)
                  LOG4CXX_CAST_ENTRY_CHAIN(RollingPolicyBase)
                  LOG4CXX_CAST_ENTRY_CHAIN(TriggeringPolicy)
          END_LOG4CXX_CAST_MAP()

        private:
            //新增以下三个属性
        	size_t maxFileSize;//单个文件的大小上限
        	bool fileSizeChanged;//当前记录的文件是否已经超出上限
        	int backupIndex;//rollover的index,即需要给备份文件增加的后缀
        public:
            TimeBasedRollingPolicy();

			//maxFileSize属性的getter/setter
        	void setMaxFileSize(size_t size);
        	size_t getMaxFileSize() const;
        ;
        LOG4CXX_PTR_DEF(TimeBasedRollingPolicy);
    

timebasedrollingpolicy.cpp 修改如下:

构造函数进行属性初始化,index从1开始,maxFileSize为0表示没有大小限制

TimeBasedRollingPolicy::TimeBasedRollingPolicy()
    : maxFileSize(0), fileSizeChanged(false), backupIndex(1) 

新增 maxFileSize 属性的 getter/setter

void TimeBasedRollingPolicy::setMaxFileSize(size_t size) 
    maxFileSize = size;


size_t TimeBasedRollingPolicy::getMaxFileSize() const 
    return maxFileSize;

isTriggeringEvent() 方法用于判断是否需要进行 rollover,修改如下:

lastFileName 变量存放备份的日志的文件的绝对路径,不需要备份,则它应该和当前记录的日志文件一直,若我们需要备份,则该变量就是备份的文件

这里先检查日期是否变化,如果日志变化了,则无需判断文件大小

bool TimeBasedRollingPolicy::isTriggeringEvent(
  Appender* /* appender */,
  const log4cxx::spi::LoggingEventPtr& /* event */,
  const LogString& /* filename */,
  size_t fileLength)  
    if (apr_time_now() > nextCheck) 
        return true;
    
    //maxFileSize <= 0 则不检查大小,当前日志文件超出限额,则需要rollover
    if (maxFileSize > 0 && fileLength >= maxFileSize) 
        fileSizeChanged = true;//置true,表示需要分片
        //备份的文件增加后缀index,同时index加一
        lastFileName = lastFileName + "." + std::to_string(backupIndex++);
        return true;
    
    return false;

rollover() 方法是日志回滚的具体实现,修改如下:

RolloverDescriptionPtr TimeBasedRollingPolicy::rollover(
   const LogString& currentActiveFile,
   Pool& pool) 
  apr_time_t n = apr_time_now();
  nextCheck = ((n / APR_USEC_PER_SEC) + 1) * APR_USEC_PER_SEC;

  LogString buf;
  ObjectPtr obj(new Date(n));
  formatFileName(obj, buf, pool);

  LogString newFileName(buf);

  //
  //  if file names haven\'t changed, no rollover
  //
  //新增fileSizeChanged的判断,日志变化或者文件超限都需要执行rollover
  if (newFileName == lastFileName && !fileSizeChanged) 
      RolloverDescriptionPtr desc;
      return desc;
  
  if (fileSizeChanged) 
      //如果是文件超限,表示不是日期变化的roll,重置flag
      fileSizeChanged = false;
   else 
    //日期变化,则备份的文件后缀index重新从1开始
    backupIndex = 1;
  

  ActionPtr renameAction;
  ActionPtr compressAction;
  LogString lastBaseName(
    lastFileName.substr(0, lastFileName.length() - suffixLength));
  LogString nextActiveFile(
    newFileName.substr(0, newFileName.length() - suffixLength));

  //
  //   if currentActiveFile is not lastBaseName then
  //        active file name is not following file pattern
  //        and requires a rename plus maintaining the same name
  if (currentActiveFile != lastBaseName) 
    //创建rename action,用于将当前的日志文件重命名,实现日志的备份
    renameAction =
      new FileRenameAction(
        File().setPath(currentActiveFile), File().setPath(lastBaseName), true);
    nextActiveFile = currentActiveFile;
  

  if (suffixLength == 3) 
    compressAction =
      new GZCompressAction(
        File().setPath(lastBaseName), File().setPath(lastFileName), true);
  

  if (suffixLength == 4) 
    compressAction =
      new ZipCompressAction(
        File().setPath(lastBaseName), File().setPath(lastFileName), true);
  

  lastFileName = newFileName;

  return new RolloverDescription(
    nextActiveFile, false, renameAction, compressAction);

这里我们只需要创建 FileRenameAction,告诉他需要 rename 的文件和新的文件即可

3、总结

本次修改的修改综合来说只有以下几步:

  1. appender 解析属性;
  2. appender 在配置active的函数中将属性交给policy;
  3. policy 负责策略,需要判断是否要进行 roll,并构造 roll 需要的action;
  4. action 最后执行,action有多种,rename 是其中一个;

由此可见,log4cxx 的框架层次分明,结构合理,便于修改和拓展,设计是相当优秀的,值得深入学习

以上是关于log4cxx配置日期回滚策略中增加MaxFileSize属性的主要内容,如果未能解决你的问题,请参考以下文章

fs-maxfile-nr和nofile的关系

log4cxx consoleappender怎么关闭

我可以在 JNI 项目中使用 Java 中的 log4j 和 C++ 中的 log4cxx 将日志存储在同一个文件中吗?

c++log4cxx日志的详解

log4cxx OutputDebugString DebugView dbgview

Mule请求响应VM的回滚异常策略