应用停机期间日志未滚动归档的问题处理咨询
问题描述
我在应用中配置了如下Appender,用于执行每小时日志滚动并压缩日志文件:
<appender name="log-file" class="ch.qos.logback.core.rolling.RollingFileAppender"> .. <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>log.json.%d{yyyy-MM-dd-HH}.gz</fileNamePattern> <maxHistory>36</maxHistory> <totalSizeCap>1GB</totalSizeCap> </rollingPolicy> </appender>
生成的文件如下:
log.json.2026-04-14-08.gz log.json.2026-04-14-09.gz log.json.2026-04-14-10 <-- 当前活跃文件
该配置运行良好,但存在以下场景问题:
- 假设应用因部署等原因在10:58至11:02期间停机
log.json.2026-04-14-10文件未执行滚动操作,无法被压缩为.gz格式- 后续执行归档清理时,该文件因不匹配
.gz后缀不会被处理 - 这会产生孤儿文件,在logback无内置清理机制的情况下逐渐占用磁盘空间
使用cleanHistoryOnStart也无济于事,因为它仅检查fileNamePattern中指定的.gz文件。
解决方案
1. 自定义Logback扩展,启动时清理未压缩旧日志
实现一个自定义的ContextListener,在应用启动时扫描日志目录,处理未压缩的过期日志:
- 筛选出符合
log.json.yyyy-MM-dd-HH格式但无.gz后缀的文件 - 校验文件时间是否早于当前时间1小时(确保是过期的活跃文件)
- 对符合条件的文件执行GZIP压缩,完成后删除原文件
- 手动触发Logback的历史清理逻辑,确保压缩后的文件纳入管理
核心代码示例:
public class UncompressedLogCleaner implements ContextListener { @Override public void start(Context context) { File logDir = new File("/path/to/your/logs"); File[] targetFiles = logDir.listFiles((dir, name) -> name.matches("log.json.\\d{4}-\\d{2}-\\d{2}-\\d{2}")); if (targetFiles == null) return; long oneHourAgo = System.currentTimeMillis() - 3600 * 1000; for (File file : targetFiles) { if (file.lastModified() < oneHourAgo) { compressFile(file); file.delete(); } } // 触发Logback内置清理 RollingFileAppender<?> appender = (RollingFileAppender<?>) context.getAppender("log-file"); if (appender != null) { TimeBasedRollingPolicy<?> policy = (TimeBasedRollingPolicy<?>) appender.getRollingPolicy(); policy.cleanHistory(); } } private void compressFile(File source) { try (FileInputStream fis = new FileInputStream(source); GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(source.getAbsolutePath() + ".gz"))) { byte[] buffer = new byte[4096]; int len; while ((len = fis.read(buffer)) != -1) { gzos.write(buffer, 0, len); } } catch (IOException e) { // 记录压缩异常日志,避免影响应用启动 } } @Override public void stop() {} @Override public void setContext(Context context) {} }
在logback配置中注册该监听器:
<configuration> <contextListener class="com.your.package.UncompressedLogCleaner"/> <!-- 原有Appender配置 --> </configuration>
2. 注册Shutdown钩子,主动触发日志滚动
在应用关闭时主动调用Logback的滚动方法,避免未压缩的活跃文件遗留:
- 注册JVM shutdown钩子,在应用正常退出时触发日志滚动
- 确保即使是部署停机场景,只要JVM正常退出,就能完成当前日志的压缩
代码示例:
Runtime.getRuntime().addShutdownHook(new Thread(() -> { LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); RollingFileAppender<?> appender = (RollingFileAppender<?>) context.getAppender("log-file"); if (appender != null) { try { appender.getRollingPolicy().rollover(); } catch (RolloverFailure e) { // 记录滚动失败日志 } } }));
3. 外部定时脚本清理
若不想修改应用代码,可借助操作系统定时任务处理:
- 编写Shell脚本,扫描日志目录找出未压缩的旧日志,执行压缩后删除原文件
- 同时清理超过
maxHistory(36小时)的压缩日志
Shell脚本示例:
#!/bin/bash LOG_DIR="/path/to/your/logs" # 计算1小时前的时间戳(秒) ONE_HOUR_AGO=$(date -d '1 hour ago' +%s) # 36小时=2160分钟 MAX_HISTORY_MIN=2160 # 压缩过期的未压缩日志 for file in "$LOG_DIR"/log.json.*; do if [[ ! $file =~ \.gz$ ]]; then FILE_TIMESTAMP=$(stat -c %Y "$file") if (( FILE_TIMESTAMP < ONE_HOUR_AGO )); then gzip "$file" fi fi done # 清理过期的压缩日志 find "$LOG_DIR" -name "log.json.*.gz" -mmin +$MAX_HISTORY_MIN -delete
在Linux系统中配置cron每小时执行一次:
0 * * * * /path/to/your/clean_logs.sh
内容的提问来源于stack exchange,提问作者Markus
相关产品推荐
相关产品推荐

