SpringBoot日志详解

一、日志分级

最开始的日志分级是由Syslog的开发者Eric Allman在1981年提出的。之后,这个级别分级系统被广泛应用于各种领域的日志记录和信息处理中。下面我们就来介绍下常用的日志等级

1、 TRACE

是最低级别的日志记录,用于输出最详细的调试信息,通常用于开发调试目的。在生产环境中,应该关闭 TRACE 级别的日志记录,以避免输出过多无用信息。

2、 DEBUG

是用于输出程序中的一些调试信息,通常用于开发过程中。像 TRACE 一样,在生产环境中应该关闭 DEBUG 级别的日志记录。

3、 INFO

用于输出程序正常运行时的一些关键信息,比如程序的启动、运行日志等。通常在生产环境中开启 INFO 级别的日志记录。

4、 WARN

是用于输出一些警告信息,提示程序可能会出现一些异常或者错误。在应用程序中,WARN 级别的日志记录通常用于记录一些非致命性异常信息,以便能够及时发现并处理这些问题。

5、 ERROR

是用于输出程序运行时的一些错误信息,通常表示程序出现了一些不可预料的错误。在应用程序中,ERROR 级别的日志记录通常用于记录一些致命性的异常信息,以便能够及时发现并处理这些问题。

当然,除了这五种级别以外,还有一些日志框架定义了其他级别,例如 Python 中的 CRITICAL、PHP 中的 FATAL 等。CRITICAL 和 FATAL 都是用于表示程序出现了致命性错误或者异常,即不可恢复的错误。当然,对于我们今天要说的内容,知道上述五种日志等级就够了。

二、常用日志插件

1. Log4j(1999年诞生)

Log4j 是Java领域中最早的流行日志框架之一。它由Ceki Gülcü开发,并后来由Apache软件基金会接管。Log4j 提供了灵活的配置选项、多种输出目的地、日志级别和分层日志体系。尽管Log4j 1在其时代取得了巨大的成功,但在性能和某些功能方面存在限制,因此后来演化为Log4j 2。

插播一条,如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 1万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的几百套,精品系列教程,免费提供。

2. SLF4J(2004年诞生)

严格来说,SLF4J(Simple Logging Facade for Java)并不算一个插件,而是Ceki Gülcü开发的一个日志门面接口。它为Java应用程序提供了统一的日志抽象,使开发人员可以使用一致的API进行日志记录,而不需要直接依赖于特定的日志实现。SLF4J 可以与多种底层日志框架(如Logback、Log4j 2、java.util.logging等)结合使用。

3. Logback(2009年诞生)

Logback 是Ceki Gülcü开发的日志框架,他也是Log4j的作者。Logback 是Log4j 1的后续版本,旨在提供更高性能、更灵活的配置和现代化的日志解决方案。Logback 支持异步日志记录、多种输出格式、灵活的配置以及与SLF4J紧密集成。

4. Log4j 2(2014年诞生)

Log4j 2 是Apache软件基金会开发的Log4j的下一代版本。它引入了许多新特性,如异步日志记录、插件支持、丰富的过滤器等,旨在提供更好的性能和灵活性。Log4j 2 在设计上考虑了Log4j 1的局限性,并且支持多种配置方式。

5. 小故事

不难注意到,一个有意思的小故事是,前三款日志插件都是*Ceki Gülcü开发的,但 Log4j 2 并不是*,虽然现在有很多人以为log4j2也是他写的,但我们在github上可以看到其个人说明 “Unaffiliated with log4j 2.x.” (与 log4j 2.x 无关),所以log4j2 和 logback 都自称是log4j 的后续版本,到底谁才算正统续作呢?这就留给各位读者自己玩味了

image-20231129194142415

三、日志框架发展史

1.原始sout

从前有个老程序员张三,使用了jdk1.3,每次打印日志都是:

System.out.println(“”)追踪 ; 异常 try catch{ System.out.println(“”) } 输出一些关键变量

System.out.println(“”)

后来项目上线,部署了大量的System.out.println(“”) 。

某天,项目发生异常 —>张三需要在服务确认异常信息, 非常郁闷? 追踪

信息 ,记录在文本, 去服务器追踪就不会查无对症,

  1. logUtil loginfo 替换 . 就可以顺利追踪到了。 不会立马反馈 , 而且随着项目运营越来越多用户请求量越来越大, 1天1G, 2G
  2. 日志信息 按天迭代, 2020-10-01.log ,… 按物理大小迭代 2020-10-01-20M.log
  3. 用户出现异常loginfo(错误信息,等级) ,能不能给我马上发送邮件。
  4. 能不能记录日志的时候按等级来区分 追踪1 信息2 调试3 异常 4 , 1天400\500M
  5. i/o 异步 … 自由控制格式 …

2.两大日志框架

a.张三开源log4j

张三开源 log4j . 受到广大开发者欢迎。 log4j simple log4j nop…。 被apatch基金会收纳

b.JDK官方的jul

jdk官方 sun , 开发出来了一个日志框架 jul java.util.logging . (jdk看不起张三的log4j,所以自己开发了一个jul库,为时已晚)


3.两大日志门面

市面上的日志框架非常的混乱, 一个项目 一个模块 log4j , 一个模块 jul , 一个jboss-logging.

a.JDK提供的JCL

jdk -> 日志门面 JCL jakarta Commons Logging JCL ( jdk下的一个小组开发的一个日志门面;不实现日志功能,整合日志的) spring 使用的就是jcl

b. 张三提供slf4j

张三 发现并不好用, 张三离开了apatch(apatch安于现状), 独自开发日志门面 slf4j . ( 比jcl要好,因为jcl是通过ClassLoader类加载器主动找,并且如果使用自定义类加载器可能会导致内存溢出;不实现日志功能,整合日志的)

适配器、桥接器(log4j要在slf4j上使用需要通过桥接器)

如果要通过slf4j实现某一个日志框架,比如实现log4j,中间必须要有一个桥接器

这种方式要比jcl要好,jcl是通过ClassLoader去找。slf4j要比jcl性能好


模块1 官方 JCL门面 jul (两个都是jdk的亲儿子)

适配器

模块2 开源 slf4j 桥接器 log4j


4.新增的两大日志框架

apatch log4j2 性能 log4j高出好多倍

张三 logback 性能 log4j高出好多倍

日志实现日志门面
log4j 淘汰 [张三]JCL [jdk官方]
jul java.util.logging 别的 [jdk官方]SLF4J [张三]
log4j2 [apatch]
logback [张三]

springboot使用的是logback和slf4j

5.适配器和桥接器

适配器(Adapter)是指一种能够将不同日志实现的API适配到SLF4J API的类或方法,使得开发人员在使用SLF4J API时,无需关心底层使用的是何种日志实现框架,可以方便地切换日志实现。常见的SLF4J适配器有:

  • slf4j-log4j12:用于将Log4j 1.x适配到SLF4J API
  • slf4j-jdk14:用于将JDK logging适配到SLF4J API
  • slf4j-nop:用于对SLF4J API进行空实现,仅用于测试和调试。

(概念:当项目使用了两种日志框架,可以使用适配器进行统一)

例:一个项目同时使用了jul+JCL和 log4j+slf4j的日志框架,现在需要统一,即可以将jul或者JCL通过导入适配器依赖的形式,整合到slf4j上面来实现。


桥接器(Bridge)是指一种用于在SLF4J API和其他日志实现之间进行桥接的东西。通常在使用SLF4J API时,为了能够支持特定的日志实现,需要使用桥接器将SLF4J API转换为具体实现框架的API。常见的SLF4J桥接器有:

  • log4j-over-slf4j:用于将Log4j 1.x的使用转换为SLF4J API;
  • jul-to-slf4j:用于将JDK logging转换为SLF4J API;
  • jcl-over-slf4j:用于将Jakarta Commons Logging转换为SLF4J API;
  • logback-classic:用于将Logback框架转换为SLF4J API;

(概念:日志框架要在日志门面使用的桥梁)

也就是说,当引入了logback日志框架和slf4j日志门面的maven依赖后,还需要引入对应的桥接器依赖,才能使日志运行。

桥接器只能有一个(slf4j只能运行一个)。

6.发展历程

早年,你工作的时候,在日志里使用了log4j框架来输出,于是你代码是这么写的

import org.apache.log4j.Logger; 
\\省略 
Logger logger = Logger.getLogger(Test.class); 
logger.trace("trace"); 
\\省略

但是,岁月流逝,sun公司对于log4j的出现内心隐隐表示嫉妒。于是在jdk1.4版本后,增加了一个包为java.util.logging,简称为jul,用以对抗log4j。于是,你的领导要你把日志框架改为jul,这时候你只能一行行的将log4j的api改为jul的api,如下所示

import java.util.logging.Logger; 
\\省略 Logger 
loggger = Logger.getLogger(Test.class.getName());  
logger.finest("finest"); 
\\省略

可以看出,api完全是不同的。那有没有办法,将这些api抽象出接口,这样以后调用的时候,就调用这些接口就好了呢?

这个时候jcl(Jakarta Commons Logging)出现了,说jcl可能大家有点陌生,讲commons-logging-xx.jar组件,大家总有印象吧。JCL 只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对 JCL 接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。

JCL可以实现的集成方案如下图所示

image-20231129194415618

jcl默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用jul(jdk自带的) 实现,再没有则使用jcl内部提供的SimpleLog 实现。

于是,你在代码里变成这么写了

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
\\省略 
Log log =LogFactory.getLog(Test.class); 
log.trace('trace'); 
\\省略

至于这个Log具体的实现类,JCL会在ClassLoader中进行查找。这么做,有三个缺点,缺点一是效率较低,二是容易引发混乱,三是在使用了自定义ClassLoader的程序中,使用JCL会引发内存泄露。

JCL动态查找机制进行日志实例化,执行顺序为:commons-logging.properties---->系统环境变量------->log4j—>jul—>simplelog---->nooplog

于是log4j的作者觉得jcl不好用,自己又写了一个新的接口api,那么就是slf4j。关于slf4j的集成图如下所示

image-20231129194802128

理解slf4j日志门面了吗,它跟jcl机制不一样。 它就相当于这个游戏机, 我本身没有游戏, 只提供一个运行游戏的平台(门面)

要运行哪个游戏我不管, 你给我放哪块光盘我就运行哪个游戏。 JCL 背靠JDK 自带jul .

image-20231129194808087

四、外观模式与SLF4J

在讲解更多插件详情之前,我们先来看看使用最多的SLF4J ,我们前面说了 SLF4J(Simple Logging Facade for Java)是Ceki Gülcü开发的一个日志门面接口,那么很显然这里就用到了门面模式(即Facade 或 外观模式),笔者比较习惯说成是外观模式,后续就称为外观模式。

image-20231129194823857

1. 外观模式

定义:外观模式是一种结构型设计模式,它提供了一个简单的接口,封装了底层复杂的子系统,使得客户端可以更方便地使用这个子系统目的:外观模式的目的是隐藏底层系统的复杂性,降低访问成本。

如果说看定义有些抽象,那我们可以以生活中的例子来说,我们都知道现在越来越流行智能家居,也就是家庭内装了很多智能家电,从电视、空调、到廊灯甚至窗帘都是智能的。这类家庭往往会有一个控制中心,我们不需要手动去开电视,只需要对着控制中心说:“小A小A,帮我打开电视,音量调到30%”,电视就会应声打开并调节音量

那么这样的话,我们不需要知道怎么开电视,怎么调音量。通通都能用最简单的话语来调节。同理,现在手机上的拍照功能:感光度,对焦,白平衡这些细节都给你自动完成了,所以这些复杂的内容你现在根本不用管,只需要猛按拍照键即可。

image-20231129194834232

这就是外观模式的意义,外观模式就是为了隐藏系统的复杂性而设计出来的,让客户端只对接触到一个外观类,而不会接触到系统内部的复杂逻辑

2. SLF4J 的诞生

在早期使用日志框架时,应用程序通常需要直接与具体的日志框架进行耦合,这就导致了以下几个问题:

1、 代码依赖性

应用程序需要直接引用具体的日志框架,从而导致代码与日志框架强耦合,难以满足应用程序对日志框架的灵活配置。

2、 日志框架不统一

在使用不同的日志框架时,应用程序需要根据具体的日志框架来编写代码,这不仅会增加开发难度,而且在多种日志框架中切换时需要进行大量的代码改动。

3、 性能问题

在日志输出频繁的情况下,由于日志框架的实现方式和API设计不同,可能会导致性能问题。

为了解决这些问题,SLF4J提供了一套通用的日志门面接口,让应用程序可以通过这些接口来记录日志信息,而不需要直接引用具体的日志框架。这样,应用程序就可以在不同的日志框架之间进行灵活配置和切换,同时还可以获得更好的性能表现。所以,我强烈建议各位使用SLF4J, 而不是直接对接某个具体的日志框架

3. SLF4J 的使用

首先,我们需要在工程内引入包,但是如果你用了springboot,各种 spring-boot-starter 启动器已经引用过了,所以引用前最好确认下:

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

然后在我们要打印日志的类里加上一行 :private static final Logger logger = LoggerFactory.getLogger(XXXX.class); 即可使用,如下:

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
public class MyClass {       
    private static final Logger log = LoggerFactory.getLogger(MyClass.class);    
    //...    
    public static void main(String[] args) {        
        log.info("This is an info message.");    
    } 
}

如果我们引用了lombok的话,也可以使用lombok的注解@Slf4j 代替上面那句话来使用 SLF4J ,如下:

import lombok.extern.slf4j.Slf4j; 
@Slf4j 
public class MyClass {       
    public static void main(String[] args) {        
        log.info("This is an info message.");    
    } 
}

但是,我们都知道SLF4J仅仅是个门面,换句话说,仅有接口而没有实现,如果此刻我们直接运行,打印日志是没有用处的

image-20231129194949554

所以,我们如果要运行,我们必须要给 SLF4J 安排上实现,而目前最常用的就是 logback 和 log4j2 了,就让我们接着往下看

4.Slf4j与其他各种日志组件的桥接说明

jar包名说明
slf4j-log4j12-1.7.13.jarLog4j1.2版本的桥接器,你需要将Log4j.jar加入Classpath。
log4j-slf4j-impl.jarLog4j2版本的桥接器,还需要log4j-api.jar log4j-core.jar
slf4j-jdk14-1.7.13.jarjava.util.logging的桥接器,Jdk原生日志框架。
slf4j-nop-1.7.13.jarNOP桥接器,默默丢弃一切日志。
slf4j-simple-1.7.13.jar一个简单实现的桥接器,该实现输出所有事件到System.err. 只有Info以及高于该级别的消息被打印,在小型应用中它也许是有用的。
slf4j-jcl-1.7.13.jarJakarta Commons Logging 的桥接器. 这个桥接器将Slf4j所有日志委派给Jcl。
logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar)Slf4j的原生实现,Logback直接实现了Slf4j的接口,因此使用Slf4j与Logback的结合使用也意味更小的内存与计算开销

如图所示,应用调了sl4j-api,即日志门面接口。日志门面接口本身通常并没有实际的日志输出能力,它底层还是需要去调用具体的日志框架API的,也就是实际上它需要跟具体的日志框架结合使用。由于具体日志框架比较多,而且互相也大都不兼容,日志门面接口要想实现与任意日志框架结合可能需要对应的桥接器,上图红框中的组件即是对应的各种桥接器!

我们在代码中需要写日志,变成下面这么写

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
//省略 
Logger logger = LoggerFactory.getLogger(Test.class); 
// 省略 
logger.info("info");

在代码中,并不会出现具体日志框架的api。程序根据classpath中的桥接器类型,和日志框架类型,判断出logger.info应该以什么框架输出!注意了,如果classpath中不小心引了两个桥接器,那会直接报错的!

因此,在阿里的开发手册上才有这么一条

强制:应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架 SLF4J 中的 API 。使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一。

  • 如果要将jcl或jul 转slf4j呢?

image-20231129195018097

ok,至此,基础知识完毕,下面是实战!

五、双雄之争

其实关于 Logback 和 Log4j 2,网络上有很多评测,就不需赘述了,主要是围绕性能方面的,从目前大家的反馈看,Log4j 2 晚出现好几年,还是有后发优势的,性能会比 Logback 好。

当然,Logback 本身性能也很强,对于大多数场景,完全是够用的,而且配置比较直观,是spring-boot 默认使用的日志插件。

image-20231129195040512

所以,选谁都可以,如果不想费神,可以直接使用spring-boot自带的Logback,如果对日志性能要求很高,使用log4j2更保险,我们接下来分别介绍两者。

1. Logback

1. 引用

由于Logback 为 spring-boot 默认日志框架,所以无需再引用,但对于非spring - boot 项目,可以做如下引用

<dependency>    
    <groupId>ch.qos.logback</groupId>    
    <artifactId>logback-classic</artifactId>    
    <version>1.2.12</version> 
</dependency>

Logback 的核心模块为 logback-classic,它提供了一个 SLF4J 的实现,兼容 Log4j API,可以无缝地替换 Log4j。它自身已经包含了 logback-core 模块,而 logback-core,顾名思义就是 logback 的核心功能,包括日志记录器、Appender、Layout 等。其他 logback 模块都依赖于该模块

2. 配置

logback 可以通过 XML 或者 Groovy 配置。下面以 XML 配置为例。logback 的 XML 配置文件名称通常为 logback.xml 或者 logback-spring.xml(在 Spring Boot 中),需要放置在 classpath 的根目录下,

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--定义日志文件的存储地址,使用Spring的属性文件配置方式-->
    <springProperty scope="context" name="log.home" source="log.home" defaultValue="logs"/>

    <!--定义日志文件的路径-->
    <property name="LOG_PATH" value="${log.home}"/>

    <!--定义控制台输出-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%-5relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--定义 INFO 及以上级别信息输出到控制台-->
    <root level="INFO">
        <appender-ref ref="console"/>
    </root>

    <!--定义所有组件的日志级别,如所有 DEBUG-->
    <logger name="com.example" level="DEBUG"/>

    <!-- date 格式定义 -->
    <property name="LOG_DATEFORMAT" value="yyyy-MM-dd"/>

    <!-- 定义日志归档文件名称格式,每天生成一个日志文件 -->
    <property name="ARCHIVE_PATTERN" value="${LOG_PATH}/%d{${LOG_DATEFORMAT}}/app-%d{${LOG_DATEFORMAT}}-%i.log.gz"/>

    <!--定义文件输出,会根据定义的阈值进行切割,支持自动归档压缩过期日志-->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--定义日志文件切割的阈值,本例是 50MB-->
            <maxFileSize>50MB</maxFileSize>
            <!--定义日志文件保留时间,本例是每天生成一个日志文件-->
            <fileNamePattern>${ARCHIVE_PATTERN}</fileNamePattern>
            <maxHistory>30</maxHistory>
            <!-- zip 压缩生成的归档文件 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!-- 删除过期文件 -->
            <cleanHistoryOnStart>true</cleanHistoryOnStart>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--定义 ERROR 级别以上信息输出到文件-->
    <logger name="com.example.demo" level="ERROR" additivity="false">
        <appender-ref ref="file"/>
    </logger>

    <!--异步输出日志信息-->
    <appender name="asyncFile" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="file"/>
    </appender>

    <!--定义INFO及以上级别信息异步输出到文件-->
    <logger name="com.example" level="INFO" additivity="false">
        <appender-ref ref="asyncFile"/>
    </logger>

</configuration>

其中,主要包括以下配置:

springProperty

定义了 log 文件的存储路径,可以通过 Spring 的属性文件配置方式进行设置,如果没有配置则默认存储在 logs 目录下。

appender

定义了日志输出的目标,这里包括了控制台输出和文件输出两种,具体可以根据需求进行配置。

root

定义了默认的日志级别和输出目标,默认情况下,INFO 级别以上的日志信息会输出到控制台,可以根据实际需求进行修改。

logger

定义了不同组件的日志级别和输出目标,例如,这里定义了 com.example 这个组件的日志级别为 DEBUG,而 com.example.demo 这个组件的日志级别为 ERROR,并将其输出到文件中。

rollingPolicy

定义了日志文件的切割规则和归档策略,此处定义了日志文件每个 50MB 进行切割,每天生成一个日志文件,并且压缩和删除过期文件,最多保留 30 天的日志文件。

encoder

定义了日志信息的输出格式,具体的格式可以自行定义。

asyncAppender

定义了异步输出日志的方式,对于高并发时,可以使用异步输出来提高系统的性能。

discardingThreshold

定义了异步输出队列的阈值,当队列中的数据量超过此值时,会丢弃最早放入的数据,此处设置为 0 表示队列不会丢弃任何数据。

queueSize

定义了异步输出队列的大小,当队列满时,会等待队列中的数据被消费后再将数据放入队列中,此处设置为 256。

3. 演示

我们新建一个普通工程(非spring工程),引用Logback后,把上述配置文件复制进logback.xml,然后将工程结构设置成如下模式

image-20231129195114335

其中两个类的代码如下:

public class Main {
     
    private static final Logger log = LoggerFactory.getLogger(Main.class);
    public static void main(String[] args) {
     
        log.trace("This is a Main trace message.");
        log.debug("This is a Main debug message.");
        log.info("This is a Main info message.");
        log.warn("This is a Main warn message.");
        log.error("This is a Main error message.");
        Slave.main(args);
    }
}

public class Slave {
   
    private static final Logger log = LoggerFactory.getLogger(Slave.class);
    public static void main(String[] args) {
   
        log.trace("This is a Slave trace message.");
        log.debug("This is a Slave debug message.");
        log.info("This is a Slave info message.");
        log.warn("This is a Slave warn message.");
        log.error("This is a Slave error message.");
    }
}

我们想实现这样的效果,首先日志要同时 输出到控制台 及 日志文件,且不同层级的代码,输出的日志层级也不同。那么我们可以对上述的xml做出一些调整:

1、 因为是非Spring项目,所以springProperty这样的标签就不要用了,我们直接写死一个日志文件地址即可;

<!--定义日志文件的路径-->
<property name="LOG_PATH" value="D://logs/log"/>

2、 去掉原有的那些root、logger标签,我们自己新建两个logger,用于两个不同的层级我们想里层输出debug级别,外层输出info级别,我们可以这么设置并且同时输出到控制台及日志文件;

<logger name="com.zhanfu" level="INFO">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>
<logger name="com.zhanfu.child" level="DEBUG" additivity="false">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>

3、 当我们运行Main.main的时候,就可以得到以下日志,slave能输出debug级别,Main只能输出info及以上级别;

image-20231129195201274

4. 细节点

其实我们上面的演示,有两个细节点,需要注意一下。一个就是我们的

 <logger name="com.zhanfu.child" level="DEBUG" additivity="false">

使用了一个 additivity=“false” 的属性,这其实是因为 logger 这个标签在锁定某个目录时,可能会发生层级关系。比如我们的两个 logger, 一个针对的目录是 com.zhanfu 另一个是 com.zhanfu.child ,后者就会被前者包含。当我们的 com.zhanfu.child.Slave 打印日志时,当然会使用后者(更精确)的设置,但前者的设置还使用吗?就依赖于 additivity=“false”,此处如果我们把 additivity=“false”(该属性默认值为true)去掉,再来打印日志

image-20231129195237438

就会发现,Slave 的日志打了两遍,而且连 debug 级别的都打了两遍,我们可以把这种逻辑理解为继承,子类执行一遍,父类还能在执行一遍,但 leve 属性还是会采用子类而非父类的。

另一点就是我们把 root 标签删除了,root 其实是一个顶级的 logger , 其他的logger都可以视为它的子类,如果那些logger存在没涵盖的地方,或其没有指定 additivity=“false”,那最后root的设置就会被使用。比如我们将设置改为如下:

<logger name="com.zhanfu" level="INFO">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>
<logger name="com.zhanfu.child" level="DEBUG">
    <appender-ref ref="file" />
    <appender-ref ref="console"/>
</logger>
<root level="WARN">
    <appender-ref ref="console"/>
</root>

结果控制台的输出日志,Main会重复两次,Slave 会重复三次,如下

image-20231129195250437

但是因为我们的 root 只配置了控制台输出,所以日志文件里还是不会变的

image-20231129195254766

2. Log4j 2

1. 引用

对于spring-boot项目,除了引用 Log4j 2 我们还需要先剔除 Logback 的引用,对于普通项目,我们只需直接引用即可。但注意我们的原则,通过 SLF4J 来使用 Log4j2,所以引用下面这个包

普通maven项目
  • 如果是普通的Maven项目则需引入如下依赖
<properties>
    <log4j2.version>2.13.3</log4j2.version>
</properties>

<!-- 日志框架 slf4j+log4j2-->
<!-- slf4j依赖 -->
<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>
    <!-- log4j2和slf4j桥接依赖-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>${log4j2.version}</version>
        <scope>test</scope>
    </dependency>
    <!-- log4j2依赖-->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>${log4j2.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>${log4j2.version}</version>
    </dependency>
</dependencies>

其内包含 Log4j2 的实现,和 SLF4J 的 API,如下:

image-20231129195336429

SpringBoot项目
  • 如果是SpringBoot项目,可以引入下面的依赖
<!-- spring-boot-starter 启动器已经引用过了slf4j -->
<!-- 使用lombok的注解@Slf4j  -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>
<!-- log4j2依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.7.13</version>
    <exclusions><!-- 去掉springboot默认配置 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.13</version>
    <exclusions><!-- 去掉springboot默认配置 -->
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2. 配置

Log4j2 的配置逻辑和 logback 是类似的,只有些细节不同,比如Logger 的首字母大写等等,最后我们写下这样一个 log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" monitorInterval="30">
  <Properties>
    <Property name="logPath">logs</Property>
  </Properties>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n" />
    </Console>
    <RollingFile name="File" fileName="${logPath}/example.log"                 
        filePattern="${logPath}/example-%d{yyyy-MM-dd}-%i.log">
      <PatternLayout pattern="%d{ISO8601} [%t] %-5level %logger{36} - %msg%n"/>
      <Policies>
        <SizeBasedTriggeringPolicy size="10 MB"/>
      </Policies>
      <DefaultRolloverStrategy max="4"/>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Logger name="com.zhanfu.child" level="DEBUG">
      <AppenderRef ref="File"/>
      <AppenderRef ref="Console"/>
    </Logger>
    <Logger name="com.zhanfu" level="INFO">
      <AppenderRef ref="File"/>
      <AppenderRef ref="Console"/>
    </Logger>
    <Root level="WARN">
      <AppenderRef ref="Console" />
    </Root>
  </Loggers>
</Configuration>

Properties

部分定义了一个 logPath 属性,方便在其他地方引用。

Appenders

定义了两个 Appender:Console 和 RollingFile,分别将日志输出到控制台和文件中。RollingFile 使用了 RollingFileAppender,并设置了日志滚动策略和默认的备份文件数量。

Loggers

定义了三个 Logger:com.zhanfu.child 的日志级别为 DEBUG,com.zhanfu 的日志级别为INFO,Root Logger 的日志级别为 WARN。并指定了两个 Appender:Console 和 File。

3. 演示

由于我们的配置逻辑没变,所以日志的结果还是一样的:

image-20231129195420529

image-20231129195425071

3. 对比

Log4j2和Logback都是Java应用程序中最流行的日志框架之一。它们均具备高度的可配置性和使用灵活性,并提供了一系列有用的功能,例如异步日志记录和日志过滤等。下面从配置遍历性、功能性、性能等方面进行比较和总结。配置遍历性

Logback的配置文件格式相对简单,易于阅读和修改。它支持符号来引用变量、属性和环境变量等。此外,它还支持条件日志记录(根据日志级别、日志记录器名称或时间等),以及滚动文件的大小或日期等。

Log4j2的配置文件格式较复杂,但它在配置文件中提供了大量的选项来控制日志记录。它支持在配置文件中直接声明上下文参数、过滤器、输出器和Appender等,这使得它的配置更加灵活。此外,Log4j2还支持异步日志记录、日志事件序列化和性能优化等。

总体来说,两者都很好地支持了配置遍历性,但Log4j2提供了更多的选项和更高的灵活性。

功能性

Logback提供了一系列基本的日志记录功能,例如异步Appender、滚动文件和GZIP压缩等。它还支持与SLF4J一起使用,可以很容易地与其他日志框架集成。

Log4j2提供了更多的高级功能,例如异步日志记录、性能优化和日志事件序列化等。它还支持Lambda表达式,可以使日志记录器更加简洁和易读。此外,Log4j2还支持Flume和Kafka等大数据处理框架,可以方便地将日志记录发送到这些框架中。

总体来说,Log4j2提供了更多的高级功能,并且可以更好地与大数据处理框架集成。

性能

Logback的性能很好,可以处理高吞吐量的日志记录。它采用了异步记录器,利用了多线程来提高性能。

Log4j2在性能方面更加强大。它使用了异步记录器和多线程,还引入了RingBuffer数据结构和Disruptor库来加速日志事件的传递和处理。这使得它比Logback具有更高的吞吐量和更低的延迟。

综上所述,Log4j2在配置灵活性、功能性和性能方面都比Logback更为强大。但如果需要轻量级的日志框架或者只需要基本的日志记录功能,Logback也是一个不错的选择

但如果我们同时引用了这两者,会报错吗?还是会使用其中的某一个?

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.12</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.13.3</version>
</dependency>

可以看到,SLF4J 发现了系统中同时存在两个插件框架,并最终选择了使用 Logback

image-20231129195459461

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懿所思

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值