Log4j2源码解读——删除过期文件

x33g5p2x  于2021-12-25 转载在 其他  
字(4.5k)|赞(0)|评价(0)|浏览(511)

之前一直以为Log4j2的过期文件删除功能是由配置文件中的 DefaultRolloverStrategy标签的max特性控制的。
既然是错误的认识,那肯定无法得到预期的结果。最近经过翻开源码和网上查找相关资料。大概有了些了解。

1. 解决方案

先给出解决方案,方便急于解决问题的童鞋。

<DefaultRolloverStrategy>
 <!-- 配置说明: * 只处理位于logs文件夹下的文件. * 只处理以.log.zip结尾的文件 (name match) * 只处理最后一次修改时间超过4天的文件 -->
 <Delete basePath="${sys:storm.home}/logs/" maxDepth="1">
   <IfFileName glob="*.log.zip" />
   <IfLastModified age="4d" />
 </Delete>
</DefaultRolloverStrategy>

2. 基本配置

完整的基本配置大概是这样的

<RollingFile name="INFO" filename="${logPath}/info.log" filepattern="${logPath}/%d{yyyyMMdd}-%i-info.log.zip">
    <Filters>
        <!--设置只输出级别为INFO的日志-->
        <ThresholdFilter level="INFO"/>
        <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
    </Filters>
    <PatternLayout pattern="[ %-5p]:%d{yyyy-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n%n" />
    <Policies>
        <!--设置每天打包日志一次-->
        <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
        <!--设置日志文件满50MB后打包 -->
        <SizeBasedTriggeringPolicy size="50 MB" /> 
    </Policies>

    <DefaultRolloverStrategy max="5">
        <Delete basePath="${logPath}/" maxDepth="1">
            <IfFileName glob="*.log.zip" />
            <IfLastModified age="5d" />
        </Delete>            
    </DefaultRolloverStrategy>
</RollingFile>

3. 底层原理

现在让我们来看看一些细节。

3.1 RollingFileManager

删除的逻辑判断最终还是落实在RollingFileManager上。也就是RollingFileManager.checkRollover方法上。

/** * Determines if a rollover should occur. * @param event The LogEvent. */
// RollingFileManager.checkRollover
public synchronized void checkRollover(final LogEvent event) {
    // 这里的triggeringPolicy使用典型的组合模式, 实际类型是CompositeTriggeringPolicy
    // 其包含的所有子Policy(TimeBasedTriggeringPolicy, SizeBasedTriggeringPolicy)有一个通过即可。
    // Policy决定是否执行rollover
    // 而Policy对应标签<Policies>中的元素。
    if (triggeringPolicy.isTriggeringEvent(event)) {
        rollover();
    }
}
  1. Policy决定是否执行rollover方法, 而Policy的基本逻辑是有一个通过即可。例如下图基于时间的,基于文件大小的等等。

  1. rollover执行时的具体操作由Action来定义。例如下图中的压缩,改名(这些是框架会自己视情况主动嵌入),删除(这个必须得开发人员主动配置了)等操作。

3.2 删除文件相关细节
  1. <Delete> 对应内存中的 DeleteAction
  2. DeleteAction将删除文件的逻辑交给了DeletingVisitor
  3. DeletingVisitor继承自java.nio.file.SimpleFileVisitor<T>
  4. DeletingVisitor对基类的visitFile的覆写可以发现,只有上面<Delete>内配置所有的Condidtion 同时满足,才会执行删除操作。
  5. 而且log4j2为了防止误删除操作,特意提供了testMode配置,这个配置项的生效就是在DeletingVisitor类的visitFile方法中完成的。
3.3 DefaultRolloverStrategy

重点就是这 rollover 方法

3.3.1 执行堆栈

3.3.2 rollover 方法
@Override
public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
    int fileIndex;
    if (minIndex == Integer.MIN_VALUE) {
        final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
        fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1;
    } else {
        if (maxIndex < 0) {
            return null;
        }
        final long startNanos = System.nanoTime();
        // 处理 <DefaultRolloverStrategy max=""> 和 <SizeBasedTriggeringPolicy /> 以及 文件名中的%i 配合生成的文件
        fileIndex = purge(minIndex, maxIndex, manager);
        if (fileIndex < 0) {
            return null;
        }

    }
    final StringBuilder buf = new StringBuilder(255);
    manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex);
    final String currentFileName = manager.getFileName();

    String renameTo = buf.toString();
    final String compressedName = renameTo;
    // 执行文件压缩的Action
    Action compressAction = null;

    FileExtension fileExtension = manager.getFileExtension();
    if (fileExtension != null) {
        renameTo = renameTo.substring(0, renameTo.length() - fileExtension.length());
        compressAction = fileExtension.createCompressAction(renameTo, compressedName,
                true, compressionLevel);
    }

    // 执行文件重命名的Action
    final FileRenameAction renameAction = new FileRenameAction(new File(currentFileName), new File(renameTo),
                manager.isRenameEmptyFiles());

    // 将Action合并, 其中的customActions里就有我们定义的, 由<Delete>生成的DeleteAction实例
    // 因为这些操作并不影响主体功能,所以log4j2是以异步的方式执行的。
    final Action asyncAction = merge(compressAction, customActions, stopCustomActionsOnError);
    return new RolloverDescriptionImpl(currentFileName, false, renameAction, asyncAction);
}

4. 总结

  1. TimeBasedTriggeringPolicy的通过时比较的文件时间实际是文件的LastModifiedTime,所以如果你参考本人配置时如果出现文件不会删除的问题,可以关注下这方面的问题。修改下电脑的系统时间往后,或者修改下日志文件的最后一次修改时间(编程方式)。
  2. Policy决定是否执行rollover方法, 而Policy的基本逻辑是有一个通过即可。例如基于时间的,基于文件大小的等等。
  3. rollover执行时的具体操作由Action来定义。例如压缩,改名(这些是框架会自己视情况主动嵌入),删除(这个必须得开发人员主动配置了)等操作。
  4. 配置文件中的 DefaultRolloverStrategy标签的max特性是配置压缩文件名中的 i 来决定同一天内(这个说法不严谨)所保存最大数量,即 i 的最大数值。

5. 参考

  1. 相关ISSUE(2014年2月提出)

  2. how-to-delete-old-logs-with-log4j2

  3. Since 2.5, Log4j supports a custom Delete action that is executed on every rollover.

  4. Out of the box you can delete files based on age, count or how much disk space they take up (accumulated file size).

  5. custom Delete action

相关文章