springboot

Java日志实现知多少?

2019-05-12  本文已影响62人  BeautifulHao
骨灰级程序员

1.Java日志历史

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

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

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

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

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


adpater adpater-jar

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

2.Spring Boot 日志使用

2.1. 依赖分析

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


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.
     * <p/>
     * <p/>
     * 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<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            ...省略...
        } 
    }

关键代码就在findPossibleStaticLoggerBinderPathSet:

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

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> 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代码:

lackLog code

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

高级开发必须理解的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

修改方式也简单:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
log4j2 maven

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

log4j

参考:

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

https://www.slf4j.org

上一篇下一篇

猜你喜欢

热点阅读