log4j slf4j实现_slf4j日志框架简析

The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.

这是slf4j官方对自己的 定义。slf4j的全称是 simple logging facade for java,他是一种接口标准不是一个具体的实现,常用的jul,log4j和logback等都是它的一种具体实现。

最新的版本

    <latest.stable.version>1.7.26</latest.stable.version>

我们现在简单回顾一下外观模式是怎么样的。

c7655cef8ec69cdd4d17237a31065693.png
模式结构

6550d0a3168b9b33ea6ab950e706efaf.png
时序图

将一个系统划分成若干个子系统有利于降低整体系统的复杂度,一个常见的设计目标就是使各个子系统之间的通信和依赖降到最低,通过引入一个外观类,为各个子系统提供了统一而简单的入口。外观模式将子系统的内部复杂性与外部系统隔离开来,通过一个统一的外观对象进行通信,使客户端不需要关注子系统内部的工程细节。

优点:

  1. 对客户端屏蔽子系统,使客户端更简洁
  2. 客户端和子系统之间松耦合
  3. 客户端也可以直接访问子系统

缺点:

  1. 不能很好的限制客户端
  2. 如果没有抽象外观类,增加子系统需要修改外观类或者客户端的代码,违背开闭原则

外观类在一个拥有复杂子系统的工程中有比较好的应用场景,同时外观类不能有具体的业务行为。


首先来看看slf4j-api的代码结构。

1a19a53e67414325b4d84a63cf20be54.png

我们来看看常用的getLogger内部是怎么实现的。

    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

    public static ILoggerFactory getILoggerFactory() {
        return getProvider().getLoggerFactory();
    }

    static SLF4JServiceProvider getProvider() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return PROVIDER;
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also Obtaining a logger before loggerFactory is assigned
            return SUBST_PROVIDER;
        }
        throw new IllegalStateException("Unreachable code");
    }

getLoggerFactory()这个方法是定义在SLF4JServiceProvider接口中的,由各个日志系统各自实现。

再来看看平时常用的info()方法,在format中可以使用{}做占位符。

    public boolean isInfoEnabled();
    
    public void info(String msg);

    public void info(String format, Object arg);

    public void info(String format, Object arg1, Object arg2);

    public void info(String format, Object... arguments);

    /**
     * Log an exception (throwable) at the INFO level with an
     * accompanying message.
     *
     * @param msg the message accompanying the exception
     * @param t   the exception (throwable) to log
     */
    public void info(String msg, Throwable t);

    public boolean isInfoEnabled(Marker marker);

    /**
     * Log a message with the specific Marker at the INFO level.
     *
     * @param marker The marker specific to this log statement
     * @param msg    the message string to be logged
     */
    public void info(Marker marker, String msg);

    public void info(Marker marker, String format, Object arg);

    /**
     * This method is similar to {@link #info(String, Object, Object)}
     * method except that the marker data is also taken into
     * consideration.
     *
     * @param marker the marker data specific to this log statement
     * @param format the format string
     * @param arg1   the first argument
     * @param arg2   the second argument
     */
    public void info(Marker marker, String format, Object arg1, Object arg2);

    /**
     * This method is similar to {@link #info(String, Object...)}
     * method except that the marker data is also taken into
     * consideration.
     *
     * @param marker    the marker data specific to this log statement
     * @param format    the format string
     * @param arguments a list of 3 or more arguments
     */
    public void info(Marker marker, String format, Object... arguments);

    public void info(Marker marker, String msg, Throwable t);

最后说一下历史演进。

Java Util Log

简称JUL,是JDK 中自带的log功能。虽然是官方自带的log lib,JUL的使用确不广泛。主要原因:

  1. JUL从JDK1.4 才开始加入(2002年),当时各种第三方log lib已经被广泛使用了。
  2. JUL早期存在性能问题,到JDK1.5上才有了不错的进步,但现在和Logback/Log4j2相比还是有所不如。
  3. JUL的功能不如Logback/Log4j2等完善,比如Output Handler就没有Logback/Log4j2的丰富,有时候需要自己来继承定制,又比如默认没有从ClassPath里加载配置文件的功能。

Log4j 1.x

Log4j 是在 Logback 出现之前被广泛使用的 Log Lib, 由 Gülcü 于2001年发布,后来成为Apache 基金会的顶级项目。Log4j 在设计上非常优秀,对后续的 Java Log 框架有长久而深远的影响,也产生了Log4c, Log4s, Log4perl 等到其他语言的移植。Log4j 的短板在于性能,在Logback 和 Log4j2 出来之后,Log4j的使用也减少了。

Commons Logging

简称JCL,是Apache下面的项目。JCL 是一个Log Facade,只提供 Log API,不提供实现,然后有 Adapter 来使用 Log4j 或者 JUL 作为Log Implementation。

就像之前所说,JDK现在带了自己的JUL,然后又有第三方的 Log4j 等日志库存在,不同的项目可能各自使用了不同的日志库。如果你的项目依赖的其他 lib 各自使用了不同的日志库,你想控制日志行为,就需要针对每个日志库都写一个配置文件,然后这个时候 JCL 就出现了。在程序中日志创建和记录都是用JCL中的接口,在真正运行时,会看当前ClassPath中有什么实现,如果有Log4j 就是用 Log4j, 如果什么都没有就是用 JDK 的 JUL。

这样,在你的项目中,还有第三方的项目中,大家记录日志都使用 JCL 的接口,然后最终运行程序时,可以按照自己的需求(或者喜好)来选择使用合适的Log Implementation。如果用Log4j, 就添加 Log4j 的jar包进去,然后写一个 Log4j 的配置文件;如果喜欢用JUL,就只需要写个 JUL 的配置文件。如果有其他的新的日志库出现,也只需要它提供一个Adapter,运行的时候把这个日志库的 jar 包加进去。

3aafc6bdf388f98fd804d615e367c160.png

到这个时候一切看起来都很简单,很美好。接口和实现做了良好的分离,在统一的JCL之下,不改变任何代码,就可以通过配置就换用功能更强大,或者性能更好的日志库实现。

这种简单美好一直持续到SLF4J出现。

SLF4J/Logback

SLF4J 和 Logback 也是Gülcü 创立的项目,其创立主要是为了提供更高性能的实现。其中,SLF4j 是类似于JCL 的Log Facade,Logback 是类似于Log4j 的 Log Implementation。

之前已经说过,Apache 有了个JCL,用来做各种Log lib统一的接口,Gülcü 认为 JCL 的 API 设计得不好,容易让使用者写出性能有问题的代码。

比如在用 JCL 输出一个 debug 级别的 log:

logger.debug("start process request, url:" + url);

这个有什么问题呢?一般生产环境 log 级别都会设到 info 或者以上,那这条 log 是不会被输出的。然而不管会不会输出,这其中都会做一个字符串连接操作,然后生产一个新的字符串。如果这条语句在循环或者被调用很多次的函数中,就会多做很多无用的字符串连接,影响性能。

所以 JCL 的最佳实践推荐这么写:

if (logger.isDebugEnabled()) {
    logger.debug("start process request, url:" + url);
}

然而开发者常常忽略这个问题或是觉得麻烦而不愿意这么写。所以SLF4J提供了新的API,方便开发者使用:

logger.debug("start process request, url:{}", url);

这样的话,在不输出 log 的时候避免了字符串拼接的开销;在输出的时候需要做一个字符串format,代价比手工拼接字符串大一些,但是可以接受。

而 Logback 则是作为 Log4j 的继承者来开发的,提供了性能更好的实现,异步 logger,Filter等更多的特性。

现在事情变复杂了。我们有了两个流行的 Log Facade,以及三个流行的 Log Implementation。Gülcü 是个追求完美的人,他决定让这些Log之间都能够方便的互相替换,所以做了各种 Adapter 和 Bridge 来连接:

6df05854caf7c83059aff2e92b50868f.png

可以看到甚至 Log4j 和 JUL 都可以桥接到SLF4J,再通过 SLF4J 适配到到 Logback!

在这里需要注意不能搞出循环的桥接,比如下面这些依赖就不能同时存在:

  1. jcl-over-slf4j 和 slf4j-jcl
  2. log4j-over-slf4j 和 slf4j-log4j12
  3. jul-to-slf4j 和 slf4j-jdk14

Log4j2

Log4j 的不想坐视用户一点点被 SLF4J /Logback 蚕食,继而搞出了 Log4j2。

Log4j2 和 Log4j1.x 并不兼容,设计上很大程度上模仿了 SLF4J/Logback,性能上也获得了很大的提升。

Log4j2 也做了 Facade/Implementation 分离的设计,分成了 log4j-api 和 log4j-core。

现在好了,我们有了三个流行的Log 接口和四个流行的Log实现。

ac0291a5dd6681cba0f2683056de9045.png
各种日志facade,实现和桥接的关系

参考文献:

https://github.com/qos-ch/slf4j​github.com 4. 外观模式 - Graphic Design Patterns​design-patterns.readthedocs.io
236f2520906c22765af699e95c08aef4.png
hsiafan:Java 日志框架解析(上) - 历史演进​zhuanlan.zhihu.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值