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.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);
}
- 拿到工厂
- 使用工厂拿到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);}
}
- spi获取SLF4JServiceProvider
- 取第一个SLF4JServiceProvider
- 初始化它
这里两个核心方法findServiceProviders
和PROVIDER.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有两个方式
- 系统指定可以使用哪个; 使用
-Dslf4j.provider=SLF4JServiceProvider的类全路径
指定, 当你的项目中由于种种原因配置了多个slf4j的实现模块的时候, 这时候你就可以用这个配置指定使用哪个具体实现, 例如slf4j-jdk14.jar
和logback-classic.jar
同时存在的话, 你可以排除某个依赖或者使用这个配置指定 - 使用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提供了操作日志的门面
- 日志初始化入口实
LoggerFactory.getLogger(App.class);
- 可以通过
-Dslf4j.provider=SLF4JServiceProvider的类全路径
来指定日志最终使用的slf4j实现 - 通过spi加载了
SLF4JServiceProvider
对象 - 初始化
SLF4JServiceProvider
后, 通过它得到具体的Logger对象
相关文章:
2.slf4j入口
文章目录 一、故事引入二、原理探究三、SLF4JServiceProvider四、总结 一、故事引入 故事要从下面这段代码说起 public class App {private static final Logger logger LoggerFactory.getLogger(App.class);public static void main( String[] args ) throws Exception {lo…...

初学stm32 --- CAN
目录 CAN介绍 CAN总线拓扑图 CAN总线特点 CAN应用场景 CAN物理层 CAN收发器芯片介绍 CAN协议层 数据帧介绍 CAN位时序介绍 数据同步过程 硬件同步 再同步 CAN总线仲裁 STM32 CAN控制器介绍 CAN控制器模式 CAN控制器模式 CAN控制器框图 发送处理 接收处理 接收过…...

软件测试—接口测试面试题及jmeter面试题
一,接口面试题 1.接口的作用 实现前后端的交互,实现数据的传输 2.什么是接口测试 接口测试就是对系统或组件之间的接口进行测试,主要是校验数据的交换、传递和控制管理过程,以及相互逻辑关系 3.接口测试必要性 1.可以发现很…...

图论的起点——七桥问题
普瑞格尔河从古堡哥尼斯堡市中心流过,河中有小岛两座,筑有7座古桥,哥尼斯堡人杰地灵,市民普遍爱好数学。1736年,该市一名市民向大数学家Euler提出如下的所谓“七桥问题”: 从家里出发,7座桥每桥…...
嵌入式开发通讯协议大全(在写中)
目录 modbus RTU通讯协议: pmbus通讯协议: modbus RTU通讯协议: 主要应用功能: 规范了软件变量,访问功能码,给不同工程师开发的不同产品有统一的通讯标准 帧结构简单,占用带宽少,…...
webpack 4 升级 webpack 5
升级至最新的 webpack 和 webpack-cli npm run build 报错, unknown option -p 解决方案: 改成 --mode production npm run build 报错 unknown option --hide-modules 解决方案:直接移除 npm run build 报错:TypeError: Cannot a…...
oneplus3t-lineageos-16.1编译-android9, oneplus3t-lineage-14编译-android7
oneplus3t-lineage-14编译-android7 1 清华linageos镜像 x lineage-14.1-20180223-nightly-oneplus3-signed.zip ntfs分区挂载为普通用户目录 , ext4分区挂载为普通用户目录 bfsu/lineageOS镜像 ts/lingeageOS镜像 oneplus3/lineage-build-simple-manual.md, manifest-p…...

HTML中最基本的东西
本文内容的标签,将是看懂HTML的最基本之基本 ,是跟您在写文章时候一样内容。一般想掌握极其容易,但是也要懂得如何使用,过目不忘,为手熟尔。才是我们学习的最终目的。其实边看边敲都行,或者是边看边复制粘贴…...

<OS 有关>Ubuntu 24 安装 openssh-server, tailscale+ssh 慢增加
更新日志: Created on 14Jan.2025 by Dave , added openssh-server, tailescape Updated on 15Jan.2025, added "tailescape - tailscape ssh" 前期准备: 1. 更新可用软件包的数据库 2. 升级系统中所有已安装的软件包到最新版本 3. 安装 cur…...
神经网络常见操作(卷积)输入输出
卷积 dimd的tensor可以进行torch.nn.Convnd(in_channels,out_channels),其中nd-1,d-2对于torch.nn.Convnd(in_channels,out_channels),改变的是tensor的倒数n1维的大小 全连接 使用torch.nn.Linear(in_features,out_features,bias)实现YXWT b,其中X 的形状为 (ba…...

25/1/16 嵌入式笔记 STM32F108
输入捕获 TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_TimeBaseStruct.TIM_Period 0xFFFF; // 自动重装载值 TIM_TimeBaseStruct.TIM_Prescaler 71; // 预分频值 TIM_TimeBaseStruct.TIM_ClockDivision 0; TIM_TimeBaseStruct.TIM_CounterMode TIM_CounterMode_Up…...

mac 安装 node
brew versions node // 安装 node brew versions node14 // 安装指定版本 卸载node: sudo npm uninstall npm -g sudo rm -rf /usr/local/lib/node /usr/local/lib/node_modules /var/db/receipts/org.nodejs.* sudo rm -rf /usr/local/include/node /Users/$USER/.npm su…...
mysql常用运维命令
mysql常用运维命令 查看当前所有连接 -- 查看当前所有连接 SHOW FULL PROCESSLIST;说明: 关注State状态列,是否有锁。如果大量状态是waiting for handler commit检查磁盘是否占满关注Time耗时列,是否有慢查询关注Command列,如果…...
正则表达式学习网站
网上亲测好用的网站: Regexlearn 这个网站可以从0开始教会正则表达式的使用。 mklab 包含常用表达式,车次,超链接,号码等提取。...
gradle,adb命令行编译备忘
追踪依赖(为了解决duplicateClass…错误) gradlew.bat app:dependencies > dep-tree.txt # 分析dep-tree.txt的依赖结构,找到对应的包,可能需要做exclude控制,或者查看库issueverbose编译(我一直需要verbose) gradlew.bat assembleDebug -Dhttps.pr…...
C++:工具VSCode的编译和调试文件内容:
ubuntu24.04, vscode 配置文件 C 的环境 下载的gcc,使用命令为 sudo aptitude update sudo aptitude install build-essential -f- sudo: 以超级用户权限运行命令。 - aptitude: 包管理工具,用于安装、更新和删除软件包。 - install: 安装指…...

SpringMVC Idea 搭建 部署war
1.创建 Idea项目 使用Maven模板 创建 webApp模板项目 2.导入依赖 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://ma…...
YOLOv10-1.1部分代码阅读笔记-loaders.py
loaders.py ultralytics\data\loaders.py 目录 loaders.py 1.所需的库和模块 2.class SourceTypes: 3.class LoadStreams: 4.class LoadScreenshots: 5.class LoadImagesAndVideos: 6.class LoadPilAndNumpy: 7.class LoadTensor: 8.def autocast_list(source…...

Windows的Redis查看自己设置的密码并更改设置密码
查看密码 由于我的Redis安装很久了,所以忘记是否有设置密码,查看步骤如下: 启动redis,启动流程可以看这篇文章:https://blog.csdn.net/changyana/article/details/127679871 在redis安装目录下打开redis-cli.exe&…...

【Linux】sed编辑器二
一、处理多行命令 sed编辑器有3种可用于处理多行文本的特殊命令。 N:加入数据流中的下一行,创建一个多行组进行处理;D:删除多行组中的一行;P:打印多行组中的一行。 1、next命令:N 单行next命…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
NPOI Excel用OLE对象的形式插入文件附件以及插入图片
static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决
问题: pgsql数据库通过备份数据库文件进行还原时,如果表中有自增序列,还原后可能会出现重复的序列,此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”,…...

动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...