前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Slf4j的优势与原理

Slf4j的优势与原理

作者头像
明明如月学长
发布2021-08-31 15:48:50
7430
发布2021-08-31 15:48:50
举报
文章被收录于专栏:明明如月的技术专栏

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019/04/29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、优势
  • 二、原理
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档