Java日志实现知多少?

Java日志实现知多少?_第1张图片
骨灰级程序员

1.Java日志历史

Java 拥有功能和性能都非常强大的日志库,但不幸的是,这样的日志库有不止一个——相信每个Java程序员都曾经迷失在JUL(Java Util Log), JCL(Commons Logging), Log4j, SLF4J, Logback,Log4j2 等等的迷宫中。让我们回顾下讲讲这段“腥风血雨”的历史。

  • Java Util Log

    来自官方JDK,带着标准和权威的光环,小名:JUL。从JDK1.4 才开始加入(2002年),当时各种第三方日志组件及其盛行,且JUL性能和使用的确又没有其他组件方便。虽然JDK1.5对其进行了改进,但还是不影响很多项目不选用该组件的命运。

  • Log4j 1.x
    在JUL推出的前一年,Gülcü 发布了Log4j 1.x,虽然进不了JDK,但是Log4j 1.x进入了Apache 基金会顶级项目。Log4j 在设计上非常优秀,对后续的 Java Log 框架有长久而深远的影响,也产生了Log4c, Log4s, Log4perl 等到其他语言的移植。但是,后浪推前浪,前浪的性能终究还是不断被后期的日志框架赶超,比如后期的LogbackLog4j2

  • JCL
    那么问题来了,JDK官方自带JUL,第三方有Log4J,不同项目或者开源Jar,采用了不同的日志实现库,那么是不是意味着要整合使用,得写多个配置文件呢。正式这个问题,带来了JCL的出现。JCL,大名:Commons Logging,同样也是Apache下的项目,但是JCL 是一个Log Facade(门面Api),只提供 Log API,不提供实现。在程序中日志创建和记录都是用JCL中的接口,在真正运行时,然后有适配器Adapter 来使用 Log4j 或者 JUL 作为Log 实现。(当前ClassPath中有什么实现,如果有Log4j 就是用 Log4j, 如果啥都没有就是用 JDK 的 JUL)。是不是感觉从面向对象的高度,JCL有种很先进的感觉。这就是面向接口编程的体现。

    这样,在你的项目中,如果用Log4j, 就添加 Log4j 的jar包进去,然后写一个 Log4j 的配置文件;如果喜欢用JUL,就只需要写个 JUL 的配置文件。如果有其他的新的日志库出现,也只需要它提供一个Adapter,运行的时候把这个日志库的 jar 包加进去。


    Java日志实现知多少?_第2张图片
    JCL

合久必分,分久必合。历史就是这样,日志组件在接下去的历史演进中,又出现了跌宕起伏。一种平衡替换另一种平衡。

  • SLF4J/Logback
    Gülcü (对头,又是ta)认为 JCL 的 API 设计得不好,容易让使用者写出性能有问题的代码,Gülcü ,不安于现状,不基于JCL添加实现类,而是创立了SLF4J 和 Logback项目,目的就是为了提高日志组件的性能。SLF4J的全称:Simple Logging Facade for Java,看其意思就是门面API,而Logback作为其实现类。当然 Logback 则是作为 Log4j 的继承者来开发的,提供了性能更好的实现,异步 logger,Filter等更多的特性。现在事情变复杂了。我们有了两个流行的 Log Facade,以及三个流行的 Log Implementation。


    Java日志实现知多少?_第3张图片
    jcl+slf4j

当你感觉现在差不多了吧的时候,三国时期的故事,其实又开始上演了。

  • Log4j2
    维护 Log4j 的人似乎坐立不安,他们不想坐视用户一点点被 SLF4J /Logback 蚕食,继而搞出了 Log4j2。
    Log4j2 和 Log4j1.x 并不兼容,设计上很大程度上模仿了 SLF4J/Logback,性能上也获得了很大的提升。
    Log4j2 也做了 Facade/Implementation 分离的设计,分成了 log4j-api 和 log4j-core。


    Java日志实现知多少?_第4张图片
    jcl+slf4j+log4f2

=========================分割线========================

Gülcü 是个追求完美的人,各种纷纷扰扰的历史看在了ta眼里,他决定让这些Log之间都能够方便的互相替换,所以做了各种 Adapter 和 Bridge 来连接:


Java日志实现知多少?_第5张图片
adpater
Java日志实现知多少?_第6张图片
adpater-jar

到这里,日志演进总算有所停歇。

2.Spring Boot 日志使用

2.1. 依赖分析

历史回顾不是我们的目的,结合现在流行的开源框架Spring Boot,我们再来谈谈具体项目该如何结合实际,使用日志。
构建Spring Boot Web项目,版本:2.1.4。分析下Pom.xml的依赖:


Java日志实现知多少?_第7张图片
logback maven

可以发现,Spring Boot采用了SLF4J+Logback的组合来完成日志的记录。并且作者把Log4j和JUL的日志组件适配到了slf4j。的确,Spring Boot为Java coder做了太多的工作。

2.2. 日志初始化过程

以上面构建的Spring Boot项目为例,添加简单日志记录代码:

@SpringBootApplication
public class SpringBootLoggerDemoApplication {

    private static Logger logger = LoggerFactory.getLogger(SpringBootLoggerDemoApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLoggerDemoApplication.class, args);
        logger.debug("hello logger");
    }

}

LoggerFactory.java

    /**
     * Return a logger named according to the name parameter using the
     * statically bound {@link ILoggerFactory} instance.
     * 
     * @param name
     *            The name of the logger.
     * @return logger
     */
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

通过getILoggerFactory获取日志工厂:

    /**
     * Return the {@link ILoggerFactory} instance in use.
     * 

*

* ILoggerFactory instance is bound with this class at compile time. * * @return the ILoggerFactory instance in use */ public static ILoggerFactory getILoggerFactory() { if (INITIALIZATION_STATE == UNINITIALIZED) { synchronized (LoggerFactory.class) { if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } } } ...省略... }

这里代码定位到performInitialization:

    private final static void performInitialization() {
        bind();
        ...省略..
    }

bind()具体日志实现:

 private final static void bind() {
        try {
            Set staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            ...省略...
        } 
    }

关键代码就在findPossibleStaticLoggerBinderPathSet:

 private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

    static Set findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set staticLoggerBinderPathSet = new LinkedHashSet();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

代码最终到最后,其实是通过ClassLoader在ClassPath里面加载指定实现类org/slf4j/impl/StaticLoggerBinder.class来实现日志组件的加载,核心就在:

 //org/slf4j/impl/StaticLoggerBinder.class           

if (loggerFactoryClassLoader == null) {    
    paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
 } else {
    paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}

类路径下看看backLog代码:

Java日志实现知多少?_第8张图片
lackLog code

这里有个题外话,之前看过很多介绍Java SPI文章,老是把日志的加载机制归类为SPI,其实通过上面的介绍,现在可以结论,其实不是。
https://www.jianshu.com/p/46b42f7f593c

Java日志实现知多少?_第9张图片
高级开发必须理解的Java中SPI机制

3.日志切换方法及原理

行文到此,主题介绍似乎差不多了,但是好像还有个问题,要是我要在Spring Boot换其他日志组件怎么办呐。其实Spring Boot已经为我们考虑过这个问题了,为我们提供了一个自动配置的starter:spring-boot-starter-log4j2

Starter for using Log4j2 for logging. An alternative to spring-boot-starter-logging

修改方式也简单:

    
        
            org.springframework.boot
            spring-boot-starter-web
            
                
                    org.springframework.boot
                    spring-boot-starter-logging
                
            
        
        
            org.springframework.boot
            spring-boot-starter-log4j2
        
Java日志实现知多少?_第10张图片
log4j2 maven

依赖切换成了最后的log4j-api+log4j-core。

Java日志实现知多少?_第11张图片
log4j

参考:

https://zhuanlan.zhihu.com/p/24272450

https://www.slf4j.org

你可能感兴趣的:(Java日志实现知多少?)