2.slf4j入口

server/2025/1/20 14:38:26/

文章目录

  • 一、故事引入
  • 二、原理探究
  • 三、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.detectLoggerNameMismatchif (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 checksynchronized (LoggerFactory.class) {if (INITIALIZATION_STATE == UNINITIALIZED) {// 初始化中INITIALIZATION_STATE = ONGOING_INITIALIZATION;// 执行初始化performInitialization();}}}// ... 省略代码
}

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

直接跳到核心方法bind

private final static void bind() {try {// 1.获取SLF4JServiceProviderList<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.获取系统指定的SLF4JServiceProviderSLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory);if (explicitProvider != null) {providerList.add(explicitProvider);return providerList;}// 2.spi获取SLF4JServiceProviderServiceLoader<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实现, 如果一个项目中引入了多个sl4fj的实现模块, 注意看实际运行的是哪一个
  3. 通过spi加载了SLF4JServiceProvider对象
  4. 初始化SLF4JServiceProvider后, 通过它得到具体的Logger对象

http://www.ppmy.cn/server/159909.html

相关文章

【机器学习实战入门项目】使用深度学习创建您自己的表情符号

深度学习项目入门——让你更接近数据科学的梦想 表情符号或头像是表示非语言暗示的方式。这些暗示已成为在线聊天、产品评论、品牌情感等的重要组成部分。这也促使数据科学领域越来越多的研究致力于表情驱动的故事讲述。 随着计算机视觉和深度学习的进步&#xff0c;现在可以…

二百八十三、Java——IDEA中通过快捷键查看某一类的定义位置

一、目的 学习尚硅谷Flink时&#xff0c;尚硅谷项目代码中有类型报错Cannot resolve method TUPLE(?, ?) 二、快捷键定义类的位置 1、定位到 Types 类&#xff1a; 将光标放在 Types 上。 使用快捷键 CtrlB&#xff08;Windows/Linux&#xff09;或 CmdB&#xff08;macOS…

PortSwigger靶场练习---第二关-查找和利用未使用的 API 端点

第二关&#xff1a;Finding and exploiting an unused API endpoint 实验&#xff1a;查找和利用未使用的 API 端点 PortSwigger靶场地址&#xff1a; Dashboard | Web Security Academy - PortSwigger 题目&#xff1a; 官方提示&#xff1a; 在 Burp 的浏览器中&#xff0c…

蓝桥杯3526 子树的大小 | 数学规律

题目传送门 这个题目是一个数学题&#xff0c;比较好的方法是从上往下寻找子树的最左和最右的结点&#xff0c;每层统计子结点数&#xff0c;到树的底部时打印结果。 如何求最左、最右的子结点呢&#xff1f; 对于第i个结点,其前面有i-1个结点,每个结点各有m个孩子,再加上1号结…

R语言的并发编程

R语言的并发编程 引言 在现代计算中&#xff0c;如何有效地利用计算资源进行数据处理和分析已成为一个重要的研究方向。尤其在大数据时代&#xff0c;数据量的急剧增加让单线程处理方式显得力不从心。为了解决这一问题&#xff0c;各种编程语言都开展了并发编程的研究和应用。…

adb 常用命令总结

adb logcat -v time | grep -iE "AndroidRuntime|MyHttpRequest|getResponseFromModel|Exception|OkhttpClient"这行命令的作用是使用 adb logcat 命令来过滤和显示安卓设备上的日志信息&#xff0c;其中包括特定的日志条目。让我们逐一解释这段命令&#xff1a; a…

通过内核模块按fd强制tcp的quickack方法

一、背景 tcp的quickack功能是为了让ack迅速回发&#xff0c;快速响应&#xff0c;减少网络通讯时延&#xff0c;属于一个优化项&#xff0c;但是tcp的quickack是有配额限制的&#xff0c;配置是16个quick&#xff0c;也就是短时间内quickack了16次以后&#xff0c;这个配额为…

NSIS 创建一键安装程序

nsis 安装redis 、mysql 、jdk navicat、 notepad、 环境变量只能设置一次 &#xff0c;多次 只有最后一次生效 ; 函数&#xff1a;检查目录是否已在 PATH 中 Function setPath; 首先获取当前的 PATH 环境变量ReadEnvStr $0 PATH; 检查是否成功读取 PATH 环境变量StrCmp $0 &q…