slf4j
log4j -> jul -> slf4j -> logback -> log4j2
简单来说,log4j是第一款日志框架,jdk看这个框架反响不错,就在jdk中内置了java.util.logging这个包提供日志功能。当时有两个日志框架在java开发界并存,如果项目A刚开始使用了log4j
,需要跟另外个项目B融合,项目B使用的是jul
。不同的日志框架的编码风格可能会造成混乱,于是乎遵循软件工程沉淀的门面模式经验,诞生了 slf4j
这个框架。
可以这么理解:
slf4j
作为所有日志框架的前置过滤器,类似 Spring MVC 的 DispatcherServlet,log4j、jul 则是不同的view。这么设计的好处是,不论view如何变化,对客户端提供的请求接口不变、model也是不变的。
logback
和 log4j2
这两个框架都诞生在 slf4j
之后,完全遵循 slf4j
的接口规范。
<configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<root level="debug">
<appender-ref ref="STDOUT" />
root>
configuration>
<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />
<root level="debug">
<appender-ref ref="STDOUT" />
root>
Logger
,但是这个Logger
的名称是顶级的,必须为名称为rootlevel
error > warn > info > debug > trace
Logger
下面可以挂载N个appender
。
log.info(“hello") 即要打印到控制台又要打印到日志文件,那么就需要控制台的appender和日志文件的appender
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
encoder>
appender>
<logger name="com.james.usinglog.InfoTests" level="INFO"/>
<logger name="com.james.usinglog.TraceTests" level="TRACE"/>
<root level="debug">
<appender-ref ref="STDOUT" />
root>
值得注意的是,获取Logger时候,使用的.class文件一定要对应当前类
上文提到 root 是一个特殊的 logger
, 也就是所有包的打印都默认继承一个叫 root
的 logger
。
如果要针对某个类或者包覆盖掉父类的实现,非常简单,提供自己的实现即可。所以定义一个logger
并声明一个独有的 level
就能实现需求。官网对这个继承体系提供例子帮助我们理解:
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logFile.logfile>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%npattern>
encoder>
appender>
<root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
root>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logFile.logfile>
<root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
root>
<logger name="com.james.usinglog.FileOnlyTests" additivity="false">
<appender-ref ref="FILE" />
logger>
<root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
root>
上文提到了 logger 的继承体系,深层次来说,他们的Appender也是继承的。怎么理解?如果一个包有自己的logger,它默认继承了root,则自己的Logger和root的logger都会被触发。这个特性使我们配置一个新logger加入到 root标签,就能做到同时往控制台和文件打印日志。
<logger name="com.james.usinglog.FileOnlyTests" additivity="false">
打破继承体系即可,仅仅声明使用自己的 additivity=“false”
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>rollingLogFile.logfile>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>rollingLogFile.%d{yyyy-MM-dd_HH-mm}.logfileNamePattern>
rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%npattern>
encoder>
appender>
<root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="ROLLING_FILE" />
root>
值得注意的是: class=“ch.qos.logback.core.rolling.RollingFileAppender” 一定要区分与普通的
FileAppender 实现,不然加载配置文件会报错
仅仅是使用一个logback 的 Appender实现。关键是理解表达式 %d{yyyy-MM-dd_HH-mm}
的副作用。官网明确说明是有两个副作用:
1. 确认文件名格式
2. 以最小单位进行分片。EG:{yyyy-MM-dd_HH-mm}
就是以分钟为单位进行分片,如:
更多表达式可以参考官网
理解了 logger 的继承体系和 appender 的具体实现后,组织自己的日志策略就相对简单了。诚然,生产环境的配置更加复杂,包括引入了邮件预警等实现,这个需要使用到其他包,后续有机会会研究这个东西。
附上本文练习用的github地址:
https://github.com/ChenghanY/usinglog