Slf4j的优势与原理

2021-08-31 15:48:50 浏览数 (1)

业务中经常用到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。

代码语言:javascript复制
 LoggerFactory.getLogger(xxx.class)

追踪LoggerFactory.getLogger源码

代码语言:javascript复制
    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名称传入):

代码语言:javascript复制
    /**
     * 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函数

代码语言:javascript复制
/**
     * Return the {@link ILoggerFactory} instance in use.
     * 
     * 
     * 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");
    }

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

代码语言:javascript复制
 StaticLoggerBinder.getSingleton().getLoggerFactory()

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

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

getLoggerFactory最终返回LoggerContext类

代码语言:javascript复制
    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。

代码语言:javascript复制
public interface ContextSelector {

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

    LoggerContext getLoggerContext(String name);

    LoggerContext getDefaultLoggerContext();

    LoggerContext detachLoggerContext(String loggerContextName);

    List getContextNames();
}

最终调用了LoggerContext的getLogger方法

源码如下:

代码语言:javascript复制
    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的函数

代码语言:javascript复制
   public List getLoggerList() {
        Collection collection = loggerCache.values();
        List loggerList = new ArrayList(collection);
        Collections.sort(loggerList, new LoggerComparator());
        return loggerList;
    }

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

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

源码如下:

代码语言:javascript复制
/**
 * 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函数,执行日志的停止操作。

代码语言:javascript复制
 public void stop() {
        reset();
        fireOnStop();
        resetAllListeners();
        super.stop();
    }

其中super.stop

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

        started = false;
    }

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

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

0 人点赞