当前位置: 首页 > article >正文

2.slf4j入口

文章目录

  • 一、故事引入
  • 二、原理探究
  • 三、SLF4JServiceProvider
  • 四、总结

一、故事引入

故事要从下面这段代码说起

public class App {

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

    public static void main( String[] args ) throws Exception {
        logger.info("abc");
    }
}

然后再加上一段日志配置

logback.xml

<configuration >
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %X{mdcKey} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 定义日志级别和输出位置 -->
    <root level="info">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

pom.xml中的配置如下

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>2.0.15</version>
</dependency>

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.5.15</version>
</dependency>

以上是一个最简单的使用logback实现slf4j的demo, 运行之后我们可以在控制台看到打印如下

SLF4J(I): Connected with provider of type [ch.qos.logback.classic.spi.LogbackServiceProvider]
2025-01-16 10:03:52 [main]  INFO  per.qiao.App - abc

为什么一句logger.info("abc");就把日志打印出来了, 其中原理有哪些, 那么本系列我们就一起来探究其中的门道。可以打开下载到的slf4j和logback源码项目, 跟着代码走…

二、原理探究

LoggerFactory.getLogger(App.class);

对应的是org.slf4j.LoggerFactory#getLogger(Class<?> clazz)

public static Logger getLogger(Class<?> clazz) {
    // 获取class对应的logger对象
    Logger logger = getLogger(clazz.getName());
    // 系统属性: slf4j.detectLoggerNameMismatch
    if (DETECT_LOGGER_NAME_MISMATCH) {
        // 调用当前方法的类
        Class<?> autoComputedCallingClass = Util.getCallingClass();
        // 调用getLogger的类和传入的class是否相等
        if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
            // 
            Reporter.warn(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                            autoComputedCallingClass.getName()));
            Reporter.warn("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
        }
    }
    return logger;
}

这里第一句getLogger是我们要探究的核心;

后面的判断用来校验当前执行getLogger方法的类和传入的Class对象是否是同一个类, 什么意思呢?? , 比如我们当前写代码的类是从别的类中复制而来, 那么可能这个private static final Logger logger = LoggerFactory.getLogger(App.class);就没有修改其中的App.class, 可能还是App1.class, 如果此时你开启了这种校验, 那么就会打印下面的一串警告日志。

开启方法

1、属性配置

static {
    System.setProperty("slf4j.detectLoggerNameMismatch", "true");
}
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main( String[] args ) throws Exception {
	logger.info("abc");
}

2、idea启动项中 Add VM options中添加-Dslf4j.detectLoggerNameMismatch=true, 也是可以的

-Dlogback.statusListenerClass=STDOUT

3、启动命令

java -Dslf4j.detectLoggerNameMismatch=true per.qiao.App

当然了, 2和3是一个东西

如果我们想要在main方法第一行加上属性设置行不行呢? , 就像下面这样

public static void main( String[] args ) throws Exception {
	System.setProperty("slf4j.detectLoggerNameMismatch", "true");
	logger.info("abc");
}

其实是不行的哈, 因为main方是static修饰的, 而我们定义的logger也是static修饰的, jvm调用main方法之前会先调用LoggerFactory.getLogger进行日志初始化过程, 导致自己设置的失效; 关于一个类中默认的执行顺序, 大家也可以去了解下。

文归正传, 看下getLogger方法

public static Logger getLogger(String name) {
    // 获取日志工厂
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}
  1. 拿到工厂
  2. 使用工厂拿到Logger对象

门道就在这个getILoggerFactory

public static ILoggerFactory getILoggerFactory() {
    return getProvider().getLoggerFactory();
}

static SLF4JServiceProvider getProvider() {
    // 如果未初始化
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        // double check
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                // 初始化中
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                // 执行初始化
                performInitialization();
            }
        }
    }
    // ... 省略代码
}

关于synchronized时的double check使用, 大家一定要了然于胸

直接跳到核心方法bind

private final static void bind() {
    try {
        // 1.获取SLF4JServiceProvider
        List<SLF4JServiceProvider> providersList = findServiceProviders();
        // 系统打印SLF4JServiceProvider获取的情况
        reportMultipleBindingAmbiguity(providersList);
        // 2.取第一个
        if (providersList != null && !providersList.isEmpty()) {
            PROVIDER = providersList.get(0);
            // SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
            // 3.初始化
            PROVIDER.initialize();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            // 打印实际使用的provider对象
            reportActualBinding(PROVIDER);
        } else {
            // ...
        }
        postBindCleanUp();
    } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    }
}
  1. spi获取SLF4JServiceProvider
  2. 取第一个SLF4JServiceProvider
  3. 初始化它

这里两个核心方法findServiceProvidersPROVIDER.initialize()

findServiceProviders

static List<SLF4JServiceProvider> findServiceProviders() {
        List<SLF4JServiceProvider> providerList = new ArrayList<>();

    // 加载当前类的类加载器
    final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();

    // 1.获取系统指定的SLF4JServiceProvider
    SLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory);
    if (explicitProvider != null) {
        providerList.add(explicitProvider);
        return providerList;
    }

     // 2.spi获取SLF4JServiceProvider
     ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);

    // 添加到集合中返回
    Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
    while (iterator.hasNext()) {
        safelyInstantiate(providerList, iterator);
    }
    return providerList;
}

这里我们可以看到获取SLF4JServiceProvider有两个方式

  1. 系统指定可以使用哪个; 使用-Dslf4j.provider=SLF4JServiceProvider的类全路径指定, 当你的项目中由于种种原因配置了多个slf4j的实现模块的时候, 这时候你就可以用这个配置指定使用哪个具体实现, 例如slf4j-jdk14.jarlogback-classic.jar同时存在的话, 你可以排除某个依赖或者使用这个配置指定
  2. 使用spi获取获取所有的SLF4JServiceProvider, 只有其中一个生效

spi获取的顺序是按照包顺序获取的, 也就是按照自然排序, 所以第一个就是名字排序靠前的那个包中的

关于spi, 在一些源码中经常被用到, 例如springboot的自动装配, dubbo加载, 它俩用的这个思路, 但是属于spi的变种

// springboot
SpringFactoriesLoader.loadFactoryNames(type, classLoader)

// duoboo
ExtensionLoader.getExtensionLoader(ExtensionFactory.class)
    					  .getAdaptiveExtension());

最核心的部分就是PROVIDER.initialize

SLF4JServiceProvider#initialize

这个方法有具体的实现模块提供, 本节不介绍

三、SLF4JServiceProvider

slf4j实现模块的入口类, 通过spi加载

/**
 * 它取代了SLF4J 1.0版本中使用的旧的静态绑定机制。X到1.7.x。
 */
public interface SLF4JServiceProvider {

    // 获取日志工厂实例
    public ILoggerFactory getLoggerFactory();

    // 日志标记工厂
    public IMarkerFactory getMarkerFactory();

    // 支持mdc的
    public MDCAdapter getMDCAdapter();

    // 版本校验的
    public String getRequestedApiVersion();

    // 用来初始化实现模块
    public void initialize();
}

这里最核心的是initialize方法

getMarkerFactory方法, 在打印日志的时候, 有写方法有Marker参数, 例如

public void info(Marker marker, String msg);
public void info(Marker marker, String format, Object arg);

getMDCAdapter方法, 在使用MDC的时候, 会用到它

四、总结

slf4j提供了操作日志的门面

  1. 日志初始化入口实LoggerFactory.getLogger(App.class);
  2. 可以通过-Dslf4j.provider=SLF4JServiceProvider的类全路径来指定日志最终使用的slf4j实现
  3. 通过spi加载了SLF4JServiceProvider对象
  4. 初始化SLF4JServiceProvider后, 通过它得到具体的Logger对象

http://www.kler.cn/a/507353.html

相关文章:

  • OpenCV相机标定与3D重建(55)通用解决 PnP 问题函数solvePnPGeneric()的使用
  • [Qualcomm]Qualcomm MDM9607 SDK代码下载操作说明
  • MTK6768 Android13 亮度条均匀调节实现
  • 如何在前端给视频进行去除绿幕并替换背景?-----Vue3!!
  • ClickHouse-CPU、内存参数设置
  • 数据结构-线性表
  • Edge Scdn是什么,它如何提升网站安全性与访问速度?
  • 【前端】框架-构建-包管理-语言-语法-生态工具
  • MySQL配置文件
  • Jmeter如何进行多服务器远程测试
  • excel仅复制可见单元格,仅复制筛选后内容
  • 2024年第十二期 | CCF ODC《开源战略动态月报》
  • 【JVM】总结篇之GC性能优化案例
  • vue2版本tinymce简单使用指南
  • K8S中的Pod调度之亲和性调度
  • C++实现红黑树
  • 【搭建JavaEE】(3)前后端交互,请求响应机制,JDBC数据库连接
  • 设置virtualBox7.0.12 ubuntu24.10 和 windows之间双向复制共享剪贴板
  • QT窗口阴影+拖动
  • 机器学习第一道菜(一):线性回归的理论模型
  • Apache Nifi 信息泄露漏洞复现(CVE-2024-56512)(附脚本)
  • Unity学习之UGUI登录界面制作
  • PDF编辑 PDF-XChange Editor Plus 免装优化版
  • 浅谈计算机网络01 | SDN数据平面
  • JVM类加载器(附面试题)
  • Qt Quick 和 Qt Designer