2018-12-14 · Develop

Java 日志关系梳理

前面介绍了 log4jJCLlogbacklog4j2 的使用方式,本来这篇文章应该写如何使用 slf4j 的。但是发现也没啥好写的。那我们就来捋一捋这么多的日志框架之间的复杂关系吧。

性能

网络上有很多关于各个日志之间的性能比较,这里就不讲述了,这里主要考虑下面的字符串处理时的开销。

在使 JCL 时为了减少构建日志信息的开销,通常的做法是

if(log.isDebugEnabled()) {
    log.debug(String.format("name: %d, id: %d", name, id));
}

在Slf4j阵营,你只需这么做:

log.debug("name: {}, id: {}", name, id);

各日志间的凌乱关系

java-jar-dependency

通过上面这个图,我们可以很直观的看见其中的关系。有了上面的转换关系再加上转换公式,我们就可以完美的解决日志凌乱的现象。

log-formula

实战

拿到上面的两幅图后,我们来进行下实战,就明白了如何去规划日志。

一个 web 项目大多多少使用的 Spring 框架,那么如何将 Spring 的框架日志和我们的业务日志都是用 Log4j2 进行输出呢? 套用上面的公式我们知道

spring-log4j-slf4j

原本应该走的红线线路由于日志适配器的关系走了绿色路线jcl-over-slf4j截断了原来的路线,下面我们简略的分析下这个适配器是如何阶段原来的路线的。

log4j-over-slf4j

通过上面可以看出,jcl-over-slf4j 的包里面包含有 commons-logging 的包,这个我们可以去掉 commons-logging 的包,让走其的方法走 jcl-over-slf4j中相同的类和方法。我们通过 LogFactory.getLog 其实走的是 SLF4JLogFactory 。下面看一段 SLF4JLogFactory 的源码

public Log getInstance(Class clazz) throws LogConfigurationException {
    return this.getInstance(clazz.getName());
}

public Log getInstance(String name) throws LogConfigurationException {
    Log instance = (Log)this.loggerMap.get(name);
    if (instance != null) {
        return instance;
    } else {
        Logger slf4jLogger = LoggerFactory.getLogger(name);
        Object newInstance;
        if (slf4jLogger instanceof LocationAwareLogger) {
            newInstance = new SLF4JLocationAwareLog((LocationAwareLogger)slf4jLogger);
        } else {
            newInstance = new SLF4JLog(slf4jLogger);
        }

        Log oldInstance = (Log)this.loggerMap.putIfAbsent(name, newInstance);
        return (Log)(oldInstance == null ? newInstance : oldInstance);
    }
}

从上面的代码可以看出真正获取到的是 slf4jLogger ,这样也就明白了其是如何进行截断操作的了。

除了上面的方式外,还有如下的路线可以选择

jul-to-slf4j

通过删掉 Log4J 的依赖,JCL 就走了 J.U.L 这条路,然后通过 jul-to-slf4j 适配器将其截断。这个时候需要在代码中如下调用

SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();

这样 jul-to-slf4j 适配器才能正常工作,详情可以查询该适配器工作原理。


参考文档

slf4j、jcl、jul、log4j1、log4j2、logback大总结

架构师必备,带你弄清混乱的JAVA日志体系!