问题描述
目前项目中使用的日志是 Logback 插件,在配置文件中配了只保留最近 30 天的日志:
1 | <maxHistory>30</maxHistory> |
但是最近发现服务器上存在超过 30 天的日志文件,并且后缀很杂乱:

从上面的截图也可以看出,配置的 maxHistory (日志文件的保存期限) 并未生效,当前时间是 2021-11-24,服务器上存在超过 30 天的日志文件。
历史日志清理流程分析
先来看一下 Logback 的历史日志清理操作是如何触发的。首先说下分析源码后的结论:日志文件的备份操作和历史日志的清理是一同触发的。下面看一下源码:
首先在项目启动时,会加载日志配置文件中的配置 — TimeBasedRollingPolicy$start()


日志文件后缀的计算在 SizeAndTimeBasedFNATP$start() 中进行,日志文件后缀通过一个计数器 currentPeriodsCounter 保存,在后面进行日志文件备份会用到。

上面提到了在 logback.xml 中,我们配置的滚动策略是 TimeBasedRollingPolicy,而我们配置的写日志的组件是 RollingFileAppender:
1 | <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 --> |
当有日志写入时,会执行 RollingFileAppender 的 subAppend() 方法:

isTriggerEvent 方法执行的操作是 判断是否需要执行 [备份日志] 和 [清理历史日志] 的操作

如果满足条件,那么执行 [备份日志] 和 [清理历史日志] 的操作:roller() 方法。这个方法比较重要,因为这是触发 [备份日志] 和 [清理历史日志] 的入口。

我们这里要分析的是历史文件清理的操作,因此直接看 archiveRemover.cleanAsynchronously() 方法。这里将代码分成两块进行分析:
1 | public Future<?> cleanAsynchronously(Date now) { |
历史日志未被清理的原因
先来看下 #1 端代码:clean() 方法。这个方法执行的操作是 清理超出指定时间范围的日志文件:

注意其中的 periodsElapsed 参数,这个参数代表了一个时间范围差值,通过判断 当前清理操作是否是 [服务重启后的第一次清理] 操作,返回不同的值:
在
Logback的源码中定义了一个periodsElapsed参数,默认值是32。如果是服务重启后执行的第一次清理,清理的时间范围就是32天。如果不是服务重启后执行的第一次清理,清理范围是 上一次清理时间和当前清理时间的差值。
进到源码里看一下,看下 computeElapsedPeriodsSinceLastClean() 方法:

拿到时间范围后,代码对其进行了遍历,在循环中有一个获取日期的操作,通过日期可以拿到待清理日志的文件名,来看下这个日期是如何计算的,进入 getPeriodOffsetForDeletionTarget() 方法:
1 | protected int getPeriodOffsetForDeletionTarget() { |
这个 maxHistory 就是我们在 logback.xml 配置的值,也就是说,这里拿到的日期,是 **[当前时间 - periodsElapsed - maxHistory - 1]**。这个循环中清理的文件范围是 [当前时间 - periodsElapsed - maxHistory - 1] ~ [当前时间 - maxHistory - 1] 之间的文件。
比如今天的日期是 11-24,配置的 maxHistory 是 30。假设当前不是服务重启后的第一次清理,那么拿到的时间范围 periodsElapsed 就是 32 天,那么清理的范围就是 [63 天前] ~ [31 天前] 的日志,时间范围是 09-23 ~ 10-25。
拿到时间后,就执行删除指定时间下日志文件的操作 - cleanPeriod() 方法:

注意上面的 [查找指定日期日志文件] 方法 — getFilesInPeriod():匹配的日志文件后缀范围是 0 - 999,后缀超过 1000 的日志文件将不会被筛选出来!

分析到这里,我们可以得出一个结论,有两种类型的历史日志不会被清除:
- 后缀超过
1000的历史日志。 - 时间早于 [当前时间 -
maxHistory-32-1] 的日志文件。
回到测试服务器,找到异常的历史日志,下面这一批 rsdun-app-api 的日志后缀都超过了1000,因此未被清理。

再来看一下另一个项目的日志,这个项目是配置中心。它的日志文件后缀没有超过 1000,但是仍然没有正常的被移除。

配置中心的项目一般不会打印日志,除非它进行了重启。但是实际测试发现,重启时虽然能打印日志,但是并不会触发 [日志备份] 和 [历史日志清理] 的操作。这时就需要添加一个配置:cleanHistoryOnStart。在项目启动后立刻执行一次 [清理历史日志] 的操作,但是因为这个服务基本不打印日志,如果隔了很久才重启,还是会出现部分历史日志不会被清理的情况。
1 | <!--启动项目后清理历史日志--> |
totalSizeCap 的配置
再来看下 #2 段代码:capTotalSize()。这段代码执行的是判断 maxHistory 范围内日志文件的总大小是否超出限制,如果超出了限制,就将较早的日志移除。这段代码中,**maxHistory 必须和 totalSizeCap 配合使用才会生效!**

也就是下面两个配置:
1 | <!-- 30天内的所有日志文件大小不能超过1GB --> |
这两个配置配合使用的意义是:限制 maxHistory 天内的总日志文件的大小在 totalSizeCap 内。
再看下具体的处理过程:

优化手段
- 为配置添加
cleanHistoryOnStart属性,这能解决rsdun-config-center历史日志未清除的问题。
- 32天的清理时间范围是默认的(
final属性),无法修改
- 配置
totalSizeCap
配置完毕后,重启项目,日志后缀超过 1000 的文件,以及时间早于 [当前时间 - maxHistory - 32 - 1] 的日志文件,仍然无法被删除,原因上面分析过,因此只能手动清理。
补充
开头提到了日志文件后缀杂乱的问题,这个问题的分析放在下一篇文章中分析。