日志重复打印问题
案例一:
logback.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="log.LogTest" level="DEBUG">
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
示例代码:
public class LogTest {
private static final Logger log = LoggerFactory.getLogger(LogTest.class);
@Test
public void testLogback() {
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
}
}
打印结果:
[DEBUG][2020/09/06 20:47:30][main] log.LogTest - debug
[DEBUG][2020/09/06 20:47:30][main] log.LogTest - debug
[INFO ][2020/09/06 20:47:30][main] log.LogTest - info
[INFO ][2020/09/06 20:47:30][main] log.LogTest - info
[WARN ][2020/09/06 20:47:30][main] log.LogTest - warn
[WARN ][2020/09/06 20:47:30][main] log.LogTest - warn
[ERROR][2020/09/06 20:47:30][main] log.LogTest - error
[ERROR][2020/09/06 20:47:30][main] log.LogTest - error
可见打印代码是重复的,问题出在哪里?
日志重复原因
原因是,logger 标签和root都配置了appender,当日志打印的时候,两个都会打印日志,可以将logger中的appender删除
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="log.LogTest" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
[DEBUG][2020/09/06 20:52:01][main] log.LogTest - debug
[INFO ][2020/09/06 20:52:01][main] log.LogTest - info
[WARN ][2020/09/06 20:52:01][main] log.LogTest - warn
[ERROR][2020/09/06 20:52:01][main] log.LogTest - error
案例二:
我们想实现将INFO日志写入INFO_FILE,ERROR日志写入ERROR_FILE,java测试代码不变,logback.xml如下:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name="logDir" value="./logs" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.FileAppender">
<File>${logDir}/INFO_FILE.log</File>
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.FileAppender">
<File>${logDir}/ERROR_FILE.log</File>
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<logger name="log.LogTest" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>
INFO_FILE:
[INFO ][2020/09/06 21:06:26][main] log.LogTest - info
[WARN ][2020/09/06 21:06:26][main] log.LogTest - warn
[ERROR][2020/09/06 21:06:26][main] log.LogTest - error
ERROR_FILE:
[ERROR][2020/09/06 21:06:26][main] log.LogTest - error
可见,info日志把warn和error也打印出来了,与error日志产生重复,不符合我们开始的需求,这里是什么问题呢?
问题原因
配置错误,这里要讲到两个比较常见的过滤器,ThresholdFilter和LevelFilter。
其中ThresholdFilter是记录比配置的level大的所有日志,LevelFilter配合Onmatch和misMatch过滤某几个特定的level日志。
- ThresholdFilter:
public FilterReply decide(ILoggingEvent event) {
if (!isStarted()) {
return FilterReply.NEUTRAL;
}
// 大于等于配置的level,则通过,返回NEUTRAL,否则deny
if (event.getLevel().isGreaterOrEqual(level)) {
return FilterReply.NEUTRAL;
} else {
return FilterReply.DENY;
}
}
其中NEUTRAL为透传,直接传递到下一个filter,如果不存在下一个filter,就打印日志。示例中用了ThresholdFilter,level为INFO,所以大于等于INFO的日志都会打印。
- LevelFilter
public FilterReply decide(ILoggingEvent event) {
if (!isStarted()) {
return FilterReply.NEUTRAL;
}
// 如果日志level与配置相同,则根据配置的onMatch与onMismatch决定
if (event.getLevel().equals(level)) {
return onMatch;
} else {
return onMismatch;
}
}
如果想只打印INFO日志,可以使用LevelFilter,做如下修改:
...
<appender name="INFO_FILE" class="ch.qos.logback.core.FileAppender">
<File>${logDir}/INFO_FILE.log</File>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
...
拓展
除了上面的过滤器,还有一些,如EvaluatorFilter,这是一个运算过滤器,你可以自行实现一个ch.qos.logback.core.boolex.EventEvaluator接口的boolean evaluate(E event)方法实例作为运算器,运算返回true的日志将会通过校验。
目前已有几个默认可用的实现:
选OnMarkerEvaluator讲一下,它可以用来收集打标记的日志。如下面的<appender name="LOGIN_SUCCESS_FILE:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name="logDir" value="./logs" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.FileAppender">
<File>${logDir}/INFO_FILE.log</File>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.OnErrorEvaluator">
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.FileAppender">
<File>${logDir}/ERROR_FILE.log</File>
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
// 看这里!!!!!!!!
<appender name="LOGIN_SUCCESS_FILE" class="ch.qos.logback.core.FileAppender">
<File>${logDir}/LOGIN_SUCCESS_FILE.log</File>
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
// login_success标记
<marker>login_success</marker>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>[%-5p][%d{yyyy/MM/dd HH:mm:ss}][%t] %logger - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="log.LogTest" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<appender-ref ref="LOGIN_SUCCESS_FILE"/>
</root>
</configuration>
创建login_success标记,表示对带有login_success标记的日志进行收集,其余日志不收集。
- 示例代码:
public class LogTest {
private static final Logger log = LoggerFactory.getLogger(LogTest.class);
@Test
public void testLogback() {
// 获取marker,打日志的时候传入marker标记
Marker timeMarker = MarkerFactory.getMarker("login_success");
log.info(timeMarker,"XXX用户登录成功!");
log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
}
}
日志打印结果:
成功!