Java日志 - log4j2升级为logback,涉及commons-logging、slf4j、Jboss-logging日志组件(SSH项目)
1 前言
SSH框架中已集成log4j2,因为要通过kafka接入elk,因为接入demo采用的是logback作为日志组件,所以需要将日志组件改为logback,由logback生产日志发送给kafka,接入到elk。因为SSH框架老旧,改造中又涉及到的日志工具:lagback、log4j2、commons-logging、slf4j、jboss-logging,遇到较多问题,因此梳理出了改造过程。
关于java各日志工具的解释、jar包关系可以看,或者是log4j改造为logback,可以结合两篇文章参考:log4j升级为log4j2,log4j2+slf4j+commons-logging+Jboss-logging(SSH项目)
slf4j 与 logback 集成可能的改造点
- log4j2 日志工具相关依赖删除
- 保留slf4j与commons-logging, commons-logging委托给slf4j
- 保留Jboss-logging与slf4j集成,Jboss-logging是自动匹配log4j,可能要搭建Jboss -> slf4j -> logback 的桥梁
- 增加logback相关依赖,搭建slf4j->logback,logback作为日志实现
2 改造步骤及问题解决(log4j2升级至logback)
2.1 日志实现工具的替换:log4j2移除,替换为logback
- 删除log4j1依赖及配置文件:log4j2.xml、log4j-api-2.11.0.jar、log4j-core-2.11.0.jar
- 增加logback的依赖:logback-classic-1.2.3.jar,logback-core-1.2.3.jar
- 增加logback的配置文件:logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 日志输出格式定义 -->
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} - %X{hotel} - %X{userName} %-5level %logger{50} - %msg%n"/>
<!-- 日志输出文件性格定义 -->
<Property name="LOG_FILE_PATH" value="${catalina.home}/projectName_log"/>
<Property name="ARCHIVE_LOG_FILE_PATH" value="${LOG_FILE_PATH}/archiveLog"/>
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--info 级别的日志-->
<!-- 按照每天生成日志文件 -->
<appender name="ROLLING_LOG_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<file>${LOG_FILE_PATH}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<fileNamePattern>${ARCHIVE_LOG_FILE_PATH}/info-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<!--日志文件保留天数-->
<MaxHistory>180</MaxHistory>
</rollingPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ROLLING_LOG_INFO"/>
</root>
</configuration>
logback更详细的配置文件情况可以看这篇:logback 配置文件详解
注意,若代码中存在使用log4j2直接作为日志输出,因修改日志输出代码,采用slf4j编程,见2.2 使用slf4j API的方式
2.2 logback、slf4j、commons-logging的集成 (解决spring日志的输出问题)
- 保留 jcl-over-slf4j-1.7.5.jar,实现JCL与slf4j的集成。
- 保留 slf4j相关的jar包:slf4j-api-1.7.5.jar
- 删除掉原本slf4j与log4j2的桥梁包:log4j-slf4j-impl-2.11.0.jar,不删除则会和其他桥梁冲突,slf4j不知道要将日志实现交给谁来处理。
- 同log4j2+slf4j不同的是,logback+slf4j不需要额外的桥梁包。
和log4j2集成类似,已经集成了slf4j,建议采用slf4j提供的API进行编程,而不是采取使用logback的API。采用slf4j,后续具体实现日志的工具有进行升级更换,如换成logback,则不需要再改程序代码,只需要修改相应桥梁的jar包即可,采用slf4j编写如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SystemAction {
private final Logger log = LoggerFactory.getLogger(SystemAction.class);
public void valid() {
log.info("效验通过slf4j日志输出");
}
}
2.3 jboss-logging、logback、slf4j的集成
Hibernate自带Jboss-logging,没有指明日志实现,jboss-logging默认会从:jboss、jdk、log4j2、log4j、slf4j的顺序去找(不同版本的jboss-logging顺序可能不同)
//jboss-logging的源码
//package org.jboss.logging.LoggerProviders
try {
String loggerProvider = SecurityActions.getSystemProperty("org.jboss.logging.provider");
if (loggerProvider != null) {
if ("jboss".equalsIgnoreCase(loggerProvider)) {
return tryJBossLogManager(cl, "system property");
}
if ("jdk".equalsIgnoreCase(loggerProvider)) {
return tryJDK("system property");
}
if ("log4j2".equalsIgnoreCase(loggerProvider)) {
return tryLog4j2(cl, "system property");
}
if ("log4j".equalsIgnoreCase(loggerProvider)) {
return tryLog4j(cl, "system property");
}
if ("slf4j".equalsIgnoreCase(loggerProvider)) {
return trySlf4j("system property");
}
}
} catch (Throwable var8) {
}
按理说我们删掉了log4j2,会采用slf4j,最终通过logback实现日志输出,但实际并非如此。集成后在Jenkins构建执行单元测试时,在Sring加载上下文过程中会出现如下异常:
Caused by: java.lang.NoSuchFieldError: TRACE
at org.jboss.logging.Log4jLogger.translate(Log4jLogger.java:55)
at org.jboss.logging.Log4jLogger.isEnabled(Log4jLogger.java:35)
at org.jboss.logging.Logger.isTraceEnabled(Logger.java:98)
at org.hibernate.internal.CoreMessageLogger_$logger.isTraceEnabled(CoreMessageLogger_$logger.java:415)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:145)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:131)
...
这种异常的解决有两种方式:
- 第一种是保留log4j2,但仅作为Jboss-logging的日志实现,因为去掉了slf4j和log4j2的桥梁(log4j-slf4j-impl-2.11.0.jar),不会影响到slf4j编写的日志,通过logback实现日志输出。(不建议这种方式,留着两个日志的实现工具,多余并且并不能完美处理,会导致出现部分日志并不通过logback,日志缺失)
- 第二种是指定Jboss-logging通过slf4j作为日志输出,最终通过logback实现(研究了好久~推荐),实现方式是通过在spring配置中配置,Sring启动时指定。
<!-- 设置修改Hibernate jboss-logging 日志输出指定为SLF4J-->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject">
<!-- System.getProperties() -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="java.lang.System"/>
<property name="targetMethod" value="getProperties"/>
</bean>
</property>
<property name="targetMethod" value="putAll"/>
<property name="arguments">
<!-- The new Properties -->
<props>
<prop key="org.jboss.logging.provider">slf4j</prop>
</props>
</property>
</bean>
上述不是唯一的方式,该文章介绍了多种指定Jboss-logging委托给slf4j的方式:Jboss-logging委托给slf4j