快速熟悉混乱的日志

414 阅读6分钟

日志的前世今生

Log4j

在JDK 1.3及以前,Java打日志依赖System.out.println(), System.err.println()或者e.printStackTrace(),Debug日志被写到STDOUT流,错误日志被写到STDERR流。这样打日志有一个非常大的缺陷,即无法定制化,且日志粒度不够细。 于是, Gülcü 于2001年发布了Log4j,后来成为Apache 基金会的顶级项目。Log4j 在设计上非常优秀,对后续的 Java Log 框架有长久而深远的影响。

J.U.L

受log4j启发,Sun在Java1.4版本中引入了java.util.logging,但是J.U.L功能远不如log4j完善,开发者需要自己编写Appenders(Sun称之为Handlers),且只有两个Handlers可用(Console和File),J.U.L在Java1.5以后性能和可用性才有所提升, 但是log4j早已深入人心。

由于JDK 1.3及以前没有日志,所以你的代码里面用的都是log4j,然后你的老板说还是用原生的比较好,所以你就需要把之前的日志代码重写一遍。接口完全不同,那有没有办法,将这些api抽象出接口,这样以后调用的时候,就调用这些接口就好了呢?这个时候JCL(Jakarta Commons Logging)出现了。JCL 只提供 log接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。

LogBack

于是log4j的作者觉得jcl不好用,自己又写了一个新的接口api,那么就是slf4j。由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,下图红框中的组件即是对应的各种桥接器!

Log4j 在Apache开源以后,Gülcü(离开apache) 耐不住寂寞,又写了一个logback,logback 继承自 log4j,它建立在有十年工业经验的日志系统之上。它比其它所有的日志系统更快并且更小,包含了许多独特并且有用的特性。

由于logbak和slf4j这两个是同一个作者,所以LogBack直接实现了slf4j的api,不需要走中间的适配层,如下图所示

Log4j2

现在有了更好的 SLF4J 和 Logback,慢慢取代JCL 和 Log4j ,事情到这里总该大统一圆满结束了吧。然而维护 Log4j 的人不这样想,他们不想坐视用户一点点被 SLF4J / Logback 蚕食,继而搞出了 Log4j2。 Log4j2 和 Log4j1.x 并不兼容,设计上很大程度上模仿了 SLF4J/Logback。Log4j2 也做了 Facade/Implementation 分离的设计,分成了 log4j-api 和 log4j-core。

在代码中,并不会出现具体日志框架的api。程序根据classpath中的桥接器类型,和日志框架类型,判断出logger.info应该以什么框架输出!注意了,如果classpath中不小心引了两个桥接器,那会直接报错的!

发展过程总结

日志框架

  • Log4j
  • J.U.L
  • LogBack
  • Log4j2

日志门面

  • SLF4J
  • JCL
    • 如果能找到Log4j 则默认使用log4j 实现。
    • 如果没有则使用jul(jdk自带的) 实现,
    • 再没有则使用jcl内部提供的SimpleLog 实现。

趋势

  • 由于Logback这个工具和logback出自同一人, 直接实现了SLF4J的API,所以连适配层都不需要了, 用起来速度飞快,效率最高,SLFJ4+Logback 成为了很多人的最爱(Commons Logging因为效率和API设计等问题,现在逐渐淡出舞台了。)

强制

应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架 SLF4J 中的 API 。使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一。

LogBack

模块

  • logback-core:为其他两个模块奠定了基础。
  • logback-classic:可以与log4j的显着改进版本同化(改良版本)。此外,经典的logback原生实现了SLF4J API,因此您可以轻松地在logback和其他日志框架(例如log4j或java.util.logging(JUL))之间来回切换。
  • logback-access:​与Servlet容器(例如Tomcat和Jetty)集成,以提供HTTP访问日志功能。请注意,您可以轻松地在logback-core之上构建自己的模块。

比Log4j的优点

  • 更快的实施 根据我们之前在log4j上所做的工作,已重写了logback内部,使其在某些关键执行路径上的执行速度提高了大约十倍。Logback组件不仅速度更快,而且内存占用空间也较小。
  • 大量的测试 Logback附带了一系列非常广泛的测试,这些测试是在几年的历时和数不尽的工作时间内开发出来的。同时还测试了log4j时,logback使测试达到了完全不同的水平。我们认为,这是偏爱log4j而不是log4j的唯一最重要的原因。您希望您的日志记录框架即使在不利条件下也能坚如磐石且可靠。
  • ...更多参考:Logback比log4j的优点

应用

应用一(logback)

  • 添加依赖
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
  • 代码实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class M1Application {

    public static void main(String[] args) {
        //ch.qos.logback.classic.Logger.class
        Logger logger = LoggerFactory.getLogger(M1Application.class);
        logger.info("hello,{}","chunlai");
    }

}

在logger.info打断点,进入方法,可以看到调用的是ch.qos.logback.classic.Logger,下面实例中可以用同样的方法查看

应用二(log4j2)

  • 添加依赖(适配器)
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.13.2</version>
    </dependency>
  • 代码实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class M1Application {

    public static void main(String[] args) {
        ////org.apache.logging.slf4j.Log4jLogger.class
        Logger logger = LoggerFactory.getLogger(M1Application.class);
        logger.info("hello,{}","chunlai");
    }

}

应用三(logback + log4j2)

  • 添加依赖
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.13.2</version>
    </dependency>
  • 代码实现

现在环境中有两个适配器了,上面说过只能有一个适配器,所以下面代码会报错

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class M1Application {

    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(M1Application.class);
        logger.info("hello,{}","chunlai");
    }
}
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/a565354062/environment/maven_repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/a565354062/environment/maven_repository/org/apache/logging/log4j/log4j-slf4j-impl/2.13.2/log4j-slf4j-impl-2.13.2.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

应用四(springboot中如何切换日志)

  • 添加依赖
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!-- 去掉springboot默认配置 -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 引入log4j2依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
  • 代码实现
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public void testLog() {
    Logger logger = LoggerFactory.getLogger(getClass());
    logger.info("hello chunlai");
}

springboot2.2.6.RELEASE 默认的是日志是logback,按照上面的依赖,代码不用改变,只需要改动依赖就可以了。

参考文章