源码仓库:
- 基于Spring源码:https://gitee.com/Ethanzyc/spring-learn.git
- spring-maven项目:
主流日志技术
JUL
java.util.logging.Logger
这是 jdk 中自带的
代码
package com.example.springmaven.log;import org.junit.jupiter.api.Test;import java.util.logging.Logger;public class TestJul {@Testpublic void jul() {Logger logger = Logger.getLogger("a");logger.info("aaa");}}
Log4j
代码
log4j 一代的依赖如下
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
提一嘴:二代的依赖如下
<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>2.13.2</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.13.2</version></dependency>
创建 log4j.properties 文件
log4j.rootLogger=DEBUG, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=log4j %5p [%t] - %m%n
测试代码如下
package com.example.springmaven.log;import org.apache.log4j.Logger;import org.junit.jupiter.api.Test;public class TestLog4j {@Testpublic void log4j() {Logger logger = Logger.getLogger("a");logger.info("11");}}
缺点
这时候 JUL 和 Log4j 互不影响,但这也会产生一些问题,比如我整个项目的日志就不是统一的,有各种格式,为了解决这个问题,诞生了 commons-logging。
commons-longging
全程叫 jarkartCommins-logging ,简称 JCL。
代码
依赖:
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency>
测试代码如下
package com.example.springmaven.log;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.junit.jupiter.api.Test;public class TestJCL {@Testpublic void jcl() {Log log = LogFactory.getLog("a");log.info("jjjjjjj");}}
为什么是log4j去打印的呢?
jcl 并不是一个具体的日志打印技术,他是一个抽象类,可以看下他的 getLog() 方法。
public static Log getLog(String name) throws LogConfigurationException {return getFactory().getInstance(name);}
public Log getInstance(String name) throws LogConfigurationException {Log instance = (Log) instances.get(name);if (instance == null) {instance = newInstance(name);instances.put(name, instance);}return instance;}
主要看下 newInstance(name)方法
protected Log newInstance(String name) throws LogConfigurationException {Log instance;try {if (logConstructor == null) {instance = discoverLogImplementation(name);}else {Object params[] = { name };instance = (Log) logConstructor.newInstance(params);}if (logMethod != null) {Object params[] = { this };logMethod.invoke(instance, params);}return instance;} catch (LogConfigurationException lce) {// this type of exception means there was a problem in discovery// and we've already output diagnostics about the issue, etc.;// just pass it onthrow lce;} catch (InvocationTargetException e) {// A problem occurred invoking the Constructor or Method// previously discoveredThrowable c = e.getTargetException();throw new LogConfigurationException(c == null ? e : c);} catch (Throwable t) {handleThrowable(t); // may re-throw t// A problem occurred invoking the Constructor or Method// previously discoveredthrow new LogConfigurationException(t);}}
主要看下 discoverLogImplementation(name) 方法里,里面有个 _classesToDiscover_ 对象
private static final String[] classesToDiscover = {LOGGING_IMPL_LOG4J_LOGGER,"org.apache.commons.logging.impl.Jdk14Logger","org.apache.commons.logging.impl.Jdk13LumberjackLogger","org.apache.commons.logging.impl.SimpleLog"};
_classesToDiscover_ 会依次遍历这四个类,调用 createLogFromClass方法
for(int i=0; i<classesToDiscover.length && result == null; ++i) {result = createLogFromClass(classesToDiscover[i], logCategory, true);}
createLogFromClass 方法中会用 Class._forName_(logAdapterClassName) 去加载,如果加载到了,会调用如下方法去实例化。
constructor = c.getConstructor(logConstructorSignature);Object o = constructor.newInstance(params);
但这里实例化的时候其实还包了一层,log4j的类名 private static final String _LOGGING_IMPL_LOG4J_LOGGER _= "org.apache.commons.logging.impl.Log4JLogger"; 其实还是 commons.logging 包下面的,它实例化的时候是通过 getLogger() 方法去实例化 log4j 本体。
public Logger getLogger() {Logger result = logger;if (result == null) {synchronized(this) {result = logger;if (result == null) {logger = result = Logger.getLogger(name);}}}return result;}
流程图
去掉 log4j
所以,如果把 log4j 的依赖去掉,就是 jul 去打印日志了
番外
Class._forName_() 的时候能看它也打印了日志,这时候其实日志打印都还没准备好,点进去看下它是用什么打印的
点到最后发现用的是 PrintStream,所以我们经常看到的日志相关的错误都是直接红字显示的。
private static PrintStream diagnosticsStream = null;
缺点
- 还是存在硬编码。
- 之前假如有log4j打印的,还是不会变,必须
LogFactory.getLog("a")这样写才能做到统一。Slf4j
和 commons logging 不一样,slf4j 采用的是绑定器的思路,可以看下图流程图
代码
依赖如下
测试代码如下 ```java import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version></dependency>
public class TestSlf4j { @Test public void testJCL() { Logger logger = LoggerFactory.getLogger(“a”); logger.info(“slf4j”); } }
这时候执行会报错,可以看到错误就是没有绑定器。<br /><br />这时候我们项目虽然有 slf4j 和 log4j的依赖,但我们并没有把他们绑定在一起,所以 slf4j 并不知道要用什么日志技术去打印。<br />从 slf4j 的官网可以看到有哪些官方的绑定器 [https://www.slf4j.org/manual.html](https://www.slf4j.org/manual.html)<br /><br />所以我们加入 slf4j 和 log4j 的绑定器```java<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.32</version></dependency>
再执行测试代码就正常了
这时候假如把 log4j 的绑定器换成 jul 的,log4j 本身的依赖还是不动
<!-- <dependency>--><!-- <groupId>org.slf4j</groupId>--><!-- <artifactId>slf4j-log4j12</artifactId>--><!-- <version>1.7.32</version>--><!-- </dependency>--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-jdk14</artifactId><version>1.7.32</version></dependency>
缺点
绑定器还是没有解决之前代码用别的日志框架打印的日志。
但slf4j其实也有解决方式,就是 slf4j 的桥接器。
桥接器
现在依赖如下
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><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.4</version></dependency>
logback-classic 包含了 logback 本身和绑定器。也就是说现在我们用 Slf4j 打印的应该是 logback 打印的,但如果是 log4j 打印还是 log4j。如下:

这时候,我们想让 log4j 打印出来的还是用 slf4j 输出,那就要用到桥接器。
依赖如下:
<!-- https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j --><dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.32</version></dependency>
去掉一些冲突依赖,最终依赖如下
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.4</version></dependency><!-- https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j --><dependency><groupId>org.slf4j</groupId><artifactId>log4j-over-slf4j</artifactId><version>1.7.32</version></dependency>
可以看到如下打印,虽然是 log4j 实例化的对象打印的,但还是用 logback 打印的。
