这几天在搭建新的项目服务,正好需要整合日志框架进来,看了一下公司原来项目中有Log4j2、LogBack等好几种日志框架,刚好也有同事问我如何选型,我们从两个方面好好探究一下,首先彻底搞懂Log4j、Log4j2、LogBack,Slf4j他们之间的关系。然后通过代码示例展示如何使用。
一、搞懂Log4j、Log4j2、LogBack,Slf4j的关系
在讲他们之间的关系之前,先认识一个设计模式—门面模式
在Java中,门面模式(Facade Pattern)也被称为外观模式,是一种为子系统中的一组接口提供一个统一的接口的设计模式。
门面模式定义了一个高层接口,这个接口使得子系统更加容易使用。该模式隐藏了系统的复杂性,对客户端来说,只需要知道门面接口即可,无需关心子系统内部的具体实现和细节。
门面模式通常涉及以下几个重要角色:
1.门面(Facade):客户端通过它访问子系统。门面知道如何与子系统的各个组件进行交互,并为客户端提供一个统一和简单的接口。
2.子系统(Subsystem):包含多个组件,每个组件都实现了一些特定的功能。子系统对客户端来说是未知的或者难以使用的。
3.客户端(Client):与门面进行交互,通过门面来间接地使用子系统的功能。
为什么我们先说一下这个门面模式呢,因为现今主流的日志组件都是使用门面模式实现的。而commons-logging(JCL)和Slf4j就是门面模式中的Facade角色。
SLF4J与commons-logging的区别又是什么
1.SLF4J介绍
SLF4J:是一个用于Java的简单日志门面(Facade)或抽象层,它本身并不实现日志功能,而是依赖于其他实际的日志框架(如Logback、log4j、java.util.logging等)来完成日志记录。SLF4J的主要目标是使得应用程序能够在运行时动态地绑定到任何日志框架,而无需修改任何代码。
SLF4J提供了一组简单的API,允许开发人员编写不依赖于特定日志框架的代码。然后,通过在类路径中放置适当的SLF4J绑定和日志框架实现,可以在运行时选择实际的日志框架。
2.commons-logging介绍
commons-logging:是Apache Commons项目的一部分,它提供了一个简单的日志抽象层,类似于SLF4J。然而,与SLF4J不同的是,commons-logging试图自动检测并使用类路径中存在的日志框架,而不是依赖于显式绑定。
commons-logging的设计初衷是为了提供一个统一的日志接口,以便可以在不同的项目中使用不同的日志框架,而无需修改代码。然而,由于其自动检测机制,有时可能会导致不可预测的行为或版本冲突。
说明:
commons-logging这个日志组件是JDK自带的日志框架。由于在使用便利性和性能上都欠佳,所以现在主流使用的还是SLF4J。
下面我们就来看一下slf4j、log4j、logback和log4j2的区别
1.slf4j:
slf4j是一个日志接口(或称为门面/抽象层),它本身并不实现日志功能,而是为各种日志框架(如log4j、logback等)提供了一套统一的接口。
使用slf4j的好处是,开发者可以在代码中只使用slf4j的接口,然后在运行时或部署时根据实际需要选择具体的日志实现框架。这增加了代码的灵活性和可移植性。
slf4j与具体的日志实现框架(如log4j、logback)之间通过桥接器(binding)进行连接。
2.log4j:
log4j是Apache的一个早期日志框架,它允许开发者通过配置文件灵活地控制日志的输出目标、记录级别、输出格式等。
随着时间的推移,log4j已经逐渐被其后续版本log4j2所取代。
3.logback:
logback是log4j的创始人Ceki Gülcü开发的一个新的日志框架,它被视为log4j的继任者。
logback在性能、内存使用和灵活性方面都优于log4j,并且它直接实现了slf4j的接口,因此可以作为slf4j的一个具体实现。logback本身包含了一套完整的日志解决方案,无需依赖其他库。
4.log4j2:
log4j2是log4j的后续版本,它在性能、功能、灵活性和易用性方面都有了很大的提升。
log4j2支持异步日志记录,可以极大地提高多线程环境下的日志性能。
log4j2也支持通过配置文件进行灵活的配置,包括日志的输出目标、记录级别、输出格式等。
log4j2也提供了对slf4j的桥接支持,因此可以作为slf4j的一个具体实现。
总结:
①.slf4j是一个日志接口,它定义了日志记录的规范,但不具体实现日志功能。
②.log4j是一个早期的日志框架,但已经逐渐被其后续版本log4j2所取代。
③.logback是一个新的日志框架,它直接实现了slf4j的接口,并提供了高性能、灵活的日志记录功能。
④.log4j2是log4j的后续版本,它在性能、功能和易用性方面都有很大的提升,并支持作为slf4j的一个具体实现。
项目中如何使用,官方提供了相关的实现和依赖关系图
上述图总结如下:
1.slf4j + log4j:
需要使用slf4j和log4j来进行日志输出的话,我们需要引入下面的jar包
slf4j-api.jar:slf4j核心jar包
log4j.jar:log4j核心jar包
slf4j-log412.jar:slf4j与log4j的桥接包
2.slf4j + logback:
需要使用slf4j和logback来进行日志输出的话,我们需要引入下面的 jar包
slf4j-api.jar: slf4j核心jar包
logback-core.jar:logback直接实现了 slf4j-api 逻辑,不再需要桥接包
logback-classic.jar(集成包)
3.slf4j + jul:
需要使用slf4j和jul来进行日志输出的话,我们需要引入下面的 jar 包
slf4j-api.jar: slf4j核心jar包
slf4j-jdk14.jar:桥接包
4.也可以只用slf4j无日志实现:
slf4j-api.jar + slf4j-nop.jar
官方图中没有log4j2依赖jar的关系
5.slf4j + log4j2:
需要使用 slf4j 和 log4j2 来进行日志输出的话,我们需要引入下面的 jar 包:
log4j2核心jar包:log4j-api.jar 和 log4j-core.jar
slf4j核心jar包:slf4j-api.jar
slf4j与log4j2的桥接包:log4j-slf4j-impl-2.8.2.jar
二、代码实战
项目中应用springboot集成log4j2
SpringBoot默认使用的日志系统为:SLF4j + Logback。
现在需要替换成SLF4j + Log4j2
1.去除SpringBoot中默认的Logback包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
2.添加需要的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
3.配置文件中增加需要的配置
logging:
config: classpath:log4j2-spring.xml
4.新增log4j2-spring.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别以及优先级排序:OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!-- Configuration的status属性用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出 -->
<!-- Configuration的monitorInterval属性用于设置log4j2自动检测配置文件的执行间隔,当检测到有更新时会重新加载配置,单位为秒 -->
<Configuration status="debug" monitorInterval="60">
<Appenders>
<!-- 这个输出控制台的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<!-- 输出日志的格式 -->
<PatternLayout pattern="[%d] %-5p (%F:%L#%M) : %m%n"/>
</Console>
<!-- 输出所有的debug及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="RollingFileDebug" fileName="${sys:user.home}/logs/netmeeting-debug.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/netmeeting-debug-%d{yyyy-MM-dd}-%i.log">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%p] [%l] - %m%xEx%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了5 -->
<DefaultRolloverStrategy max="5"/>
</RollingFile>
<!-- 输出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 -->
<RollingFile name="RollingFileSystem" fileName="${sys:user.home}/logs/netmeeting-system.log"
filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/netmeeting-system-%d{yyyy-MM-dd}-%i.log">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%p] [%l] - %m%xEx%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了5 -->
<DefaultRolloverStrategy max="5"/>
</RollingFile>
</Appenders>
<Loggers>
<!--添加打印sql和结果集的log-->
<Logger name="druid.sql.Statement" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileSystem"/>
</Logger>
<Logger name="com.avicnet.dynamic.datasource" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileSystem"/>
</Logger>
<Logger name="com.avicnet.meeting" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileSystem"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileSystem"/>
</Root>
</Loggers>
</Configuration>
5.在类中打印日志
private static final Logger LOGGER = LoggerFactory.getLogger(TestServiceImpl.class);
LOGGER.info("参数={}", JSONUtil.toJsonStr(resp))
当然也可以使用@Slf4j和@Log4j2注解形式,但是需要引入lombok依赖包。