Slf4j的优势与原理

业务中经常用到slf4j来写日志,但是没有深入研究过为啥通过这个就可以调用log4j或者logback的函数来写日志呢?

 

一、优势

《阿里巴巴Java开发手册》关于日志章节专门提到:

【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架

SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);

今天觉得有必要研究一下。

二、原理

slf4j采用门面模式,即把自己作为一个日志接口,并不提供实现。

这里调用log4j或者Logback的实现。我的演示代码用的是logback。

 LoggerFactory.getLogger(xxx.class)

追踪LoggerFactory.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;
    }

调用是如下方法(直接获取类名当做logger名称传入):

    /**
     * Return a logger named according to the name parameter using the
     * statically bound {@link ILoggerFactory} instance.
     * 
     * @param name
     *            The name of the logger.
     * @return logger
     */
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

我们看下getILoggerFacotry函数

/**
     * Return the {@link ILoggerFactory} instance in use.
     * <p/>
     * <p/>
     * ILoggerFactory instance is bound with this class at compile time.
     * 
     * @return the ILoggerFactory instance in use
     */
    public static ILoggerFactory getILoggerFactory() {
       // 这里用了double-check!!!
        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");
    }

我们发现关键的代码在这里:

 StaticLoggerBinder.getSingleton().getLoggerFactory()

我们跟进去,发现这个类包名为org.slf4j.impl,但是在logback包里!!!!!!!!

我们发现该类使用了单例模式

getLoggerFactory最终返回LoggerContext类

 

    public ILoggerFactory getLoggerFactory() {
        if (!initialized) {
            return defaultLoggerContext;
        }

        if (contextSelectorBinder.getContextSelector() == null) {
            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
        }
        return contextSelectorBinder.getContextSelector().getLoggerContext();
    }

获取ContextSelector然后获取LoggerContext。

public interface ContextSelector {

// 嗨,看这里!!
    LoggerContext getLoggerContext();

    LoggerContext getLoggerContext(String name);

    LoggerContext getDefaultLoggerContext();

    LoggerContext detachLoggerContext(String loggerContextName);

    List<String> getContextNames();
}

最终调用了LoggerContext的getLogger方法

 

源码如下:

    public final Logger getLogger(final Class<?> clazz) {
        return getLogger(clazz.getName());
    }

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

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

        // 如果要的是ROOT logger直接返回
        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
            return root;
        }

        int i = 0;
        Logger logger = root;

        // 检查是否存在,如果存在直接返回(从缓存中拿)
        Logger childLogger = (Logger) loggerCache.get(name);
        if (childLogger != null) {
            return childLogger;
        }

        // 如果想要的logger不存在, 创建所需的logger,也包括中间的logger
        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);
                  // 将Logger放到缓存中
                    loggerCache.put(childName, childLogger);
                    incSize();
                }
            }
            logger = childLogger;
            if (h == -1) {
                return childLogger;
            }
        }
    }

这个类中还包括获取所有Logger的函数

   public List<Logger> getLoggerList() {
        Collection<Logger> collection = loggerCache.values();
        List<Logger> loggerList = new ArrayList<Logger>(collection);
        Collections.sort(loggerList, new LoggerComparator());
        return loggerList;
    }

 

另外我们发现logback还用了SPI机制

实现ServletContainerInitializer接口,在web容器初始化时将一个LogbackServletContextListener实例注册到servlet上下文中。

源码如下:

/**
 * Allows for graceful shutdown of the {@link LoggerContext} associated with this web-app.
 * 
 * @author Ceki Gulcu
 * @since 1.1.10
 */
public class LogbackServletContextListener implements ServletContextListener {

    ContextAwareBase contextAwareBase = new ContextAwareBase();

    @Override
    public void contextInitialized(ServletContextEvent sce) {

    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

        ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
        if (iLoggerFactory instanceof LoggerContext) {
            LoggerContext loggerContext = (LoggerContext) iLoggerFactory;
            contextAwareBase.setContext(loggerContext);
            StatusViaSLF4JLoggerFactory.addInfo("About to stop " + loggerContext.getClass().getCanonicalName() + " [" + loggerContext.getName() + "]", this);
            loggerContext.stop();
        }
    }

}

在上下文销毁时调用LoggerContext的stop函数,执行日志的停止操作。

 public void stop() {
        reset();
        fireOnStop();
        resetAllListeners();
        super.stop();
    }

其中super.stop

    public void stop() {
        // 这里不检查 "started" ,因为executor service使用了懒加载机制, 不是在starter函数中创建的
// 停止executor service
        stopExecutorService();

        started = false;
    }

更多细节可以进入源码中查看。

如果觉得本文对你有帮助,欢迎点赞评论,欢迎关注我,我将努力创作更多更好的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悟鸣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值