【Java】slf4j 日志

简介

slf4j(Simple Logging Facade for Java)是简单日志门面框架,主要提供了日志接口,不提供实现。使用了Facade设计模式。

与common-logging对比

之前,slf4j的功能由common-logging来完成,二者都是日志框架的抽象层,有什么区别呢?

1.slf4j的日志接口更高效,提供了占位符式的打印日志接口,避免了字符串的拼接代价。同时是否打印日志的逻辑在接口内部实现,效率更高。

2.更重要的,slf4j可以避免common-logging动态查找算法的bug。slf4j是在编译期决定日志实现类的,而common-logging是在运行时通过classloader决定实现类,后者存在一定bug(具体可以google“common-logging classloader issue”)。

总之,能用slf4j就用slf4j。

架构

我们的应用程序直接依赖slf4j的接口,也就是slf4j-api包。这个日志接口层可以由不同的日志包来实现,比如log4j、logback等等。每一种实现要想与接口对接,就要提供适配器,这样就产生了上图中slf4j-xxx.jar包。

使用

考虑maven的场景。理论上,我们需要三个jar包:slf4j-api.jar、具体实现jar和适配jar。但是我们其实只需要引入slf4j-xxx.jar即可,这个jar包会包含另外两个jar包。

例子:构建一个maven项目,其关于日志的依赖只有:

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.25</version>
    </dependency>

查看最终的dependency:

可以看到所需的三种jar都被导入。

        String name = "liyao";
        Logger logger = LoggerFactory.getLogger(App.class);
        logger.info("name: {}", name);

使用时,占位符式的api十分方便。

实现细节

1.如何定位实现:

这是getLogger()方法:

    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();
                }
            }
        }
        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 performInitialization() {
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }

最终是在bind()方法处完成的绑定。

    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);
            fixSubstituteLoggers();
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } 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);
        }
    }

上面代码很长,其实只有一句“StaticLoggerBinder.getSingleton”完成了加载。看下StaticLoggerBinder的import。

import org.slf4j.impl.StaticLoggerBinder;

也就是这里会在类路径下寻找这个StaticLoggerBinder类。

接着点进去看下这个类的实现:

public class StaticLoggerBinder implements LoggerFactoryBinder {
    private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
    public static String REQUESTED_API_VERSION = "1.6.99";
    private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();
    private final ILoggerFactory loggerFactory = new Log4jLoggerFactory();

    public static final StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }

    private StaticLoggerBinder() {
        try {
            Level var1 = Level.TRACE;
        } catch (NoSuchFieldError var2) {
            Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
        }

    }

    public ILoggerFactory getLoggerFactory() {
        return this.loggerFactory;
    }

    public String getLoggerFactoryClassStr() {
        return loggerFactoryClassStr;
    }
}

这是一个非接口的类,所以会在编译器决定。其内部实际上负责返回具体日志实现类的工厂类实例。而且这个类已经不是slf4j-api.jar内的类了,而是slf4j-log4j12.jar内的类:

看到这里应该明朗了,每种具体的日志实现类都要提供一个StaticLoggerBinder类,当具体使用时,只需要导入其中一个jar到类路径即可。slf4j-api的getLogger方法就可以通过staticbinder类拿到具体实现类的工厂,进而构建具体日志实现类的实例。由于这里的StaticLoggerBinder类是非interface的,所以在编译器就可以确定,所以这是一种静态绑定。

这与common-logging的classloader运行时加载方式不同。那如果有多个实现类,也就是多个staticbinder类在类路径下呢?这时slf4j会发出警告,具体绑定哪一个是不确定的,与虚拟机具体的执行有关,所以我们需要保证只有一个staticbinder类在类路径下。

2.适配器:

使用slf4j-xxx.jar工厂返回的日志实例其实是一个adapter,其本身不会提供日志功能的实现,而是转交给具体的日志实现类完成,下面是Log4jLoggerAdapter的debug方法:

    public void debug(String format, Object arg1, Object arg2) {
        if (this.logger.isDebugEnabled()) {
            FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
            this.logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
        }

    }

可以看到这里转交给了log变量完成,而log变量就是log4j日志实现类的实例。slf4j-xxx.jar仅仅是完成适配。

lombok

最后奉上一个小技巧,我们可以使用@slf4j注解来完成日志变量的定义和实例化,这样就不需要每一次都在类里写Log log = Factory.getLogger()这样的方法了。lombok会为我们自动生成一个log变量,例子:

@slf4j
public class A {
    public void f(){
        log.info("name: {}", "ly");
    }
}

在idea下使用需要再导入一个idea里的lombok插件,这样就不会报编译错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值