Java日志实现知多少?

骨灰级程序员

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 包加进去。


    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。


    jcl+slf4j

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

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


    jcl+slf4j+log4f2

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351