日志门面SLF4J介绍和使用

SLF4j概述

上篇文章写了日志门面介绍和JCL使用,作为一个曾经的主流日志门面技术,JCL终究因为设计时的产生问题所限,导致至今已没什么人使用,而一款优秀的日志门面技术SLF4j,吸取了JCL的教训与经验,出现在java开发者的视野中,开始被广泛关注和使用。

这里如果谈谈历史还蛮好玩的,涉及到一位Java日志领域知名人物Ceki Gülcü,log4j、slf4j、logback都是出自他之手,在Apache组织开发的log4j,后来离开了apache开发了logback,apache反而又推出了log4j2与之竞争,感兴趣的可以去看看Java日志系统历史从入门到崩溃 - 个人文章 - SegmentFault 思否

SLF4J,即简单日志门面(Simple Logging Facade for Java),也为Java日志访问提供了一套标准、规范的API框架,它支持了log4j和logback,它本身也提供了功能较简单的日志实现,但一般很少用到,它的设计思想和JCL较为不同,要么遵循slf4j规范开发,要么使用适配器来完成slf-api与具体日志实现框架的整合。

SLF4j使用

maven依赖

<!-- slf4j日志门面 --> 
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
<!-- slf4j内置的简单实现 --> 
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.30</version>
</dependency>

日志绑定

SLF4J使用指定的日志框架需要通过“日志绑定”,通过一些jar文件实现。

绑定具体的日志实现框架:
1. 绑定已经实现了sf4的日志框架直接添加对应依赖
2. 绑定没有实现sf4的日志框架先添加日志的适配器再添加实现类的依赖

logback、slf4j-simple遵循了slf4j-api的开发,只需要导入对应依赖。

slf4j-nop是关闭日志功能。

如果绑定了多个日志实现,使用第一个找到的日志实现。

日志门面SLF4J介绍和使用

例:绑定log4j

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.12</version>
</dependency>

桥接

slf4j的强大之处还在于它对遗留代码的支持,如果项目之前使用的是例如Log4j等日志框架实现,可以通过slf4j提供的“桥接器”将其转移至slf4j使用。甚至还可以桥接JCL。

日志门面SLF4J介绍和使用

例如:log4j代码通过桥接器转移至logback实现,首先移除log4j依赖,然后添加桥接器:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.12</version>
</dependency>

这里会出现一个问题比如log4j-over-slf4jslf4j-log4j12共存出现stack overflow,就是因为slf4jlog4j死循环

特性

核心类:

  • Logger:日志记录器,用于写日志
  • LoggerFactory:日志记录器的工厂类,用于获取日志记录器。

日志级别:

  • trace
  • debug
  • info
  • warn
  • error

比JCL还少一个fatal级别。

使用占位符输出日志信息:

public static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class);

@Test
public void testQuick() {
    String name = "rhett";
    LOGGER.info("名字:{}",name);
}

将系统异常信息输出:

Logger提供了带throwable参数的方法重载,可以便捷地输出错误信息:

public static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class);

@Test
public void testException(){
    try{
        int i = 1/0;
    }catch (Exception e){
        LOGGER.error("出现异常:",e);
    }
}

SLF4J原理

这个阶段推荐自己进行源码跟踪,我大概把方法栈贴一下

public static Logger getLogger(Class<?> clazz) {
    Logger logger = getLogger(clazz.getName());//!!!
    if (DETECT_LOGGER_NAME_MISMATCH) {
        Class<?> autoComputedCallingClass = Util.getCallingClass();
        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
            Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                            autoComputedCallingClass.getName()));
            Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
        }
    }
    return logger;
}
public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                performInitialization();//通往bind方法
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    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 http://jira.qos.ch/browse/SLF4J-97
        return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}
private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        // skip check under android, see also
        // http://jira.qos.ch/browse/SLF4J-328
        if (!isAndroid()) {
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();//!!!
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // the next line does the binding
        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
    } catch (NoClassDefFoundError ncde) {
        String msg = ncde.getMessage();
        if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
            INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
            Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
        } else {
            failedBinding(ncde);
            throw ncde;
        }
    } catch (java.lang.NoSuchMethodError nsme) {
        String msg = nsme.getMessage();
        if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
            INITIALIZATION_STATE = FAILED_INITIALIZATION;
            Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
            Util.report("Your binding is version 1.5.5 or earlier.");
            Util.report("Upgrade your binding to version 1.6.x.");
        }
        throw nsme;
    } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    } finally {
        postBindCleanUp();
    }
}
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;
}

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

注意: 虽然StaticLoggerBinder是在org.slf4j.impl包下,但日志门面jar包中并无此类,此类是由桥接器实现的。

桥接器的原理其实就是在其jar包中存在StaticLoggerBinder.class,这样就可在运行时动态绑定底层的日志实现框架。

然后回到bind方法:

StaticLoggerBinder.getSingleton();

获取了一个StaticLoggerBinder的单例对象

然后返回getILoggerFactory方法:

case SUCCESSFUL_INITIALIZATION:
    return StaticLoggerBinder.getSingleton().getLoggerFactory();

总结:桥接器会调用的日志框架实现的相关代码生成其内部的Logger(此Logger与org.slf4j.Logger)不兼容,再通过适配器包装日志框架实现内部的Logger.

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%97%a5%e5%bf%97%e9%97%a8%e9%9d%a2slf4j%e4%bb%8b%e7%bb%8d%e5%92%8c%e4%bd%bf%e7%94%a8/