为什么要使用slf4j和log4j2
1、多日志实现组件存在问题
java世界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,许多应用程序的日志部分都交给了 log4j,不过作为组件开发者,他们希望自己的组件不要紧紧依赖某一个工具,毕竟在同一个时候还有很多其他很多日志工具,假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了。
2、解决多日志实现组件问题,引入JCL
为了解决这个问题,Apache Commons Logging (之前叫 Jakarta Commons Logging,JCL)粉墨登场,JCL 只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。
3、JCL引发的问题
所以即使到现在你仍会看到很多程序应用 JCL + log4j 这种搭配,不过当程序规模越来越庞大时,JCL的动态绑定并不是总能成功,在Sping官方文档中Not Using Commons Logging小节也提到因为算法问题引起,
4、解决JCL动态绑定引发的问题
解决方法之一就是在程序部署时静态绑定指定的日志工具,这就是 SLF4J 产生的原因。
跟 JCL 一样,SLF4J 也是只提供 log 接口,具体的实现是在打包应用程序时所放入的绑定器(名字为 slf4j-XXX-version.jar)来决定,XXX 可以是 log4j12, jdk14, logback, nop 等,他们实现了跟具体日志工具(比如 log4j)的绑定及代理工作。
举个例子:如果一个程序希望用 log4j 日志工具,那么程序只需针对 slf4j-api 接口编程,额外添加slf4j-log4j12-version.jar 和 log4j.jar 的依赖就可以了。
5、排除现有commons-logging依赖的影响
现在还有一个问题,假如你正在开发应用程序所调用的组件当中已经使用了 JCL 的,还有一些组建可能直接调用了 java.util.logging,这时你需要一个桥接器(名字为 jcl-over-slf4j.jar)把他们的日志输出重定向到 SLF4J
所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具。
过程如下:
Component(服务)
|
| log to Apache Commons Logging(JCL)
V
jcl-over-slf4j.jar — (redirect) —> SLF4j —> slf4j-log4j12-version.jar —> log4j.jar —> 输出日志
注意:如果使用了log4j实现slf4j,添加log4j-over-slf4j桥接器,结果就是log4j —> slf4j —> log4j,输出日志命令被踢来踢去,此时要么去掉log4j-over-slf4j桥接器,要么替换slf4j的实现,比如换成logback,否则陷入死循环(这种方式的使用场景是,项目中原来使用的是log4j日志,想要更改为logback,需要先用log4j-over-slf4j桥接到slf4j,然后slf4j再用logback作为日志的实现)
6、如何排除现有commons-logging依赖
①:maven项目,通过标签排除commons-logging依赖,这种方式的弊端是繁琐,不彻底,容易遗漏,说不定某个依赖就又把commons-logging引入了项目
注意:如果在依赖中剔除了common-loggin包,但又不引入其他日志系统,则Spring会在启动的时候报如下错误:
java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory
参考:
②:通过桥接的模式,添加该jar包,所有common-logging真正的实现将由jcl-over-slf4j提供,也就是由slf4j负责
参考地址:http://slf4j.org/faq.html#excludingJCL
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.0-alpha2-SNAPSHOT</version>
</dependency>
7.为什么要使用slf4j和log4j2
①:为什么选择slf4j
②:为什么选择Log4j2
参考文章:
- Log4j 2被设计成可用作审计日志框架。在重新配置时,Log4j 1.x和Logback都会丢失事件。Log4j 2不会。在Logback中,Appender中的异常永远不会对应用程序可见。 在Log4j中,可以将Appender配置为允许异常渗透到应用程序。
- Log4j 2包含基于LMAX Disruptor库的下一代异步日志记录器(Asynchronous Loggers)。 在多线程场景中,相比 Log4j 1.x和Logback ,异步日志记录器的吞吐量高10倍,延迟低几个数量级。
- Log4j 2对于独立应用程序是无垃圾的,对于稳态日志记录期间的Web应用程序是低垃圾。 这减少了垃圾收集器的压力,并提供更好的响应时间性能。
如何使用slf4j和Log4j2
1.导入jar包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.11.1</version>
</dependency>
jcl-over-slf4j: 将jcl日志桥接到slf4j
log4j-slf4j-impl :如果现有组件使用SLF4J,并且您希望将此日志记录路由到Log4j 2
上面两部分官方文档参考:
log4j2: https://logging.apache.org/log4j/2.x/maven-artifacts.html#Optional_Components(仔细看,肯定有)
slf4j: http://www.slf4j.org/manual.html
log4j-web:官方文档参考:https://logging.apache.org/log4j/2.x/manual/webapp.html
2.在classPath下添加 Log4j2.xml
(Log4j2.xml,Log4j2.properties,Log4j2.yaml,Log4j2.json都支持)
支持类型官方文档参考:http://logging.apache.org/log4j/2.x/manual/configuration.html#AutomaticConfiguration
xml内容官方文档参考:http://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax
<?xml version="1.0" encoding="UTF-8"?>
<Configuration monitorInterval="1800">
<!--变量配置 -->
<Properties>
<!-- 格式化输出:%date表示日期 %-5level什么级别的日志 %c代表已逗号分隔的包名展示几个 %msg日志内容 %n换行 -->
<property name="logPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5level %c{20} - %msg%n" />
<!-- 定义日志存储的位置,不要配置相对路径 -->
<property name="folderUrl" value="${sys:catalina.home}/logs" />
<!-- 备份文件数量 -->
<property name="backupsFileNum" value="31" />
</Properties>
<Appenders>
<!-- 输出到控制台 -->
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="${logPattern}" />
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</Console>
<!-- 输出到文件中 fileName文件的名称 filePattern当文件备份时的路径和格式 -->
<RollingRandomAccessFile name="infoLevelLog" fileName="${folderUrl}/info.log"
filePattern="${folderUrl}/$${date:yyyy-MM}/info/info-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${logPattern}" />
<Filters>
<!-- ThresholdFilter level代表匹配大于等于该级别的日志,onMatch匹配到做什么, onMismatch没有匹配到做什么,三个值,
ACCEPT 接受, DENY 拒绝, NEUTRAL 中立,即向下执行,在有多个过滤器的时候必须完全匹配才行-->
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Policies>
<!-- modulate="true" (boolean)以0点为边界进行偏移计算 -->
<!-- interval="1" 表示将1天一个日志文件 和 filePattern 的日期格式有关,如果日期格式带有小时,就是以小时为单位,不然就是以天为单位 -->
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了31 -->
<DefaultRolloverStrategy max="${backupsFileNum}"/>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="warnLevelLog" fileName="${folderUrl}/warn.log"
filePattern="${folderUrl}/$${date:yyyy-MM}/warn/warn-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${logPattern}" />
<Filters>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
</Policies>
<DefaultRolloverStrategy max="${backupsFileNum}"/>
</RollingRandomAccessFile>
<RollingRandomAccessFile name="errorLevelLog" fileName="${folderUrl}/error.log"
filePattern="${folderUrl}/$${date:yyyy-MM}/error/error-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${logPattern}" />
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<Policies>
<TimeBasedTriggeringPolicy modulate="true" interval="1"/>
</Policies>
<DefaultRolloverStrategy max="${backupsFileNum}"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<logger name="cn.yuexiang365" level="info" additivity="false">
<AppenderRef ref="infoLevelLog" />
<AppenderRef ref="warnLevelLog" />
<AppenderRef ref="errorLevelLog" />
</logger>
<Root level="info">
<AppenderRef ref="stdout" />
<AppenderRef ref="warnLevelLog" />
<AppenderRef ref="errorLevelLog" />
</Root>
</Loggers>
</Configuration>
3.日志打印
获取日志对象的两种方式
private Log log = LogFactory.getLog(getClass()); //jcl
private Logger log = LoggerFactory.getLogger(xxx.class); //slf4j
通过jcl获取的日志打印对象和slf4j获取的日志打印对象都是log4j2,因为已经通过jcl-over-slf4j将jcl桥接到slf4j了,slf4j获取的日志实现是log4j2,所以两者获取的日志对象实现类都是log4j2.
log.info("info"); //注意xml中设置的日志打印级别,如果高于该级别,通过该级别的打印就无法显示
问题锦集
配置失效导致spring等框架一直打印DEBUG级别的日志
在应用中使用了SpringMVC+hibernate+Log4j+SLF4J。Log4j的日志级别调到INFO。但是在web容器(Tomcat)在启动和运行的时候spring,Hibernate等框架中DEBUG级别的日志不断狂刷,导致日志文件剧增,磁盘没几天就爆了。
一直以为自己的Log4j配置文件写的有问题。后来在自己新搭建的project中发现Log4j的配置是正确的。只打印出INFO及以上级别的日志信息。后来才知道,是因为自己的应用依赖的jar隐式依赖了logback日志框架。
Log4j和logback是有冲突的,这样会导致Log4j的日志级别降低到DEBUG级别。知道了原因,就在maven依赖中把logback的隐式依赖给去掉。然后启动tomcat,发现日志可以正常打印。
log4j2 无法绑定到 SLF4J
SLF4J: No SLF4J providers were found.
pom.xml中添加的依赖有问题
官方文档解释:https://logging.apache.org/log4j/2.x/log4j-slf4j-impl/index.html