转载

日志?聊一聊slf4j吧

作为一个Java程序员,肯定对于日志记录不会陌生,无论项目大小,日志记录都是必须的;因为好的日志可以很容易的帮助我们定位一些生产问题。

我怀念的是 无话不说 System.out.println("这里是重要的日志");

我怀念的是 一起作梦 System.err.println("这里是错误的日志");

对于日常开发来说,其实System.out.println挺好用的,但是为什么在实际的开发应用中不使用这个来输出日志呢?

System.out.println()除了使用方便一点之外,其他感觉真的没有什么多大的用处。方便在哪儿呢?在Eclipse中你只需要输入syso【IDEA中输出sout即可】,然后按下代码提示键,这个方法就会自动出来了,相信这也是很多Java新手对它钟情的原因,因为我一开始也是这么干的,直到...【此处省略泪水】。那缺点又在哪儿了呢?这个就太多了,比如日志打印不可控制、打印时间无法确定、不能添加过滤器、日志没有级别区分……

记得我最开始接触的是log4j,log4j作为Apache的一个开放源代码的项目,通过使用log4j,我们可以控制日志信息输送的目的地是控制台、文件等我们期望它输出到的地方;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。重要的是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

确实,log4j作为最先比较流行的日志框架,给我们在应用开发和维护带来了很大的便捷,那么为什么这样优秀的框架最后还是慢慢的走下“神坛”呢?而逐渐被logback代替呢?下面是摘自网上一位大佬对于这个问题的解释:

无论从设计上还是实现上,Logback相对log4j而言有了相对多的改进。不过尽管难以一一细数,这里还是列举部分理由为什么选择logback而不是log4j。牢记logback与log4j在概念上面是很相似的,它们都是有同一群开发者建立。

  • 更快的执行速度

    基于我们先前在log4j上的工作,logback 重写了内部的实现,在某些特定的场景上面,甚至可以比之前的速度快上10倍。在保证logback的组件更加快速的同时,同时所需的内存更加少。

  • 充分的测试

    Logback 历经了几年,数不清小时数的测试。尽管log4j也是测试过的,但是Logback的测试更加充分,跟log4j不在同一个级别。我们认为,这正是人们选择Logback而不是log4j的最重要的原因。人们都希望即使在恶劣的条件下,你的日记框架依然稳定而可靠。

slf4j log4j logback

slf4j:The Simple Logging Facade for Java 即java的简单日志门面

简答的讲就是slf4j是一系列的日志接口,slf4j作为一个日志的抽象行为存在,但是并没有提供真正的实现。

slf4j为各种日志框架提供了一个统一的界面,使用户可以用统一的接口记录日志,但是动态地决定真正的实现框架。logback,log4j,common-logging等框架都实现了这些接口。

slf4j 源码分析

想了很久都不知道从哪里开头写比较合适,slf4j包中总共29个类【1.7.21版本】,不可能一一列举。所以就从我们熟知的这个语句来说。

private static final Logger logger =
LoggerFactory.getLogger(DemoTest.class);

上面这段代码其实就是我们平时在代码里面申明一个日志对象的常用方式。

先来看下Logger接口提供的方法;

package org.slf4j;

public interface Logger {
    //根Logger
    String ROOT_LOGGER_NAME = "ROOT";
    String getName();
    //判断记录器Trace跟踪是否激活;Trace跟踪激活后一般会打印比较详细的信息。
    boolean isTraceEnabled();
    //trace级别
    void trace(String var1);
    void trace(String var1, Object var2);
    void trace(String var1, Object var2, Object var3);
    void trace(String var1, Object... var2);
    void trace(String var1, Throwable var2);
    boolean isTraceEnabled(Marker var1);
    void trace(Marker var1, String var2);
    void trace(Marker var1, String var2, Object var3);
    void trace(Marker var1, String var2, Object var3, Object var4);
    void trace(Marker var1, String var2, Object... var3);
    void trace(Marker var1, String var2, Throwable var3);
    //进行预先判断,提升系统性能
    boolean isDebugEnabled();
    void debug(String var1);
    void debug(String var1, Object var2);
    void debug(String var1, Object var2, Object var3);
    void debug(String var1, Object... var2);
    void debug(String var1, Throwable var2);
    boolean isDebugEnabled(Marker var1);
    void debug(Marker var1, String var2);
    void debug(Marker var1, String var2, Object var3);
    void debug(Marker var1, String var2, Object var3, Object var4);
    void debug(Marker var1, String var2, Object... var3);
    void debug(Marker var1, String var2, Throwable var3);
    //info级别
    boolean isInfoEnabled();
    void info(String var1);
    void info(String var1, Object var2);
    void info(String var1, Object var2, Object var3);
    void info(String var1, Object... var2);
    void info(String var1, Throwable var2);
    boolean isInfoEnabled(Marker var1);
    void info(Marker var1, String var2);
    void info(Marker var1, String var2, Object var3);
    void info(Marker var1, String var2, Object var3, Object var4);
    void info(Marker var1, String var2, Object... var3);
    void info(Marker var1, String var2, Throwable var3);
    //warn级别
    boolean isWarnEnabled();
    void warn(String var1);
    void warn(String var1, Object var2);
    void warn(String var1, Object... var2);
    void warn(String var1, Object var2, Object var3);
    void warn(String var1, Throwable var2);
    boolean isWarnEnabled(Marker var1);
    void warn(Marker var1, String var2);
    void warn(Marker var1, String var2, Object var3);
    void warn(Marker var1, String var2, Object var3, Object var4);
    void warn(Marker var1, String var2, Object... var3);
    void warn(Marker var1, String var2, Throwable var3);
    //error级别
    boolean isErrorEnabled();
    void error(String var1);
    void error(String var1, Object var2);
    void error(String var1, Object var2, Object var3);
    void error(String var1, Object... var2);
    void error(String var1, Throwable var2);
    boolean isErrorEnabled(Marker var1);
    void error(Marker var1, String var2);
    void error(Marker var1, String var2, Object var3);
    void error(Marker var1, String var2, Object var3, Object var4);
    void error(Marker var1, String var2, Object... var3);
    void error(Marker var1, String var2, Throwable var3);
}

isXXXXEnabled

以isDebugEnabled来说明下这个isXXXXEnabled方法的作用。

logger.debug("the debug msg is " +doMethod());

假设我们的日志级别设置为info,那这句话不会输出日志,但这个方法还是会调用(预判断作用)。要调用这个方法,必须提供参数。doMethod()方法返回的结果就是参数的一部分。doMethod()要执行n秒钟,n秒钟后,进入到debug()方法里;

但是日志级别为info。结果是日志虽然没有输出,却花费了n秒钟来构造参数。很显然这里得不偿失的。尽管实际应用中几乎不可能有这种花n秒钟来构造这样一个参数的情况,但如果并发数大的话,这样写还是会影响系统的性能的。这个时候,就应该写成:

if(logger.isDebugEnabled()){
    logger.debug("the debug msg is " + doMethod());
}

接下来说LoggerFactory这个类;先从getLogger这个方法为入口来看下:

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

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 http://www.slf4j.org/codes.html#loggerNameMismatch for an explanation");
        }
    }

    return logger;
}

getLogger方法提供了两种重载方式,一种是传入一个name,用于标注当前日志的名字。另外一个是提供一个Class对象,其实里面也是通过clazz.getName()来作为日志的名称。

从上面的代码中可以比较明显的看到ILoggerFactory这个接口。

package org.slf4j;

public interface ILoggerFactory {
    Logger getLogger(String var1);
}

ILoggerFactory这个接口实际上就是为不同接入的日志实现提供了统一的顶层类型;每个日志框架都需要实现ILoggerFactory接口,来说明自己是怎么提供Logger的。像log4j、logback能够提供父子层级关系的Logger,就是在ILoggerFactory的实现类里实现的。同时,它们也需要实现Logger接口,以完成记录日志。

logback中的实现

public class LoggerContext extends ContextBase implements
ILoggerFactory, LifeCycle

上面提到过,对于不同的日志框架的实现都实现了ILoggerFactory接口。

@Override
public final Logger getLogger(final String name) {

    if (name == null) {
        throw new IllegalArgumentException("name argument cannot be null");
    }

    // if we are asking for the root logger, then let us return it without
    // wasting time
    if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
        return root;
    }

    int i = 0;
    Logger logger = root;

    // check if the desired logger exists, if it does, return it
    // without further ado.
    Logger childLogger = (Logger) loggerCache.get(name);
    // if we have the child, then let us return it without wasting time
    if (childLogger != null) {
        return childLogger;
    }

    // if the desired logger does not exist, them create all the loggers
    // in between as well (if they don't already exist)
    String childName;
    while (true) {
        int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
        if (h == -1) {
            childName = name;
        } else {
            childName = name.substring(0, h);
        }
        // move i left of the last point
        i = h + 1;
        synchronized (logger) {
            childLogger = logger.getChildByName(childName);
            if (childLogger == null) {
                childLogger = logger.createChildByName(childName);
                loggerCache.put(childName, childLogger);
                incSize();
            }
        }
        logger = childLogger;
        if (h == -1) {
            return childLogger;
        }
    }
}

关于logback的源码可以参考这个系列的文章:logback源码系列文章

遇到过的坑

我自己在日志这块遇到的最多的坑就是日志jar的依赖冲突;因为项目用到的很多框架或者三方依赖中可能自己都集成了日志框架,有的时候我们自己的项目中也会有自己的一套日志方案,所以就很大程度上导致了这种依赖冲突的发生。幸运的是,项目中关于日志包的依赖冲突解决也是很容易排除的【idea和eclipse都很方便排查】

另外一个就是使用slf4j之后,同时引入了不同的框架实现;比如同时引入了log4j和logback。

从上面getLogger的方法中可以看到,在获取Logger的时候都需要去先取得ILoggerFactory:

ILoggerFactory iLoggerFactory = getILoggerFactory();

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == 0) {
        Class var0 = LoggerFactory.class;
        synchronized(LoggerFactory.class) {
            if (INITIALIZATION_STATE == 0) {
                INITIALIZATION_STATE = 1;
                //初始化
                performInitialization();
            }
        }
    }

    switch(INITIALIZATION_STATE) {
    case 1:
        return SUBST_FACTORY;
    case 2:
        throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
    case 3:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case 4:
        return NOP_FALLBACK_FACTORY;
    default:
        throw new IllegalStateException("Unreachable code");
    }
}

在getILoggerFactory中会通过performInitialization方法来做初始化判断;而在performInitialization中又调用了bind方法,bind方法中的异常或者错误信息打印很多情况下我们都会遇到:

private static final void bind() {
    String msg;
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        if (!isAndroid()) {
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }

        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = 3;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstituteLoggers();
        replayEvents();
        SUBST_FACTORY.clear();
    } catch (NoClassDefFoundError var2) {
        msg = var2.getMessage();
        if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
            failedBinding(var2);
            throw var2;
        }

        INITIALIZATION_STATE = 4;
        Util.report("Failed to load class /"org.slf4j.impl.StaticLoggerBinder/".");
        Util.report("Defaulting to no-operation (NOP) logger implementation");
        Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
    } catch (NoSuchMethodError var3) {
        msg = var3.getMessage();
        if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
            INITIALIZATION_STATE = 2;
            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 var3;
    } catch (Exception var4) {
        failedBinding(var4);
        throw new IllegalStateException("Unexpected initialization failure", var4);
    }

}

reportMultipleBindingAmbiguity方法用来提示多重绑定,并通过日志告诉你具体是有那几个绑定了。

Class path contains multiple SLF4J bindings.

reportActualBinding这个是绑定成功的时候,会通过日志显示具体绑定成功的是哪一个日志框架

Actual binding is of type [XXXX]

关于StaticLoggerBinder

从下面这个异常说起:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for
further details.

这个问题是因为加载类文件org.slf4j.impl.StaticLoggerBinder时失败;为什么呢?下面是官网对于这个原因的解释:

This error is reported when the org.slf4j.impl.StaticLoggerBinder class could not be loaded into memory. This happens when no appropriate SLF4J binding could be found on the class path. Placing one (and only one) of slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem.

这个错误发生的原因是StaticLoggerBinder类不能被加载到内存中。发生这种情况时,无法找到合适的SLF4J绑定类路径。slf4j-nop放置一个(且只有一个)。slf4j-simple jar。slf4j-log4j12 jar。slf4j-jdk14 jar。jar或logback-classic。jar的类路径应该解决这个问题。

也就是说没有找到具体的日志框架来绑定实现。所以我们需要引入一个具体的日志实现jar就可以了。

网上看到的一片关于slf4j的文章,感觉挺好的,分享给大家:slf4j源码剖析

新的环境还在适应中,博客的更新频次也直线下降,慢慢学,慢慢写吧。也希望小伙伴们多多见谅!

原文  https://juejin.im/post/5ad1ccc86fb9a028c14ae528
正文到此结束
Loading...