SLF4J Spring Boot日志框架

news2025/1/24 17:47:23

JAVA日志框架

JAVA有好多优秀的日志框架,比如log4j、log4j2、logback、JUL(java.util.logging)、JCL(JAVA Common Logging)等等,logback是后起之秀,是Spring Boot默认日志框架。

今天文章的目标不是研究JAVA的这些日志框架,而是在应用中处于他们前面的日志门面SLF4J,以及初步了解一下Spring Boot的默认日志框架是在什么地方配置的、怎么替换Spring Boot默认的日志框架。

SLF4J

SFL4J,全名Simple Logging Facade for Java,意思是简单JAVA日志门面,是Facade设计模式的一个典型实现。

SFL4J本身并没有日志的任何实现,它只是一个日志门面,目的是为了让应用层能够简单的、方便的使用以上提到的各种不同的日志框架,在代码层不做任何改动的情况下,在最终部署的时候决定具体使用哪一个日志框架。冲这一点,SLF4J就非常NB,能允许程序员在编码的时候不考虑具体使用哪一个日志框架、部署的时候不需要修改一行代码、随意选择日志框架。而且,虽然不是很有必要,但是只要你高兴,应用运行的过程中你都可以随时切换日志框架,比如你刚开始选择log4j2,但是后来觉得不爽想要换成logback,只要你在开发的时候使用了SLF4J,你就可以任性地随时切换。

使用SLF4J

严格来说,SLF4J只需要一个包:slf4j-api-xxx.jar(xxx是版本号) 就可以使用,POM文件中引入:

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

之后:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {

  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

应用classpath下有任何前述日志框架存在的情况下,以上代码中的日志打印语句就会根据日志框架的配置输出到控制台或者日志文件中了。

SLF4J API及jar包框架

上述案例可以发现,原则上来说,项目Pom文件引入slf4j-api之后,SLF4J就可以正常工作了,但是SLF4J可以正常工作,并不代表整个日志系统可以正常工作,因为SLF4J只是日志门面,需要绑定后端的日志框架之后,日志系统才能正常工作。

我们先看一下SLF4J官网对于SLF4J API的框架图:
在这里插入图片描述
可以看出,如果我们采用SLF4J本家的日志框架作为我们应用的日志框架的话,比如用logback的话,我们只需要slf4j-api.jar + logback相关的jar包就可以了,或者我们用slf4j自带的simple日志框架的话,也只需要slf4j-api.jar + slf4j-simple.jar。

但是如果用其他的三方日志框架,比如log4j、或者JUL的话,由于三方框架比如log4j早在SLF4J出现之前就已经占据JAVA日志的半壁江山了,所以早期版本的日志框架不可能适应后面才出现的SLF4J,所以只能SLF4J自己想办法来适应。

SLF4J给出的解决方案就是针对各日志框架的API:


slf4j-log4j12-2.0.10.jar: 为绑定log4j version 1.2,这是一个很古老的版本了,早已宣布寿终正寝了,但是考虑到有些老系统可能还在使用log4j 1.x,所以提供了这个jar包以便兼容。其实从SLF4J 1.7.35之后,slf4j-log4j模块的调用就已经在编译器直接导航到slf4j-reload4j的调用了,所以这个模块对于新版本的SLF4J来说几乎没用了。


slf4j-reload4j-2.0.10.jar:后期版本的SLF4J绑定log4j日志框架,同时需要reload4j.jar(log4j 1.2x之后的替代品)。


slf4j-jdk14-2.0.10.jar: SLF4J绑定JUL(java14之后的内置日志框架)日志框架,同时需要JDK14。


slf4j-nop-2.0.10.jar: 应用不绑定任何日志框架的情况下的SLF4J的默认NOP实现,啥也没干,只是在应用运行进行日志框架绑定的时候不报错。


slf4j-simple-2.0.10.jar: SLF4J自己实现的一个简单的、轻量级日志框架,应该没人用。


slf4j-jcl-2.0.10.jar: 绑定Apache Commons Logging。


logback-classic-xxx.jar: logback日志框架,同时需要logback-core-xxx.jar,可直接支持SLF4J。


将以上jar包放入或移除当前应用(或者通过pom文件)后,就可以轻而易举的实现日志框架的更换,不需要你动一行代码,这就是SLF4J的魔力。

不绑定

如果不引入任何日志框架、代码中又使用了SLF4J的话,会是什么情况?

如果版本是SLF4J 1.6.0 之前的版本,没有加载任何日志框架的情况下,SLF4J在绑定日志框架的时候会抛出异常:NoClassDefFoundError 。因为绑定日志框架的时候找不到org.slf4j.impl.StaticLoggerBinder类。

之后的、SLF4J2.0之前的版本,控制台会打印:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

SLF4J2.0以上,控制台会打印:

SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.

SLF4J 1.6.0之后,如果你的项目并没有加载日志框架,SLF4J提供了一个日志哑实现,叫no-operation (NOP) logger implementation,这个NOP其实就是个假把式,所有的日志打印操作都不会得到任何输出,NOP啥都不干。

桥接API

如果你的已有应用没有使用SLF4J、而是使用JCL或log4j等JAVA日志框架,SLF4J非常贴心的为你提供了两种方案、让你能够以最小代价将as-is系统的日志框架切换到SLF4J上来:

  1. Bridging legacy APIs:桥接API
  2. slf4j-migrator:代码迁移工具

桥接API图:
在这里插入图片描述
通过桥接API,允许你将既有系统的基于log4j、JCL、JUL等日志框架的系统迁移到SLF4J上来。原理是:jcl-over-slf4j.jar替换原有的commons-logging.jar,应用中对原来的JCL日志框架的调用接口都被替换成了jcl-over-slf4j.jar中的桥接接口,jcl-over-slf4j.jar桥接接口会将日志接口API重新定向到SLF4J中、从而纳入到SLF4J体系中来。

slf4j-migrator代码迁移工具是直接对你的代码动手术的,支持JCL、log4j、JUL的迁移:
在这里插入图片描述

Spring Boot默认日志框架

Spring Boot的默认日志框架是在spring-boot-starter中指定的,spring-boot-starter中包含了spring-boot-starter-logging:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
      <version>3.1.4</version>
      <scope>compile</scope>
    </dependency>

而spring-boot-starter-logging中引入了logback:

<dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.4.11</version>
      <scope>compile</scope>
    </dependency>

所以,Spring Boot的默认日志框架是logback。

如果想要替换默认的日志框架,比如换成log4j2,首先需要在pom文件中加入依赖:

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>2.0.10</version>
        </dependency>

之后直接启动应用,发现:
在这里插入图片描述
这个信息是在应用系统的早期就打印出来了,说明日志框架的加载在Spring Boot启动过程中是比较早的。

然后,logback也可以正常工作了(代码中加入log可以检查一下底层日志框架到底是啥):

    public HelloWorldController(){
        log.info("---1=2---");
        log.info(log.getClass().getName());
    }

在这里插入图片描述
但是我们想要替换logback为log4j2的目标却没有实现!

原因在上面第一张图里已经说了,SLF4J在classpath下发现了两个provider(我们用的是SLF4J2.x版本,通过provider绑定底层日志框架)。

发现两个provider的话,SLF4J会依赖JVM随机绑定一个,我们测试的这个案例是绑定了logback。

怎么能让它绑定log4j呢?

其实通过SLF4J的源码发现了一种方法,就是指定环境变量:
在这里插入图片描述

System property for explicitly setting the provider class. If set and the provider could be instantiated, then the service loading mechanism will be bypassed.
Since:2.0.9

如果指定了这个环境变量,SLF4J的加载机制就会被跳过而直接加载指定的provider,比如:
在这里插入图片描述
从代码看这个设置也是应该能生效的:

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

        // retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
        // loaded the present class to search for services
        final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();

        SLF4JServiceProvider explicitProvider = loadExplicitlySpecified(classLoaderOfLoggerFactory);
        if(explicitProvider != null) {
            providerList.add(explicitProvider);
            return providerList;
        }

loadExplicitlySpecified方法:

static SLF4JServiceProvider loadExplicitlySpecified(ClassLoader classLoader) {
        String explicitlySpecified = System.getProperty(PROVIDER_PROPERTY_KEY);
        if (null == explicitlySpecified || explicitlySpecified.isEmpty()) {
            return null;
        }
        try {
            String message = String.format("Attempting to load provider \"%s\" specified via \"%s\" system property", explicitlySpecified, PROVIDER_PROPERTY_KEY);
            Util.report(message);
            Class<?> clazz = classLoader.loadClass(explicitlySpecified);
            Constructor<?> constructor = clazz.getConstructor();
            Object provider = constructor.newInstance();
            return (SLF4JServiceProvider) provider;
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            String message = String.format("Failed to instantiate the specified SLF4JServiceProvider (%s)", explicitlySpecified);
            Util.report(message, e);
            return null;
        } catch (ClassCastException e) {
            String message = String.format("Specified SLF4JServiceProvider (%s) does not implement SLF4JServiceProvider interface", explicitlySpecified);
            Util.report(message, e);
            return null;
        }
    }

如果指定了这个系统参数的话,就直接通过classloader实例化这个provider…但是确实没有测试成功,暂时没找到原因。

我们还有另外一个启用log4j2、停用logback的方案,pom文件排除logback:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

之后刷新pom,发现logback的引用已经消失了。
刷新前:
在这里插入图片描述
刷新后:
在这里插入图片描述

启动应用:
在这里插入图片描述
就会发现Spring Boot项目使用log4j就没有用logback那么舒服了,log4j是需要配置的。随便放一个log4j.properties配置文件就可以正常工作了。

@Slf4j注解

@Slf4j是lombok的一个注解,所以你就能知道他其实没啥,语法糖而已,帮着你在当前类生成一个:

private static final org.slf4j.Logger log =org.slf4j.LoggerFactory.getLogger(Youclass.class);

Ohhh…OK

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

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

相关文章

C++多态(+多态原理超详解哦)

C多态 引言定义及实现多态的条件虚函数与虚函数的重写接口继承与实现继承函数重载&#xff0c;隐藏&#xff0c;重写的区别 抽象类多态的原理虚函数表&#xff08;虚表&#xff09;动态绑定与静态绑定 总结 引言 在生活中不乏这样的例子&#xff1a;成人与儿童在买票时会有不同…

Java基础之并发篇(二)

1、前言 本篇主要基于Java基础之并发篇&#xff08;一&#xff09;继续梳理java中关于并发相关的基础只是。本篇基于网络整理&#xff0c;和自己编辑。在不断的完善补充哦。 2、synchronized 的原理是什么? synchronized是 Java 内置的关键字&#xff0c;它提供了一种独占的…

vue中组件的传递取值方式总结

vue中组件的传递取值方式总结 目录概述需求&#xff1a; 设计思路实现思路分析1.父子传参2.vue 子组件接收数据进行渲染或处理3.父组件通过v-on监听自定义事件并取得传递的值4.ref引用子组件5.通过provide/inject传值6.attrs和listeners7.通过vuex进行全局状态管理8.Vue mixin9…

快速查询自己哔哩哔哩账号的注册时间

登录自己哔哩哔哩 访问下面地址 https://member.bilibili.com/x2/creative/h5/calendar/event?ts0 打开后&#xff0c;在网页中查找“jointime”&#xff0c;join time&#xff0c;“加入时间”&#xff0c; 如下图&#xff0c;“jointime”冒号后面的一串数字&#xff0c;是…

汽车ECU的虚拟化技术初探(四)--U2A内存管理

目录 1.内存管理概述 2. 内存保护功能 2.1 SPID 2.2 Slave Guard 3.小结 1.内存管理概述 为了讲清楚U2A 在各种运行模式、特权模式下的区别&#xff0c;其实首先应该搞清楚不同模式下可以操作的寄存器有哪些。 但是看到这个寄存器模型就头大。 再加上之前没有研究过G4MH…

网页设计-用户体验

Use Cases (用例) 用例是用户如何在网站上执行任务的书面描述&#xff0c;从用户的角度描述了系统响应请求时的行为。每个用例都是用户实现目标的一系列简单的步骤。简言之&#xff0c;用例是一种用于描述系统如何满足用户需求的方法。 用例的好处 1. 明确需求&#xff1a; Use…

掌握WPF控件:熟练常用属性(二)

WPF布局常用控件&#xff08;二&#xff09; Calendar 用于日期选择的控件。它提供了一个可视化的界面&#xff0c;可以通过它来选择特定的日期。 常用属性描述DisplayMode用来设置Calendar的显示模式&#xff0c;有三种可选值&#xff1a;默认Month&#xff08;月&#xff…

详解SpringCloud微服务技术栈:认识微服务、服务拆分与远程调用

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;首期文章 &#x1f4da;订阅专栏&#xff1a;微服务技术全家桶 希望文章对你们有所帮助 在此之前&#xff0c;耗时半个月&#x…

高效学习新编程语言的实践指南

学习一门新的编程语言或框架对于新手和有经验的程序员来说都是一个挑战。然而&#xff0c;通过采用一些有效的策略和方法&#xff0c;我们可以快速入门并掌握新的编程工具。本文将从新手和有编程经验两个不同的角度&#xff0c;分享一些实用的建议和技巧&#xff0c;帮助读者在…

Echarts图表如何利用formatter自定义tooltip的内容和样式

在展示多数据图表的时候 有的时候需要图例也展示出一些内容来&#xff0c;例如官方这样子&#xff1a;鼠标悬停的时候展示该点数据 但是&#xff0c;官方提供的样式有时不适用所有的开发场景 我的项目需要实现鼠标悬停在某一点的时候&#xff0c;只展示该条线的数据&#xff0…

【python】进阶--->MySQL数据库(三)

一、修改列的类型长度及约束 alter table 表名 modify 列名 类型(长度) [约束];修改列名 : alter table 表名 change 旧列名 新列名 类型(长度) [约束];二、数据查询语言 查询表中所有的数据 : select * from 表名; 查询表中部分列数据 : select 列名1, 列名2 from 表名;1. …

长亭科技-雷池WAF的安装与使用

目录 1、安装雷池 2、登录雷池 3、简单配置 4、防护测试 5、其他补充 1、安装雷池 在Linux系统上执行如下命令 &#xff08;需要docker环境&#xff0c;提前把docker、docker-compose 装好&#xff09; bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release…

MySQL8.0安装(Linux - centos)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 其他…

企业异地访问办公系统:对比运营商MPLS专线,内网穿透有何优势?

为了实现连锁门店、企业内部各地分支机构ERP、OA、远程监控、自建邮件服务器、智能网络设备等数据传输、互访&#xff0c;使用运营商专线或是采用内网穿透方案&#xff0c;彼此之间究竟有何区别呢&#xff1f; 简单来说&#xff0c;MPLS专线和普通宽带类似是运营商提供的网络租…

【GitHub项目推荐--国外大神复刻暗黑2】【转载】

《暗黑破坏神2》&#xff0c;由顶尖游戏公司暴雪研发&#xff0c;2000 年上市&#xff0c;其资料片 2001 年上市&#xff0c;2D 画面。相信这款游戏已经成为很多人的回忆了&#xff0c;不知道当时是不是也和我一样沉迷于收集套装呢&#xff1f; 这款游戏的剧情设计、画面感都令…

js关闭当前窗口报错Scripts may close only the windows that were opened by them

文章目录 一、问题二、原因三、解决四、最后 一、问题 在Chrome浏览器中调用window.close()关闭当前页面时浏览器控制台报出 Scripts may close only the windows that were opened by them. 且无法关闭当前页面。 先上结论&#xff1a;不是通过JS打开的浏览器标签&#xff0c…

HTML--CSS--边框、列表、表格样式

边框样式 属性&#xff1a; border-width 边框宽度 border-style 边框外观 border-color 边框颜色 需要同时设定三个属性 border-width 边框宽度 取值为像素值 border-style 边框样式 none 无样式 dashed 虚线 solid 实线 border-color 边框颜色 如示例&#xff1a; 为div设…

知识点整理[(GraphGeo)RELATED WORK]

2 RELATED WORK 2.1 IP Geolocation 问题一:IP定位预测方法之一:Data mining-based methods 回答: 依赖于在公开的资源中挖掘位置线索来对目标IP(target IP)进行地理定位。其中一些数据分析了来自与IP相关的数据库,如WHOIS数据库和DNS的数据。 (1)例如,Moore等…

如何简单的使用文心一言(高级版)(中国版ChatGPT)

文心一言API高级版使用 一、百度文心一言API(高级版)二、使用步骤1、接口2、请求参数3、请求参数示例4、接口 返回示例5、智能生成API代码 三、 如何获取appKey和uid1、申请appKey:2、获取appKey和uid 四、重要说明 一、百度文心一言API(高级版) 基于百度文心一言语言大模型的…

NLP论文阅读记录 - WOS | 2022 使用语言特征空间的抽象文本摘要的神经注意模型

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作三.本文方法3.1 总结为两阶段学习3.1.1 基础系统 3.2 重构文本摘要 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Neural A…