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);
}
  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.mzph.cn/pingmian/67583.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【软件测试】用例篇

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 &#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系…

优雅解决webview_flutter不支持安卓选择图片视频文件问题

这个问题&#xff0c;网上一搜索&#xff0c;就是要去修改别人写好的插件。 搞Flutter开发&#xff0c;尽量不要去修改别人的插件 &#xff0c;特别是像 webview_flutter 这种比较大的官方插件。 相信我&#xff0c;你拿捏不了它。 主要问题就是&#xff1a; webview_flutter…

Jenkins-基于Role的鉴权机制

jenkins自带了一些全局性的安全配置。 但无法通过job等相对细粒度的来控制使用者的权限。但它可以借助相关的插件实现细颗粒的权限控制。 插件&#xff1a; Role-based Authorization Strategy 需要在configure global security中配置授权策略如下&#xff1a; 保存后&#x…

javaEE初阶————多线程初阶(2)

今天给大家带来第二期啦&#xff0c;保证给大家讲懂嗷&#xff1b; 1&#xff0c;线程状态 NEW安排了工作还未开始行动RUNNABLE可工作的&#xff0c;或者即将工作&#xff0c;正在工作BLOCKED排队等待WAITING排队等待其他事TIMED_WAITING排队等待其他事TERMINATED工作完成了 …

VB.net实战(VSTO):解决WPS Ribbon图标灰色背景

问题&#xff1a;用VSTO制作插件&#xff0c;在MS Office中图标显示正常&#xff0c;但在WPS Office中图标显示为灰色背景 原因&#xff1a;使用的图标是纯透明背景的&#xff0c;这样的图标在WPS中会变为灰色背景。 以下这个解决办法是我自己摸索出来的&#xff0c;对您有用的…

在.NET用C#将Word文档转换为HTML格式

将Word文档转换为HTML格式尤其具有显著的优势&#xff0c;它不仅能够确保文档内容在多种设备和平台上保持一致灵活的显示&#xff0c;还便于通过网络进行传播和集成到各种Web应用中。随着越来越多的企业和开发者寻求更灵活、更具兼容性的文件处理方式&#xff0c;.NET框架下的C…

在服务器上增加新网段IP的路由配置

在服务器上增加新网段IP的路由配置 前提条件步骤一:检查当前路由表步骤二:添加新路由步骤三:验证新路由步骤四:持久化路由配置脚本示例结论在网络管理中,路由配置是一项基本且重要的任务。它决定了数据包在网络中的传输路径。本文将详细介绍如何在服务器上增加新的路由配置…

鸿蒙学习构建视图的基本语法(二)

一、层叠布局 // 图片 本地图片和在线图片 Image(https://developer.huawei.com/allianceCmsResource/resource/HUAWEI_Developer_VUE/images/080662.png) Entry Component//自适应伸缩 设置layoutWeight属性的子元素与兄弟元素 会按照权重进行分配主轴的空间// Position s…

介绍下常用的前端框架及时优缺点

以下是一些常用的前端框架及其优缺点介绍&#xff1a; React • 优点 • 组件化架构&#xff1a;可构建可复用的UI组件&#xff0c;提高开发效率和组件可维护性。 • 虚拟DOM&#xff1a;高效更新页面&#xff0c;减少直接操作DOM的性能开销。 • 灵活性和可扩展性&#xf…

使用python脚本点击页面,避免google colab超时断开

from pynput.mouse import Button, Controller import timedef move_and_click_periodically(x, y, interval10):mouse Controller()while True:# 移动鼠标到指定位置mouse.position (x, y)print(f现在鼠标位置: {mouse.position})# 点击鼠标左键mouse.click(Button.left, 1)…

【Leetcode 每日一题 - 扩展】421. 数组中两个数的最大异或值

问题背景 给你一个整数数组 n u m s nums nums&#xff0c;返回 n u m s [ i ] X O R n u m s [ j ] nums[i]\ XOR\ nums[j] nums[i] XOR nums[j] 的最大运算结果&#xff0c;其中 0 ≤ i ≤ j < n 0 ≤ i ≤ j < n 0≤i≤j<n。 数据约束 1 ≤ n u m s . l e n g…

AWS S3 跨账户访问 Cross Account Access

进入S3对应的存储桶&#xff0c;上面选项选权限&#xff0c;存储桶策略 -- 编辑&#xff0c;输入对应的policy。 完全控制&#xff0c;包含上传删除权限&#xff0c;policy如下&#xff1a; {"Version": "2012-10-17","Statement": [{"Si…

电脑风扇声音大怎么办? 原因及解决方法

电脑风扇是电脑的重要组件之一&#xff0c;它的作用是为电脑的各个部件提供冷却&#xff0c;防止电脑过热。然而&#xff0c;有时候我们会发现电脑风扇的声音特别大&#xff0c;不仅影响我们的使用体验&#xff0c;也可能是电脑出现了一些问题。那么&#xff0c;电脑风扇声音大…

使用 Go 语言生成样式美观的 PDF 文件

文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons&#xff1a;JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram&#xff0c;自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 &#xff1f; 5 IDEA必装的插件&…

OpenVela——专为AIoT领域打造的开源操作系统

目录 一、系统背景与开源 1.1. 起源 1.2. 开源 二、系统特点 2.1. 轻量化 2.2. 标准兼容性 2.3. 安全性 2.4. 高度可扩展性 三、技术支持与功能 3.1. 架构支持 3.2. 异构计算支持 3.3. 全面的连接套件 3.4. 开发者工具 四、应用场景与优势 4.1. 应用场景 4.2. …

хорошо哈拉少wordpress俄语主题

хорошо哈拉少wordpress俄语主题 wordpress俄文网站模板&#xff0c;推荐做俄罗斯市场的外贸公司建俄语独立站使用。 演示 https://www.jianzhanpress.com/?p7360

TOSUN同星TsMaster使用入门——3、使用系统变量及c小程序结合panel面板发送报文

本篇内容将介绍TsMaster中常用的Panel面板控件以及使用Panel控件通过系统变量以及c小程序来修改信号的值&#xff0c;控制报文的发送等。 目录 一、常用的Panel控件介绍 1.1系统——启动停止按钮 1.2 显示控件——文本框 1.3 显示控件——分组框 1.4 读写控件——按钮 1.…

Learning Prompt

说明&#xff1a;这是我的学习笔记&#xff0c;很多内容转自网络&#xff0c;请查阅文章末尾的参考资料。 目录 基本要求(C.R.E.A.T.E)总结文章(Summarise)改写文章(Rewrite)根据参考资料回答问题(Question & Answer)参考资料 基本要求(C.R.E.A.T.E) Character This is th…

程序设计:排版、检验报告的上下标解决几种办法

【啰嗦两句】 本文重点在于提供几个针对排版文档、各种检验报告系统等程序设计时&#xff0c;遇到的上下标录入、绘制展示等问题的应对办法&#xff0c;但是准确地说&#xff0c;并没有非常优秀的方案。 【上下标难题】 一般的行业或许对上下标并没有严格要求&#xff0c;多数…

python mysql库的三个库mysqlclient mysql-connector-python pymysql如何选择,他们之间的区别

三者的区别 1. mysqlclient 特点&#xff1a; 是一个用于Python的MySQL数据库驱动程序&#xff0c;用于与MySQL数据库进行交互。 依赖于MySQL的本地库&#xff0c;因此在安装时需要确保系统上已安装了必要的依赖项&#xff0c;如libmysqlclient-dev等。 性能较好&#xff0c…