日志门面和日志实现
jcl和slf4j都是日志门面
jul,log4j1.x,log4j2,logback都是日志的实现
有说log4j既是门面也是实现, 待我进一步考证理解
jcl的日志使用
public class JclTest {
@Test
public void jclTest() {
Log log = LogFactory.getLog("jcl");
// 常用日志默认格式:
// 信息: 这是一个jcl日志, 使用什么实现? 这是jul格式,默认红色
log.info("这是一个jcl日志, 使用什么实现?");
}
}
其实日志使用很简单,我们比较迷糊的还是日志的依赖组合
jcl+log4j1.x的依赖组合
完整依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jcl</artifactId>
<dependencies>
<dependency>
<groupId>len.hgy</groupId> <!--parent只会传递器依赖, 并不会传递器src的代码(也就是非jar的类容)-->
<artifactId>logger-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- <dependency> <!–log4j 1.x–>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-log4j</artifactId>-->
<!-- <version>1.3.8.RELEASE</version>-->
<!-- </dependency>-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
附一个简单的日志配置
log4j.properties
log4j.rootLogger=DEBUG, stdout, logfile
log4j.category.org.springframework=ERROR
log4j.category.org.apache=INFO
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=log4jV1 %d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=${logger-root}/WEB-INF/log/jcl.log
log4j.appender.logfile.MaxFileSize=512KB
log4j.appender.logfile.MaxBackupIndex=5
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=log4jV1 %d %p [%c] - %m%n
打印日志格式:
log4jV1 2021-12-29 00:09:05,380 INFO [jcl] - 这是一个jcl日志, 使用什么实现?
说明: log4j1.x还是社区维护的开源项目, 到了log4j2已经是apache基金会的开源项目,所以依赖坐标变了
jcl+log4j2的依赖组合
完整依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jcl</artifactId>
<dependencies>
<dependency>
<groupId>len.hgy</groupId> <!--parent只会传递器依赖, 并不会传递器src的代码(也就是非jar的类容)-->
<artifactId>logger-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- <dependency> <!–log4j 1.x–>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-log4j</artifactId>-->
<!-- <version>1.3.8.RELEASE</version>-->
<!-- </dependency>-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency> <!--log4j2 已经属于apache项目了-->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
配置: log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="log4j2 %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
打印日志格式:
log4j2 00:06:03.254 [main] INFO jcl - 这是一个jcl日志, 使用什么实现?
原生的jcl+jul组合的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jcl</artifactId>
<dependencies>
<dependency>
<groupId>len.hgy</groupId> <!--parent只会传递器依赖, 并不会传递器src的代码(也就是非jar的类容)-->
<artifactId>logger-root</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- <dependency> <!–log4j 1.x–>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-log4j</artifactId>-->
<!-- <version>1.3.8.RELEASE</version>-->
<!-- </dependency>-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
对,你没有看错,就是不需要引入日志的实现,因为jdk已经自带了, jul = java.util.logging
打印日志:
十二月 29, 2021 12:11:26 上午 jcl.JclTest jclTest
信息: 这是一个jcl日志, 使用什么实现?
这个日志很特别它默认是红色的(我还不知道怎么修改颜色), 类基本信息和日志信息是换行打印的,这意味着它的行数更多, 它支持中文时间(没什么实际意义), jul的存在感几乎没有, 已经是被淘汰的日志系统了
jcl日志的原理
Apache Commons Loging (JCL)
Commons Loging 本身只提供日志接口,具体实现在运行时动态寻找对应组件?比如: log4j、jdk14looger 等。
JCL实现逻辑
通过jcl 的源码可看出,jcl为每一种日志实现采用了一个适配器,具体采用哪个 是根据 动态的 根据指定顺序查找classPath 是否存在相应的实现。如果一个应用当中有多个classLoader 。 比如OSGI 规定了每个模块都有其独立的ClassLoader 。这种机制保证了插件互相独立, 同时也 限制了JCL在OSGi中的正常使用。这时出现了slf4j 基于静态绑定的方式解决了这个问题。
jcl源码
apache jcl源码
学会了使用,我们就该了解其原理了
入口:
public void jclTest() {
Log log = LogFactory.getLog("jcl"); // 这个是核心入口
// 常用日志默认格式:
// 信息: 这是一个jcl日志, 使用什么实现? 这是jul格式,默认红色
log.info("这是一个jcl日志, 使用什么实现?");
}
// LogFactory#getLog(java.lang.String)
public static Log getLog(String name) throws LogConfigurationException {
return getFactory().getInstance(name);
}
// LogFactoryImpl#getInstance(java.lang.String)
public Log getInstance(String name) throws LogConfigurationException {
Log instance = (Log) instances.get(name); // name的日志是否已经创建了
if (instance == null) {
instance = newInstance(name); // 没有创建重新new一个实例
instances.put(name, instance); // 放入Hashtable的缓存中
}
return instance; // 返回实例
}
protected Log newInstance(String name) throws LogConfigurationException {
Log instance;
try {
if (logConstructor == null) { // log构造器为空就调用日志发现方法
instance = discoverLogImplementation(name);
}else {
Object params[] = { name };
instance = (Log) logConstructor.newInstance(params);
}
if (logMethod != null) {
Object params[] = { this };
logMethod.invoke(instance, params);
}
return instance;
}
}
上面有一个很重要的getFactory没有说明,避免代码太乱,单独说明
// LogFactory#getLog(java.lang.String)
public static Log getLog(String name) throws LogConfigurationException {
return getFactory().getInstance(name);
}
// 这里我们进入getFactory, 因为不同的Factory将会使用不同的getInstance, 导致使用的日志实现不同
// public static final String FACTORY_PROPERTY = "org.apache.commons.logging.LogFactory";
// 这是获取jcl的factory的片段
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
"' as specified by system property " + FACTORY_PROPERTY);
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
}
}
// 这是更加类加载器获取factory的代码片段
// This code is needed by EBCDIC and other strange systems.
// It's a fix for bugs reported in xerces
BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (java.io.UnsupportedEncodingException e) {
rd = new BufferedReader(new InputStreamReader(is));
}
String factoryClassName = rd.readLine();
rd.close();
if (factoryClassName != null && ! "".equals(factoryClassName)) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " +
factoryClassName +
" as specified by file '" + SERVICE_ID +
"' which was present in the path of the context classloader.");
}
factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
}
发现日志入口
LogFactoryImpl#discoverLogImplementation
从这方法名我们可以知道,这个意思是 发现日志实现,意思已经很明确了
方法有一个循环遍历
Log result = null;
...
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
// 循环中的属性的顺序就决定它的优先级
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER, // org.apache.commons.logging.impl.Log4JLogger 高
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog" // 低
}; // 可见commons-loggin对这些类型的日志都做了一个适配的类
注意,这个退出的条件是&& result == null
, 所以就算没有遍历完, 只要创建了日志类,就退出
所以我们非常有必要看看这个方法createLogFromClass
Class logAdapterClass = null;
ClassLoader currentCL = getBaseClassLoader(); // 获取类加载器
URL url;
String resourceName = logAdapterClassName.replace('.', '/') + ".class";
if (currentCL != null) {
url = currentCL.getResource(resourceName );
} else {
url = ClassLoader.getSystemResource(resourceName + ".class");
} // 简单粗暴,直接根据类名去查找
更具返回的具体日志类型实现打印日志
这么多的实现类
spring-jcl源码
上面我们知道了apache jcl的工厂实现类是
org.apache.commons.logging.impl.LogFactoryImpl#getInstance(java.lang.String)
但是如果你使用的spring,可能会有这个类
org.apache.commons.logging.LogFactoryService
注释翻译:
标准Apache Commons Logging的LogFactory类的最小子类,覆盖抽象的getInstance查找方法。 这只适用于标准公共日志jar意外进入类路径的情况,而标准LogFactory类执行其META-INF服务发现。 这个实现简单地委托给Spring的通用Log工厂方法。
已弃用: 因为它只适用于上述的后备方案
一次这个也是不推荐的方式, 我们来看下代码
public Log getInstance(String name) {
return LogAdapter.createLog(name);
}
static { // 初始化 logApi的类型
if (isPresent(LOG4J_SPI)) {
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
// Class.forName判断类有没有通过spi导入
private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false;
}
}
// 更具初始化的日志信息创建适配的Log对象
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name); // log4j的适配器
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name); // slf4j的适配器
default:
// Defensively use lazy-initializing adapter class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
// 没有找到就使用jul, 所以jdk自带一个日志还是很有必要的, 对于不会使用日志的小白开发省去了麻烦
return JavaUtilAdapter.createLog(name);
}
}
这个使用的方式就不是通过哦ClassLoader进行动态绑定的了,这个就是静态绑定, 因为项目的jar包他国Class.forName加载就已经确定了是否可以使用
为什么现在推荐使用slf4j不使用jcl
commons-logging在2015之后就没有更新了
Class logAdapterClass = null;
ClassLoader currentCL = getBaseClassLoader(); // 获取类加载器
jcl是通过调用日志的ClassLoader去当前的classpath中找日志实现类, 属于一种动态绑定方式,对于不同的类加载器,加载出来的日志,可能会部分特性不能使用
怎么理解,画个图理解
spring-jcl使用的是静态绑定,但是spring-jcl注释也说明弃用了, 在LogFactoryImpl没有获取到时候做了一个兜底操作,不推荐再使用了
所以我们应该使用slf4j
slf4j使用(适配方案)
SLF4j 组成方案
全称 Simple Logging Facade for Java(简单日志门面),与jcl 类似本身不替供日志具体实 现,只对外提供接口或门面。与commons loging 不同的是其采用在classPath 加入适配器ajar 包来表示具体采用哪种实现 :
-
slf4j-log4j12.jar (表示适配 log4j) (不需要实现)
-
slf4j-jdk14.jar(表示适配 jdk Logging) (不需要实现包)
这个就是 JUL, 这个14可不是指的jdk14,而是jdk1.4,因为不能使用点,所以省略了,log4j12同理
-
slf4j-jcl.jar(表示适配 jcl) (不需要实现包)
-
log4j-slf4j-impl(表示适配 log4j2) (不需要实现包)
-
logback-classic(表示适配 logback) (不需要实现包)
所以说slf4j是可以不引入api的依赖包的, 因为这些适配包里面都有slf4j的接口类的依赖(适配一般已经集成了API的jar), 保证我们代码各个场景都可以编译通过
基本测试类
public class SLF4JTest {
@Test
public void testSjf4j() {
Logger log = LoggerFactory.getLogger(SLF4JTest.class);
log.info("这是一个slf4j的日志");
}
}
slf4j+log4j1.x依赖组合
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>slf4j</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>1.7.30</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.32</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.logging.log4j</groupId>-->
<!-- <artifactId>log4j-slf4j-impl</artifactId>-->
<!-- <version>2.17.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
</project>
日志配置文件同上,不再赘述
日志格式:
log4jV1 2021-12-29 02:19:39,727 INFO [slf4j.SLF4JTest] - 这是一个slf4j的日志
slf4j+log4j2.x依赖组合
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>slf4j</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>1.7.30</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artifactId>-->
<!-- <version>1.7.32</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
</project>
由于log4j-slf4j-impl既是适配包也是实现包,所以log4j2只需要引入一个依赖即可
日志配置文件同上,不再赘述
日志格式
log4j2 02:22:54.853 [main] INFO slf4j.SLF4JTest - 这是一个slf4j的日志
slf4j+jdk14依赖组合
依赖: 同样是适配包,所以不需要单独引入实现
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>slf4j</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>1.7.30</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.32</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artifactId>-->
<!-- <version>1.7.32</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.logging.log4j</groupId>-->
<!-- <artifactId>log4j-slf4j-impl</artifactId>-->
<!-- <version>2.17.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
</project>
日志格式: 红色
十二月 29, 2021 2:44:43 上午 slf4j.SLF4JTest testSjf4j
信息: 这是一个slf4j的日志
slf4j+jcl依赖组合
有问题的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>slf4j</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-api</artifactId>-->
<!-- <version>1.7.30</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.32</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-jdk14</artifactId>-->
<!-- <version>1.7.32</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-log4j12</artifactId>-->
<!-- <version>1.7.32</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.apache.logging.log4j</groupId>-->
<!-- <artifactId>log4j-slf4j-impl</artifactId>-->
<!-- <version>2.17.0</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
</project>
报错如下:
java.lang.StackOverflowError
at java.lang.reflect.Method.copy(Method.java:153)
at java.lang.reflect.ReflectAccess.copyMethod(ReflectAccess.java:140)
at sun.reflect.ReflectionFactory.copyMethod(ReflectionFactory.java:316)
at java.lang.Class.searchMethods(Class.java:3013)
at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
at java.lang.Class.getMethod0(Class.java:3018)
at java.lang.Class.getMethod(Class.java:1784)
at org.apache.commons.logging.LogFactory.directGetContextClassLoader(LogFactory.java:896)
调用栈溢出
slf4j+logback依赖组合
这个依赖可以使用
<dependency>
<groupId>com.networknt</groupId>
<artifactId>slf4j-logback</artifactId>
<version>2.0.32</version>
</dependency>
包含了logback-classic,但是明显这不是一个正式包
实际使用包
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.9</version>
</dependency>
日志格式
02:53:47.207 [main] INFO slf4j.SLF4JTest - 这是一个slf4j的日志
slf4j常见的报错
SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
因为静态日志绑定器加载失败, 缺少包
比如 log4j1.x的适配包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.32</version>
</dependency>
log4j2.x的适配包(log4j2属于apache项目了)
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId> (可能是为了和log4j12区分开, 将slf4j-log4j改成了log4j-slf4j并加了impl)
<version>2.17.0</version>
</dependency>
slf4j桥接方案
既然有了适配器方案,为什么还要使用桥接?
如果我们新代码想使用适配方案使用log4j2,但是老代码使用的是log4j,此时我们如果直接使用,代码将会有两个日志系统在打印日志,是非常不好的
于是就有了桥接方案, 我们可以为旧代码搭建一桥梁,让它的日志也通过slf4j打印不就好了吗?
slf4j桥接方案原理图
只要classPath 当中指定 sfl4j 适配器 包即可无缝将 原日志输出转移动slf4j上来。
-
jcl-over-slf4j :转移jcl 日志至slf4j
-
log4j-over-slf4j : 转移log4j 日志至 slf4j
-
jul-over-slf4j : 转移jul 日志至 slf4j
为什么没有log4j2和logback的桥接方案, 因为这两个日志框架足够优秀, 没有需要被桥接的需求
log4j直接打印 + log4j2适配案例桥接整改
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>logger-root</artifactId>
<groupId>len.hgy</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>slf4j-bridging</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
</dependencies>
</project>
日志配置同上
log4j.properties
log4j2.xml
测试代码
public class SLF4jBridgingTest {
@Test
public void testSlf4jBridging() {
Logger log = LoggerFactory.getLogger("slf4j");
log.info("这是一个slf4j的日志");
org.apache.log4j.Logger log4j = org.apache.log4j.Logger.getLogger("log4j");
log4j.info("这是一个log4j日志");
}
}
打印结果
log4j2 03:32:57.038 [main] INFO slf4j - 这是一个slf4j的日志
log4jV1 2021-12-29 03:32:57,086 INFO [log4j] - 这是一个log4j日志
很明显出现了两个日志系统同时打印
通过log4j-over-slf4j 桥接整改
在加一个桥接依赖包,同时需要去掉log4j的依赖包,具体改动如下
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.32</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
再次执行测试代码,结果如下
log4j2 03:39:15.451 [main] INFO slf4j - 这是一个slf4j的日志
log4j2 03:39:15.460 [main] INFO log4j - 这是一个log4j日志
现在两个打印都是使用log4j2了
其他日志的桥接方案类似处理
解决slf4j+jcl适配器方案报的StackOverflowError
报错
java.lang.StackOverflowError
at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
at java.lang.Class.getMethod0(Class.java:3018)
at java.lang.Class.getMethod(Class.java:1784)
at org.apache.commons.logging.LogFactory.directGetContextClassLoader(LogFactory.java:896)
at org.apache.commons.logging.LogFactory$1.run(LogFactory.java:862)
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.commons.logging.LogFactory.getContextClassLoaderInternal(LogFactory.java:859)
at org.apache.commons.logging.LogFactory.getFactory(LogFactory.java:423)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
at org.slf4j.impl.JCLLoggerFactory.getLogger(JCLLoggerFactory.java:77) ## JCL
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
at org.apache.commons.logging.LogAdapter$Slf4jAdapter.createLocationAwareLog(LogAdapter.java:130)
at org.apache.commons.logging.LogAdapter.createLog(LogAdapter.java:91)
at org.apache.commons.logging.LogFactoryService.getInstance(LogFactoryService.java:46)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
at org.slf4j.impl.JCLLoggerFactory.getLogger(JCLLoggerFactory.java:77)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
at org.apache.commons.logging.LogAdapter$Slf4jAdapter.createLocationAwareLog(LogAdapter.java:130)
at org.apache.commons.logging.LogAdapter.createLog(LogAdapter.java:91)
at org.apache.commons.logging.LogFactoryService.getInstance(LogFactoryService.java:46)
at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:704)
at org.slf4j.impl.JCLLoggerFactory.getLogger(JCLLoggerFactory.java:77)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:363)
很显然, jcl是一个门面没有日志实现, 也不匹配默认的jul包,导致,jcl还是去找jcl的门面,形成死循环, 最后栈溢出
尝试解决这个问题, 我有两个思路, 一个是使用 jcl-over-slf4j 桥接到slf4j,另一个是添加日志实现,打破死循环
但是细想, 其实这两个是同一个问题, 就算桥接到slf4j, 还是没有实现包
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.17.0</version>
</dependency>
报错
SLF4J: Detected both jcl-over-slf4j.jar AND bound slf4j-jcl.jar on the class path, preempting StackOverflowError.
SLF4J: See also http://www.slf4j.org/codes.html#jclDelegationLoop for more details.
java.lang.ExceptionInInitializerError
at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:71)
at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:42)
at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
at slf4j.SLF4JTest.testSjf4j(SLF4JTest.java:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.IllegalStateException: Detected both jcl-over-slf4j.jar AND bound slf4j-jcl.jar on the class path, preempting StackOverflowError. See also http://www.slf4j.org/codes.html#jclDelegationLoop for more details.
at org.slf4j.impl.JCLLoggerFactory.<clinit>(JCLLoggerFactory.java:54)
... 75 more
很明显jcl-over-slf4j.jar AND bound slf4j-jcl.jar不能同时使用
所以我尝试使用第二个方案
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.7.32</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>jcl-over-slf4j</artifactId>-->
<!-- <version>1.7.32</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.0</version>
</dependency>
通过添加实现的方式确实解决了, 不过这不就是jcl+log4j2的方式的一部分吗?我们的是slf4j+jcl+log4j2
所以我们为什么不直接使用 slf4j+log4j2呢? 解决这个问题只是为了自己更深入理解日志框架, 实际生成中不会这么用
slf4j源码
待完成, 太晚了