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对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/954992.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot的企业级工位管理系统【源码+文档+部署讲解】

系统介绍 基于SpringBootVue实现的企业级工位管理系统采用前后端分离架构方式&#xff0c;系统设计了管理员、员工两种角色&#xff0c;系统实现了用户登录与注册、个人中心、员工管理、部门信息管理、工位信息管理、使用情况管理、工位分配管理等功能。 技术选型 开发工具&…

keepalived双机热备(LVS+keepalived)实验笔记

目录 前提准备&#xff1a; keepalived1&#xff1a; keepalived2&#xff1a; web1&#xff1a; web2&#xff1a; keepalived介绍 功能特点 工作原理 应用场景 前提准备&#xff1a; 准备4台centos&#xff0c;其中两台为keepalived&#xff0c;两台为webkeepalive…

【Linux】12.Linux进程概念(1)

文章目录 1. 冯诺依曼体系结构2. 操作系统(Operator System)概念设计OS的目的胆小的操作系统定位如何理解 "管理"总结 3. 进程基本概念task_struct-PCB的一种task_ struct内容分类组织进程查看进程通过系统调用获取进程标示符通过系统调用创建进程-fork初识 1. 冯诺依…

LabVIEW 程序中的 R6025 错误

R6025错误 通常是 运行时库 错误&#xff0c;特别是与 C 运行时库 相关。这种错误通常会在程序运行时出现&#xff0c;尤其是在使用 C 编译的程序或依赖 C 运行时库的程序时。 ​ 可能的原因&#xff1a; 内存访问冲突&#xff1a; R6025 错误通常是由于程序在运行时访问无效内…

03JavaWeb——Ajax-Vue-Element(项目实战)

1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xf…

2024 京东零售技术年度总结

每一次回望&#xff0c;都为了更好地前行。 2024 年&#xff0c;京东零售技术在全面助力业务发展的同时&#xff0c;在大模型应用、智能供应链、端技术、XR 体验等多个方向深入探索。京东 APP 完成阶段性重要改版&#xff0c;打造“又好又便宜”的优质体验&#xff1b;国补专区…

Apache搭建https服务器

Apache搭建https服务器 REF: 使用OpenSSL自建一个HTTPS服务

XML在线格式化 - 加菲工具

XML在线格式化 打开网站 加菲工具 选择“XML 在线格式化” 输入XML&#xff0c;点击左上角的“格式化”按钮 得到格式化后的结果

BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测

BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测 目录 BO-SVM贝叶斯算法优化支持向量机的数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于BO-SVR贝叶斯算法优化支持向量机的数据多变量时间序列预测&#xff0c;加入5折交叉验…

flutter R库对图片资源进行自动管理

项目中对资源的使用是开发过程中再常见不过的一环。 一般我们在将资源导入到项目中后,会通过资源名称来访问。 但在很多情况下由于我们疏忽输入错了资源名称,从而导致资源无法访问。 所以,急需解决两个问题: 资源编译期可检查可方便预览资源安装相关插件 在vscode中安装两…

【鱼皮大佬API开放平台项目】Spring Cloud Gateway HTTPS 配置问题解决方案总结

问题背景 项目架构为前后端分离的微服务架构&#xff1a; 前端部署在 8000 端口API 网关部署在 9000 端口后端服务包括&#xff1a; api-backend (9001端口)api-interface (9002端口) 初始状态&#xff1a; 前端已配置 HTTPS&#xff08;端口 8000&#xff09;后端服务未配…

Windows远程桌面网关出现重大漏洞

微软披露了其Windows远程桌面网关&#xff08;RD Gateway&#xff09;中的一个重大漏洞&#xff0c;该漏洞可能允许攻击者利用竞争条件&#xff0c;导致拒绝服务&#xff08;DoS&#xff09;攻击。该漏洞被标识为CVE-2025-21225&#xff0c;已在2025年1月的补丁星期二更新中得到…

‌如何有效学习PyTorch:从基础到实践的全面指南‌

随着人工智能和深度学习技术的飞速发展&#xff0c;PyTorch作为当前最流行的深度学习框架之一&#xff0c;凭借其动态计算图、灵活的编程模型以及强大的社区支持&#xff0c;在学术界和工业界均得到了广泛应用。本文旨在为初学者和有一定基础的读者提供一套系统、全面的PyTorch…

2Spark Core

2Spark Core 1.RDD 详解1) 为什么要有 RDD?2) RDD 是什么?3) RDD 主要属性 2.RDD-API1) RDD 的创建方式2) RDD 的算子分类3) Transformation 转换算子4) Action 动作算子 3. RDD 的持久化/缓存4. RDD 容错机制 Checkpoint5. RDD 依赖关系1) 宽窄依赖2) 为什么要设计宽窄依赖 …

视频超分(VSR)论文阅读记录/idea积累(一)

STAR: Spatial-Temporal Augmentation with Text-to-Video Models for Real-World Video Super-Resolution 关键词: text-to-video (T2V) Local Information Enhancement Module (LIEM) Dynamic Frequency (DF) 引言: VSR: 传统VSR分两大类recurrent-based和sliding-wind…

MySQL8数据库全攻略:版本特性、下载、安装、卸载与管理工具详解

大家好&#xff0c;我是袁庭新。 MySQL作为企业项目中的主流数据库&#xff0c;其5.x和8.x版本尤为常用。本文将详细介绍MySQL 8.x的特性、下载、安装、服务管理、卸载及管理工具&#xff0c;旨在帮助用户更好地掌握和使用MySQL数据库。 1.MySQL版本及下载 企业项目中使用的…

Docker安装PostGreSQL docker安装PostGreSQL 完整详细教程

Docker安装PostGreSQL docker安装PostGreSQL 完整详细教程 Docker常用命令大全Docker 运行命令生成Docker 上安装 PostGreSQL 14.15 的步骤&#xff1a;1、拉取 PostGreSQL 14.15 镜像2、创建并运行容器3、测试连接4、设置所有IP都可以运行连接进入容器内 修改配置文件关闭容器…

Elasticsearch:Jira 连接器教程第一部分

作者&#xff1a;来自 Elastic Gustavo Llermaly 将我们的 Jira 内容索引到 Elaasticsearch 中以创建统一的数据源并使用文档级别安全性进行搜索。 在本文中&#xff0c;我们将回顾 Elastic Jira 原生连接器的一个用例。我们将使用一个模拟项目&#xff0c;其中一家银行正在开发…

Spring 6 第1章——概述

一.Spring是什么 Spring是一款主流的Java EE轻量级&#xff08;体积小、不需要依赖其它组件&#xff09;开源框架Spring的目的是用于简化Java企业级应用的开发难度和开发周期Spring的用途不仅限于服务端的开发&#xff0c;从简单性、可测试性和松耦合的角度而言&#xff0c;任…

git管理源码之git安装和使用

git是什么&#xff1f; git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理&#xff0c;也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。git与常用的版本控制工具SVN等不同&#xff0c;它采用…