源码仓库:

主流日志技术

JUL

java.util.logging.Logger
这是 jdk 中自带的

代码

  1. package com.example.springmaven.log;
  2. import org.junit.jupiter.api.Test;
  3. import java.util.logging.Logger;
  4. public class TestJul {
  5. @Test
  6. public void jul() {
  7. Logger logger = Logger.getLogger("a");
  8. logger.info("aaa");
  9. }
  10. }

默认效果如下
image.png

Log4j

代码

log4j 一代的依赖如下

  1. <dependency>
  2. <groupId>log4j</groupId>
  3. <artifactId>log4j</artifactId>
  4. <version>1.2.17</version>
  5. </dependency>

提一嘴:二代的依赖如下

  1. <dependency>
  2. <groupId>org.apache.logging.log4j</groupId>
  3. <artifactId>log4j-api</artifactId>
  4. <version>2.13.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.logging.log4j</groupId>
  8. <artifactId>log4j-core</artifactId>
  9. <version>2.13.2</version>
  10. </dependency>

创建 log4j.properties 文件

  1. log4j.rootLogger=DEBUG, stdout
  2. log4j.appender.stdout=org.apache.log4j.ConsoleAppender
  3. log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
  4. log4j.appender.stdout.layout.ConversionPattern=log4j %5p [%t] - %m%n

测试代码如下

  1. package com.example.springmaven.log;
  2. import org.apache.log4j.Logger;
  3. import org.junit.jupiter.api.Test;
  4. public class TestLog4j {
  5. @Test
  6. public void log4j() {
  7. Logger logger = Logger.getLogger("a");
  8. logger.info("11");
  9. }
  10. }

image.png

缺点

这时候 JUL 和 Log4j 互不影响,但这也会产生一些问题,比如我整个项目的日志就不是统一的,有各种格式,为了解决这个问题,诞生了 commons-logging。

commons-longging

全程叫 jarkartCommins-logging ,简称 JCL。

代码

依赖:

  1. <dependency>
  2. <groupId>commons-logging</groupId>
  3. <artifactId>commons-logging</artifactId>
  4. <version>1.2</version>
  5. </dependency>

测试代码如下

  1. package com.example.springmaven.log;
  2. import org.apache.commons.logging.Log;
  3. import org.apache.commons.logging.LogFactory;
  4. import org.junit.jupiter.api.Test;
  5. public class TestJCL {
  6. @Test
  7. public void jcl() {
  8. Log log = LogFactory.getLog("a");
  9. log.info("jjjjjjj");
  10. }
  11. }

image.png

为什么是log4j去打印的呢?

jcl 并不是一个具体的日志打印技术,他是一个抽象类,可以看下他的 getLog() 方法。

  1. public static Log getLog(String name) throws LogConfigurationException {
  2. return getFactory().getInstance(name);
  3. }
  1. public Log getInstance(String name) throws LogConfigurationException {
  2. Log instance = (Log) instances.get(name);
  3. if (instance == null) {
  4. instance = newInstance(name);
  5. instances.put(name, instance);
  6. }
  7. return instance;
  8. }

主要看下 newInstance(name)方法

  1. protected Log newInstance(String name) throws LogConfigurationException {
  2. Log instance;
  3. try {
  4. if (logConstructor == null) {
  5. instance = discoverLogImplementation(name);
  6. }
  7. else {
  8. Object params[] = { name };
  9. instance = (Log) logConstructor.newInstance(params);
  10. }
  11. if (logMethod != null) {
  12. Object params[] = { this };
  13. logMethod.invoke(instance, params);
  14. }
  15. return instance;
  16. } catch (LogConfigurationException lce) {
  17. // this type of exception means there was a problem in discovery
  18. // and we've already output diagnostics about the issue, etc.;
  19. // just pass it on
  20. throw lce;
  21. } catch (InvocationTargetException e) {
  22. // A problem occurred invoking the Constructor or Method
  23. // previously discovered
  24. Throwable c = e.getTargetException();
  25. throw new LogConfigurationException(c == null ? e : c);
  26. } catch (Throwable t) {
  27. handleThrowable(t); // may re-throw t
  28. // A problem occurred invoking the Constructor or Method
  29. // previously discovered
  30. throw new LogConfigurationException(t);
  31. }
  32. }

主要看下 discoverLogImplementation(name) 方法里,里面有个 _classesToDiscover_ 对象

  1. private static final String[] classesToDiscover = {
  2. LOGGING_IMPL_LOG4J_LOGGER,
  3. "org.apache.commons.logging.impl.Jdk14Logger",
  4. "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
  5. "org.apache.commons.logging.impl.SimpleLog"
  6. };

_classesToDiscover_ 会依次遍历这四个类,调用 createLogFromClass方法

  1. for(int i=0; i<classesToDiscover.length && result == null; ++i) {
  2. result = createLogFromClass(classesToDiscover[i], logCategory, true);
  3. }

createLogFromClass 方法中会用 Class._forName_(logAdapterClassName) 去加载,如果加载到了,会调用如下方法去实例化。

  1. constructor = c.getConstructor(logConstructorSignature);
  2. Object o = constructor.newInstance(params);

但这里实例化的时候其实还包了一层,log4j的类名 private static final String _LOGGING_IMPL_LOG4J_LOGGER _= "org.apache.commons.logging.impl.Log4JLogger"; 其实还是 commons.logging 包下面的,它实例化的时候是通过 getLogger() 方法去实例化 log4j 本体。

  1. public Logger getLogger() {
  2. Logger result = logger;
  3. if (result == null) {
  4. synchronized(this) {
  5. result = logger;
  6. if (result == null) {
  7. logger = result = Logger.getLogger(name);
  8. }
  9. }
  10. }
  11. return result;
  12. }

所以这里实际是log4j打印的日志。

流程图

2. Spring 日志 - 从 JUL 到 SLF4J - 图4

去掉 log4j

所以,如果把 log4j 的依赖去掉,就是 jul 去打印日志了
image.png

番外

Class._forName_() 的时候能看它也打印了日志,这时候其实日志打印都还没准备好,点进去看下它是用什么打印的
image.png
点到最后发现用的是 PrintStream,所以我们经常看到的日志相关的错误都是直接红字显示的。

  1. private static PrintStream diagnosticsStream = null;

缺点

  1. 还是存在硬编码。
  2. 之前假如有log4j打印的,还是不会变,必须 LogFactory.getLog("a") 这样写才能做到统一。

    Slf4j

    和 commons logging 不一样,slf4j 采用的是绑定器的思路,可以看下图

    流程图

    2. Spring 日志 - 从 JUL 到 SLF4J - 图7

    代码

    依赖如下
    1. <dependency>
    2. <groupId>org.slf4j</groupId>
    3. <artifactId>slf4j-api</artifactId>
    4. <version>1.7.32</version>
    5. </dependency>
    测试代码如下 ```java import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;

public class TestSlf4j { @Test public void testJCL() { Logger logger = LoggerFactory.getLogger(“a”); logger.info(“slf4j”); } }

  1. 这时候执行会报错,可以看到错误就是没有绑定器。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/764382/1640422336344-4b998703-a274-4735-989c-52d9262e93ae.png#clientId=u0a258d75-3ff5-4&from=paste&height=191&id=uf1c50d04&margin=%5Bobject%20Object%5D&name=image.png&originHeight=382&originWidth=2006&originalType=binary&ratio=1&size=102746&status=done&style=none&taskId=u7c70f2de-a207-42a4-87af-d36cf1304c5&width=1003)<br />这时候我们项目虽然有 slf4j 和 log4j的依赖,但我们并没有把他们绑定在一起,所以 slf4j 并不知道要用什么日志技术去打印。<br />从 slf4j 的官网可以看到有哪些官方的绑定器 [https://www.slf4j.org/manual.html](https://www.slf4j.org/manual.html)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/764382/1640422578430-9f5527ae-5153-4e8f-b5f9-03d4c07cf3ea.png#clientId=u0a258d75-3ff5-4&from=paste&height=542&id=u8e0cc576&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1084&originWidth=2146&originalType=binary&ratio=1&size=315269&status=done&style=none&taskId=u9f97900f-937e-480d-b880-be5d425339e&width=1073)<br />所以我们加入 slf4j 和 log4j 的绑定器
  2. ```java
  3. <dependency>
  4. <groupId>org.slf4j</groupId>
  5. <artifactId>slf4j-log4j12</artifactId>
  6. <version>1.7.32</version>
  7. </dependency>

再执行测试代码就正常了
image.png
这时候假如把 log4j 的绑定器换成 jul 的,log4j 本身的依赖还是不动

  1. <!-- <dependency>-->
  2. <!-- <groupId>org.slf4j</groupId>-->
  3. <!-- <artifactId>slf4j-log4j12</artifactId>-->
  4. <!-- <version>1.7.32</version>-->
  5. <!-- </dependency>-->
  6. <dependency>
  7. <groupId>org.slf4j</groupId>
  8. <artifactId>slf4j-jdk14</artifactId>
  9. <version>1.7.32</version>
  10. </dependency>

打印日志就变成 jul 打印了
image.png

缺点

绑定器还是没有解决之前代码用别的日志框架打印的日志。
但slf4j其实也有解决方式,就是 slf4j 的桥接器。

桥接器

现在依赖如下

  1. <dependency>
  2. <groupId>log4j</groupId>
  3. <artifactId>log4j</artifactId>
  4. <version>1.2.17</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>commons-logging</groupId>
  8. <artifactId>commons-logging</artifactId>
  9. <version>1.2</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.slf4j</groupId>
  13. <artifactId>slf4j-api</artifactId>
  14. <version>1.7.32</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>ch.qos.logback</groupId>
  18. <artifactId>logback-classic</artifactId>
  19. <version>1.2.4</version>
  20. </dependency>

logback-classic 包含了 logback 本身和绑定器。也就是说现在我们用 Slf4j 打印的应该是 logback 打印的,但如果是 log4j 打印还是 log4j。如下:
image.png
image.png
这时候,我们想让 log4j 打印出来的还是用 slf4j 输出,那就要用到桥接器。
依赖如下:

  1. <!-- https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j -->
  2. <dependency>
  3. <groupId>org.slf4j</groupId>
  4. <artifactId>log4j-over-slf4j</artifactId>
  5. <version>1.7.32</version>
  6. </dependency>

去掉一些冲突依赖,最终依赖如下

  1. <dependency>
  2. <groupId>ch.qos.logback</groupId>
  3. <artifactId>logback-classic</artifactId>
  4. <version>1.2.4</version>
  5. </dependency>
  6. <!-- https://mvnrepository.com/artifact/org.slf4j/log4j-over-slf4j -->
  7. <dependency>
  8. <groupId>org.slf4j</groupId>
  9. <artifactId>log4j-over-slf4j</artifactId>
  10. <version>1.7.32</version>
  11. </dependency>

可以看到如下打印,虽然是 log4j 实例化的对象打印的,但还是用 logback 打印的。
image.png