95 TimeBasedRollingPolicy 配合 SizeBasedTriggeringPolicy 导致 logback 日志轮转失败, 最终只生成启动的时间日志文件
前言最近碰到了这样的一个问题项目使用 logback, 日志配置每天一个日志文件, 然后 启动项目之后 能够看到今天的日志文件但是 第二天来查看 项目 却看不到第二天的日志文件了, 并且 查看一下 前一天的日志, 大小大概是配置的大小上限, 打开日志查看, 里面没有任何异常信息但是 你重启一下之后, 你发现 今天的日志文件 又生成了?logback 配置文件复制自网上这里 今天来看一下 这个问题l
前言
最近碰到了这样的一个问题
项目使用 logback, 日志配置每天一个日志文件, 然后 启动项目之后 能够看到今天的日志文件
但是 第二天来查看 项目 却看不到第二天的日志文件了, 并且 查看一下 前一天的日志, 大小大概是配置的大小上限, 打开日志查看, 里面没有任何异常信息
但是 你重启一下之后, 你发现 今天的日志文件 又生成了?
logback 配置文件复制自网上
这里 今天来看一下 这个问题
logback配置文件
复现也很好复现, 日志轮转更新为按分钟就可以
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds" debug="true">
<property name="LOG_HOME" value="target/logs"/>
<appender name="STD_OUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE_LOGGER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/ROOT_%d{yyyy-MM-dd_HH-mm}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>7KB</maxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="STD_OUT"/>
<appender-ref ref="FILE_LOGGER"/>
</root>
</configuration>
SpringBoot 启动类如下, 启动之后 定时输出日志
package com.hx.boot;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@MapperScan("com.hx.boot.mapper")
@SpringBootApplication
public class HelloSpringBootApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(HelloSpringBootApplication.class, args);
}
public static Logger logger = LoggerFactory.getLogger(HelloSpringBootApplication.class);
@Override
public void run(String... args) throws Exception {
new Thread(() -> {
try {
int counter = 0;
while (true) {
logger.info(" log - " + counter++);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
启动项目之后 一段时间, 标准输出 记录如下异常, 所以 我们上面查找日志 不应该仅仅在 logback 输出的日志里面查询, 还应该在标准输出对应的日志文件里面查询
可以看到是在 rollover 方法里面抛出的异常, 估计这便是 造成没有正常日志轮转的原因了
另外可以看到的一个现象是 这个异常出现了三次之后 就没有再输出了
这是因为 外层的 catch 有限制
问题的排查
来看一下 问题爆发的地方, 可以看到这里的 elapsedPeriodsFileName 为 null, NPE 抛出的地方就是在断点处的方法里面
首先我们来看一下 rollingFileAppender 和 triggingPolicy 和 rollingPoliicy 的一些关系
前者 组合了后面两个, 当有日志事件过来的时候, 首先 triggeringPolicy 判断是否需要 rollover, 如果需要 则基于 rollingPolicy 进行 rollover
我们这里的 triggeringPolicy 为 SizeBasedTriggeringPolicy, 那么 appender 就只会基于 fileSize 进行判断是否需要 rollover, 并不会根据我们期望的按天进行 rollover
然后当 fileSize 超过 maxSize 的时候, 基于 rollingPolicy 进行轮转的时候 发生异常, 这就是 "前言" 中的几个现象
1. 当天的日志文件大小为配置的大小上限
2. 第二天日志没有正常生成
上面 为什么会抛出 NPE 的异常?
我们查找一下 elapsedPeriodsFileName 变量赋值的地方, 大概可以找到 三处, 都是 在TimeBasedFileNamingAndTriggeringPolicyBase 的子类的 isTriggeringEvent 中, 这个类是我们上面配置的 timeBasedRollingPoliicy 的一个属性
所以 大致的情况就是 timeBasedRollingPoliicy 如果是配置在 appender 中, 那么是既需要他来当 triggingPolicy, 有需要它来当 rollingPolicy
因为在 timeBasedRollingPoliicy 是有相互的关联, 依赖
然后 我们将 rollingPolicy 也配置成 timeBasedRollingPoliicy
日志轮转的时候
日志轮转也正常了
或者说光配置其中的一个也行, appender 中有一些 两者的处理
关于配置的 pattern 中的占位符
其中的常量没有什么说的, 其中的一些 %d 表示什么可以参考下面, 引用自 ch.qos.logback.classic.PatternLayout
defaultConverterMap.putAll(Parser.DEFAULT_COMPOSITE_CONVERTER_MAP);
defaultConverterMap.put("d", DateConverter.class.getName());
defaultConverterMap.put("date", DateConverter.class.getName());defaultConverterMap.put("r", RelativeTimeConverter.class.getName());
defaultConverterMap.put("relative", RelativeTimeConverter.class.getName());defaultConverterMap.put("level", LevelConverter.class.getName());
defaultConverterMap.put("le", LevelConverter.class.getName());
defaultConverterMap.put("p", LevelConverter.class.getName());defaultConverterMap.put("t", ThreadConverter.class.getName());
defaultConverterMap.put("thread", ThreadConverter.class.getName());defaultConverterMap.put("lo", LoggerConverter.class.getName());
defaultConverterMap.put("logger", LoggerConverter.class.getName());
defaultConverterMap.put("c", LoggerConverter.class.getName());defaultConverterMap.put("m", MessageConverter.class.getName());
defaultConverterMap.put("msg", MessageConverter.class.getName());
defaultConverterMap.put("message", MessageConverter.class.getName());defaultConverterMap.put("C", ClassOfCallerConverter.class.getName());
defaultConverterMap.put("class", ClassOfCallerConverter.class.getName());defaultConverterMap.put("M", MethodOfCallerConverter.class.getName());
defaultConverterMap.put("method", MethodOfCallerConverter.class.getName());defaultConverterMap.put("L", LineOfCallerConverter.class.getName());
defaultConverterMap.put("line", LineOfCallerConverter.class.getName());defaultConverterMap.put("F", FileOfCallerConverter.class.getName());
defaultConverterMap.put("file", FileOfCallerConverter.class.getName());defaultConverterMap.put("X", MDCConverter.class.getName());
defaultConverterMap.put("mdc", MDCConverter.class.getName());defaultConverterMap.put("ex", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("exception", ThrowableProxyConverter.class.getName());
defaultConverterMap.put("rEx", RootCauseFirstThrowableProxyConverter.class.getName());
defaultConverterMap.put("rootException", RootCauseFirstThrowableProxyConverter.class.getName());
defaultConverterMap.put("throwable", ThrowableProxyConverter.class.getName());defaultConverterMap.put("xEx", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("xException", ExtendedThrowableProxyConverter.class.getName());
defaultConverterMap.put("xThrowable", ExtendedThrowableProxyConverter.class.getName());defaultConverterMap.put("nopex", NopThrowableInformationConverter.class.getName());
defaultConverterMap.put("nopexception", NopThrowableInformationConverter.class.getName());defaultConverterMap.put("cn", ContextNameConverter.class.getName());
defaultConverterMap.put("contextName", ContextNameConverter.class.getName());defaultConverterMap.put("caller", CallerDataConverter.class.getName());
defaultConverterMap.put("marker", MarkerConverter.class.getName());
defaultConverterMap.put("property", PropertyConverter.class.getName());
defaultConverterMap.put("n", LineSeparatorConverter.class.getName());
defaultConverterMap.put("black", BlackCompositeConverter.class.getName());
defaultConverterMap.put("red", RedCompositeConverter.class.getName());
defaultConverterMap.put("green", GreenCompositeConverter.class.getName());
defaultConverterMap.put("yellow", YellowCompositeConverter.class.getName());
defaultConverterMap.put("blue", BlueCompositeConverter.class.getName());
defaultConverterMap.put("magenta", MagentaCompositeConverter.class.getName());
defaultConverterMap.put("cyan", CyanCompositeConverter.class.getName());
defaultConverterMap.put("white", WhiteCompositeConverter.class.getName());
defaultConverterMap.put("gray", GrayCompositeConverter.class.getName());
defaultConverterMap.put("boldRed", BoldRedCompositeConverter.class.getName());
defaultConverterMap.put("boldGreen", BoldGreenCompositeConverter.class.getName());
defaultConverterMap.put("boldYellow", BoldYellowCompositeConverter.class.getName());
defaultConverterMap.put("boldBlue", BoldBlueCompositeConverter.class.getName());
defaultConverterMap.put("boldMagenta", BoldMagentaCompositeConverter.class.getName());
defaultConverterMap.put("boldCyan", BoldCyanCompositeConverter.class.getName());
defaultConverterMap.put("boldWhite", BoldWhiteCompositeConverter.class.getName());
defaultConverterMap.put("highlight", HighlightingCompositeConverter.class.getName());defaultConverterMap.put("lsn", LocalSequenceNumberConverter.class.getName());
fileName 中的 converter 如下, %d 和 %i 两种类型
pattern 结合参数转换为期望的字符串
TimeBasedRollingPolicy
SizeAndTimeBasedRollingPolicy
完
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)