1 介绍:日志门面
当系统变得更加复杂的时候,日志就容易发生混乱。随着系统开发的进行,可能会更新不同的日志框架。造成当前系统中存在不同的日志依赖,难以统一管理和控制。即便强制要求所有模块使用相同的日志框架,系统难免使用其它类似spring,mybatis等其它的第三方框架,它们依赖于我们规定不同的日志框架,且它们自身的日志系统就有着不一致性,依然会出现日志体系的混乱。
所以我们需要借鉴JDBC思想,为日志系统提供一套门面,就可以面向这些接口规范来开发,避免了直接依赖具体的日志框架。
常见的日志门面:JCL、slf4j;常见的日志实现:JUL、Log4j、logback、log4j2
日志门面和日志实现的关系:
用户-》日志接口(Commons-logging、Slf4j)-》日志实现(Log4j、Java.logging、Slf4j-nop、Slf4j-simple、Logback…)
日志框架出现的历史顺序:log4j->JUL->JCL->slf4j->logback->log4j2
常用组合:slf4j+log4j2 ;logback+log4j2
2 SLF4J的使用
简单日志门面(Simple Logging Facade For Java)主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其它日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。
官网:https://www.slf4j.org/ 查看:
而且类路径要绑定具体的日志实现框架,否则无操作去实现,因为slf4j只是接口。
SLF4J是目前市面上最流行的日志门面。SLF4J主要提供以下两大功能:
1 日志框架的绑定
2 日志框架的桥接
3 依赖配置
pom.xml中配置slf4j依赖:
<!-- slf4j 日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!-- slf4j 内置的简单实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.32</version>
</dependency>
配置junit依赖:
<!-- 只希望junit在src下的main中使用 -->
<dependency>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>compile</scope>
</dependency>
<!-- 只希望junit在src下的test中使用-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!--如果希望junit在main和test下均可使用,去掉scope即可-->
4 SLF4J初识
package com.base7;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SLF4JTest {
public static final Logger LOGGER= LoggerFactory.getLogger(SLF4JTest.class);
@Test
public void testLog(){
//日志输出
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
//使用占位符输出日志信息
String name="xiaoxu";
Integer age=34;
//slf4j的占位符,不用在{}中输入0、1、2等等
LOGGER.info("用户:{},年龄:{}",name,age);
//将系统的异常信息输出
try {
int i=1/0;
} catch (Exception e) {
// e.printStackTrace();
// LOGGER中输出,可以逗号隔开(类似python的print),但是System.out.println只能+拼接
LOGGER.error("出现异常:",e);
}
}
}
如前所述,SLF4J支持各种日志框架。SLF4J发行版附带了几个称为"SLF4J绑定"的jar文件,每个绑定对应一个受支持的框架。
使用slf4j的日志绑定流程:
1.添加slf4j-api依赖
2.使用slf4j的API在项目中进行统一的日志记录
3.绑定具体的日志实现框架:
3.1绑定已经实现了slf4j的日志框架,直接添加对应依赖
3.2绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
4.slf4j有且仅有一个日志实现框架的绑定(如果出现多个默认使用第一个依赖日志实现)
添加依赖:
<!-- logback日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.5</version>
</dependency>
但是执行依然使用的是simpleLoggerFactory:
如果希望执行的是logback的日志,就需要注释掉先前的slf4j-simple依赖,然后在pom.xml中重新执行ctrl+shift+o:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-simple</artifactId>-->
<!-- <version>1.7.32</version>-->
<!-- </dependency>-->
<!-- logback日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.5</version>
</dependency>
将前面用到的slf4j-simple、logback-classic相关的依赖注释掉,保留slf4j-api依赖,以及添加下面的依赖:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.32</version>
</dependency>
重新执行SLF4JTest,不会打印日志:
5.3 log4j日志实现
将日志开关slf4j-nop依赖注释掉,依赖配置如下:
<!-- slf4j日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.30</version>
</dependency>
log4j.properties:
log4j.rootLogger=WARN,conso
log4j.appender.conso=org.apache.log4j.ConsoleAppender
log4j.appender.conso.layout=org.apache.log4j.PatternLayout
log4j.appender.conso.layout.conversionPattern=[%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
同理,去掉上述slf4j-log4j12的依赖配置,增加如下依赖,然后pom.xml中执行ctrl+shift+o:
<!-- 绑定jul日志实现,需要导入适配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.0</version>
</dependency>
重新执行代码:
6 SLF4J桥接旧的日志框架(Bridging)
通常,您依赖的某些组件依赖于SLF4J以外的日志记录API。您也可以假设这些组件在不久的将来不会切换到SLF4J。为了解决这种情况,SLF4J附带了几个桥接模块,这些模块将对log4j、JCL和java.util.loggingAPI的调用重定向,就好像它们是SLF4J的API一样。
桥接解决的是项目中日志的遗留问题,当系统中存在之前的日志API,可以通过桥接转接到slf4j的实现。
1 先去除之前老的日志框架的依赖
2 添加SLF4J提供的桥接组件
3 为项目添加SLF4J的具体实现
举个例子,比如以前的项目代码,没有使用日志门面,比如slf4j,直接使用的log4j日志框架:
package com.base7;
import org.junit.Test;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
import org.apache.log4j.Logger;
public class SLF4JTest {
public static final Logger LOGGER= Logger.getLogger(SLF4JTest.class);
@Test
public void testLog(){
//日志输出
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
如果修改了依赖为logback的日志框架(先注释掉log4j的依赖,放开slf4j-api和logback的依赖):
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<!-- slf4j日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.5</version>
</dependency>
代码就会提示错误(如果整个项目各个地方用到,每个地方都去修改就会工作量很大):
在原有的依赖配置上添加如下依赖:
<!-- 配置log4j的桥接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.32</version>
</dependency>
报错消失:
运行结果如下(表面上看,导入的是log4j的包,实际上执行效果却是logback的日志打印):
注意问题:
1 jcl-over-slf4j.jar和slf4j-jcl.jar不能同时部署。前一个jar文件将导致JCL将日志系统的选择委托给SLF4J,后一个jar文件将导致SLF4J将日志系统的选择委托给JCL,从而导致无限循环。
2 log4j-over-slf4j.jar和slf4j-log4j12.jar不能同时出现。
3 jul-to-slf4j.jar和slf4j-jdk14.jar不能同时出现。
4 所有的桥接都只对Logger日志记录器对象有效,如果程序中调用了内部的配置类或者是Appender、Filter等对象,将无法产生效果。
去掉logback的依赖,依赖配置如下(logback、log4j的依赖全部去掉):
<!-- 配置log4j的桥接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.32</version>
</dependency>
<!-- slf4j日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.32</version>
</dependency>
1 SLF4J通过LoggerFactory加载日志具体的实现对象。
2 LoggerFactory在初始化的过程中,会通过performInitialization()方法绑定具体的日志实现。
3 在绑定具体实现的时候,通过类加载器,加载org/slf4j/impl/StaticLoggerBinder.class
4 所以,只要是一个日志实现框架,在org.slf4j.impl包中提供一个自己的StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以被SLF4J所加载