忽视日志吃大亏,手把手教你玩转 SpringBoot 日志


一、日志重要吗

程序中的日志重要吗? 在回答这个问题前,笔者先说个事例:

笔者印象尤深的就是去年某个同事,收到了客户反馈的紧急bug。尽管申请到了日志文件,但因为很多关键步骤没有打印日志,导致排查进度很慢,数个小时都没能排查到问题,也无法给出解决对策。导致了客户程序一直阻断,最终产生了不少损失。 事后,经过仔细推敲,成功复现了这个bug,其实是一个很不起眼的数据转换导致的。可因为日志内容的匮乏,排查起来难度很大。其实只要在数据转换前后进行日志输出,这个问题就是一眼的事。但可惜没如果,故事的最后,开发部门还是遭到了客户的投诉,影响到了部门绩效

对于刚学习编程的同学,很多人都对日志满不在乎,我们在做code review的时候,经常发现一些新同学喜欢一个方法写得很长,然后中间的注释和日志都少的可怜。

坦白的说,这是很不好的习惯,这意味着日后方法出了bug,或者需要迭代,要花费大量时间来理清方法的思路。千万别迷信什么“方法名、字段名起的见明知意,就可以不写注释与日志”,那是他们的业务场景不够复杂。以笔者为例,复杂的场景涉及很多公式、奇特的规定,不写注释与日志,后续没人能维护得了

所以请务必记住,日志在开发过程中非常重要。它可以帮助开发人员了解程序中发生了什么,以及在某些情况下为什么会发生错误或异常。通过查看日志,开发人员可以轻松地定位并解决问题,并且可以更好地监控和调整应用程序的性能,在必要时进行故障排除和安全检查

5ea1ccbf43b433a0505ae1c469762d9c.jpeg

二、日志分级

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

  • TRACE

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

  • DEBUG

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

  • INFO

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

  • WARN

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

  • ERROR

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

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

三、常用日志插件

  1. Log4j(1999年诞生)

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

  1. SLF4J(2004年诞生)

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

  1. Logback(2009年诞生)

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

  1. Log4j 2(2014年诞生)

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

  1. 小故事

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

687e83f22104fd8aeb0d88012c498f4a.jpeg

四、外观模式与SLF4J

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

28329be79fd7470e7717c918edac5811.jpeg
1. 外观模式

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

目的:外观模式的目的是隐藏底层系统的复杂性,降低访问成本。

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

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

6cc11a43f1a266fa1a1dfb6cf5d7ebb1.jpeg

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

2. SLF4J 的诞生

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

  • 代码依赖性

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

  • 日志框架不统一

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

  • 性能问题

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

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

3. SLF4J 的使用

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

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

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

import&nbsp;org.slf4j.Logger;
import&nbsp;org.slf4j.LoggerFactory;

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

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

import&nbsp;lombok.extern.slf4j.Slf4j;

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

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

e7bcbb97b9f97d08e5280e5eda11a367.jpeg

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

五、双雄之争

其实关于 Logback 和 Log4j 2,网络上有很多评测,就不需赘述了,主要是围绕性能方面的,从目前大家的反馈看,Log4j 2 晚出现好几年,还是有后发优势的,性能会比 Logback 好。当然, Logback 本身性能也很强,对于大多数场景,完全是够用的,而且配置比较直观,是spring-boot 默认使用的日志插件。

ef0b3609300e05f9f2c0646856659027.jpeg

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

1. Logback
1. 引用

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

<dependency>
&nbsp;&nbsp;&nbsp;&nbsp; <groupId>ch.qos.logback </groupId>
&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>logback-classic </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp; <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&nbsp;version="1.0"&nbsp;encoding="UTF-8"?>
<configuration>
&nbsp;&nbsp;&nbsp;&nbsp; <!--定义日志文件的存储地址,使用Spring的属性文件配置方式-->
&nbsp;&nbsp;&nbsp;&nbsp; <springProperty&nbsp;scope="context"&nbsp;name="log.home"&nbsp;source="log.home"&nbsp;defaultValue="logs"/>

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

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

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

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

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

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

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

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

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

&nbsp;&nbsp;&nbsp;&nbsp; <!--定义INFO及以上级别信息异步输出到文件-->
&nbsp;&nbsp;&nbsp;&nbsp; <logger&nbsp;name="com.example"&nbsp;level="INFO"&nbsp;additivity="false">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <appender-ref&nbsp;ref="asyncFile"/>
&nbsp;&nbsp;&nbsp;&nbsp; </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,然后将工程结构设置成如下模式

525835ea16fb94bee2707eb3354441e2.jpeg

其中两个类的代码如下:

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

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

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

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

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

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

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

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

f907c145a82dca93e76e49a31a0811f8.jpeg
4. 细节点

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

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

使用了一个 additivity="false" 的属性,这其实是因为 logger 这个标签在锁定某个目录时,可能会发生层级关系。比如我们的两个 logger, 一个针对的目录是 com.zhanfu 另一个是 com.zhanfu.child ,后者就会被前者包含。

当我们的 com.zhanfu.child.Slave 打印日志时,当然会使用后者(更精确)的设置,但前者的设置还使用吗?就依赖于 additivity=“false”,此处如果我们把 additivity="false" (该属性默认值为true)去掉,再来打印日志

ab7b64e44abcb9ec1fe47c32798282fd.jpeg

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

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

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

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

806216a337d093dd8aeb9d37f5418d2b.jpeg

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

c0e92909e2d32609d6509e246305a2f6.jpeg
2. Log4j 2
1. 引用

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

<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <groupId>org.apache.logging.log4j </groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <artifactId>log4j-slf4j-impl </artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <version>2.13.3 </version>
</dependency>

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

a729e9f092eeae9e2c99bb63dce2ff3e.jpeg
2. 配置

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

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

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

2315348b0ceba7b7814f9c4bfd869398.jpeg cd2d8c62a22441ca60c5b1b1e8b159d8.jpeg
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也是一个不错的选择

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

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

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

c6fbda3c139e88114f00da73a566903e.jpeg

总结

学习完本文,你应当对现在这几个常用框架的有所了解,并能基础应用了。此次我们没有讲源码,也没有深入的讲其配置及进阶使用,这些我们会在后面慢慢学习。但现在我希望你能知道的是。一定要写好日志,一定要写好日志,一定要写好日志。重要的事情说三遍!这是区别新人和老鸟的一个重要依据,也是让自己排查问题更轻松的不二法门!

另外,现在很多中间件都自己引用了日志插件,我们作为一个整体工程在使用中间件时,要及时发现并解决插件冲突,避免我们自己的日志配置失效,这也是一个程序员该注意的点。

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

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

相关文章

win10 使用cmake + mingw编译LAPACK

参考官网的内容: 适用于视窗的 LAPACK (utk.edu) 第一步下载好源码包: 配置好mingw的路径添加到环境变量中 gcc --version 出现这些就行了. 还有就是吧mingw中的mingw-make.exe重命名为make.exe 下载安装cmake 然后打开 1是源码路径,路径中一定要有CMakeLists.txt如果没…

Java多线程篇(6)——AQS之ReentrantLock

文章目录 1、管程2、AQS3、ReentrantLock3.1、lock/unlock3.1.1、lock3.1.2、unlock 3.2、一些思考 1、管程 什么是管程&#xff1f; 管理协调多个线程对共享资源的访问&#xff0c;是一种高级的同步机制。 有哪些管程模型&#xff1f; hansen&#xff1a;唤醒其他线程的代码…

渗透测试中的前端调试(一)

前言 前端调试是安全测试的重要组成部分。它能够帮助我们掌握网页的运行原理&#xff0c;包括js脚本的逻辑、加解密的方法、网络请求的参数等。利用这些信息&#xff0c;我们就可以更准确地发现网站的漏洞&#xff0c;制定出有效的攻击策略。前端知识对于安全来说&#xff0c;…

Pytorch单机多卡分布式训练

Pytorch单机多卡分布式训练 数据并行&#xff1a; DP和DDP 这两个都是pytorch下实现多GPU训练的库&#xff0c;DP是pytorch以前实现的库&#xff0c;现在官方更推荐使用DDP&#xff0c;即使是单机训练也比DP快。 DataParallel&#xff08;DP&#xff09; 只支持单进程多线程…

3、嵌入式系统的启动过程(BoodLoader)

1、系统启动过程 通电 - > 执行BootLoader - > 加载内核 - > 挂在根文件系统 - > 执行应用程序 Windows的启动过程&#xff1a; 通电 - > 执行BIOS - > 加载WinNT内核 - > 挂在文件系统 - > 执行应用程序 二、嵌入式系统的结构 BootLoader 1、BootL…

ElasticSearch - 基于 DSL 、JavaRestClient 实现数据聚合

目录 一、数据聚合 1.1、基本概念 1.1.1、聚合分类 1.1.2、特点 1.2、DSL 实现 Bucket 聚合 1.2.1、Bucket 聚合基础语法 1.2.2、Bucket 聚合结果排序 1.2.3、Bucket 聚合限定范围 1.3、DSL 实现 Metrics 聚合 1.4、基于 JavaRestClient 实现聚合 1.4.1、组装请求 1…

Tomcat多实例、负载均衡、动静分离

Tomcat多实例部署 安装jdk [rootlocalhost ~]#systemctl stop firewalld.service [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#cd /opt [rootlocalhost opt]#ls apache-tomcat-8.5.16.tar.gz jdk-8u91-linux-x64.tar.gz rh [rootlocalhost opt]#tar xf jdk-8u91-linu…

春招秋招,大学生求职容易遇到哪些问题?

每到毕业季就有大批大学生从校园出来&#xff0c;他们怀抱梦想&#xff0c;希望能做出一番成绩。但现实总归是残酷的&#xff0c;有些人找不到工作&#xff0c;有一些人频繁跳槽&#xff0c;也有一些人最终找到的工作与自己的专业没有一点关系&#xff0c;迷茫好几年才找到方向…

钡铼BL302与PLC:提升酿酒业效率与品质的利器

啤酒是人类非常古老的酒精饮料&#xff0c;是水和茶之后世界上消耗量排名第三的饮料。 啤酒在生产过程中主要有制造麦芽、粉碎原料、糖化、发酵、贮酒後熟、过滤、灌装包装等工序流程。需要用到风选机、筛分机、糖化锅、发酵设备、过滤机、灌装机、包装机等食品机械设备。这些食…

安全远程访问工具

什么是安全远程访问 安全远程访问是指一种 IT 安全策略&#xff0c;允许对企业网络、任务关键型系统或任何机密数据进行授权、受控访问。它使 IT 团队能够根据员工和第三方的角色和工作职责为其提供不同级别的访问权限&#xff0c;安全的远程访问方法可保护系统和应用程序&…

前缀和实例5(连续数组)

题目&#xff1a; 给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组&#xff0c;并返回该子数组的长度。 示例 1: 输入: nums [0,1] 输出: 2 说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。 示例 2: 输入: nums [0,1,0] 输出: 2 说明: [0…

el-upload实现复制粘贴图片

前言&#xff1a; 在之前的项目中&#xff0c;利用el-upload实现了上传图片视频的预览。项目上线后&#xff0c;经使用人员反馈&#xff0c;上传图片、视频每次要先保存到本地然后再上传&#xff0c;很是浪费时间&#xff0c;公司客服人员时间又很紧迫&#xff08;因为要响应下…

DAMO-YOLO训练KITTI数据集

1.KITTI数据集准备 DAMO-YOLO支持COCO格式的数据集&#xff0c;在训练KITTI之前&#xff0c;需要将KITTI的标注转换为KITTI格式。KITTI是采取逐个文件标注的方式确定的&#xff0c;即一张图片对应一个label文件。下面是KITTI 3D目标检测训练集的第一个标注文件&#xff1a;000…

JavaScript位运算的妙用

位运算的妙用: 奇偶数, 色值换算,换值, 编码等 位运算的基础知识: 操作数是32位整数自动转化为整数在二进制下进行运算 一.按位与& 判断奇偶数: 奇数: num & 1 1偶数: num & 1 0 基本知识: 用法&#xff1a;操作数1 & 操作数2规则&#xff1a;有 0 则为…

机柜PDU产品采购与安装指南——TOWE精选

机柜PDU指的是Power Distribution Unit&#xff0c;即电源分配单元。它是一种电子设备&#xff0c;通常用于为数据中心、服务器机房等设施中的计算机和其他设备提供电力&#xff0c;是各行业数据中心“标配”构成部分&#xff0c;以确保服务器等用电设备的安全和稳定运行。 数据…

查看Linux系统信息的常用命令

文章目录 1. 机器配置查看2. 常用分析工具3. 常用指令解读3.1 lscpu 4. 定位僵尸进程5. 参考 1. 机器配置查看 # 总核数物理CPU个数x每颗物理CPU的核数 # 总逻辑CPU数物理CPU个数x每颗物理CPU的核数x超线程数 cat /proc/cpuinfo| grep "physical id"| sort| uniq| w…

[Linux]多线程编程

[Linux]多线程编程 文章目录 [Linux]多线程编程pthread_create函数pthread_join函数pthread_exit函数pthread_cancel函数pthread_self函数pthread_detach函数理解线程库和线程id Linux操作系统下&#xff0c;并没有真正意义上的线程&#xff0c;而是由进程中的轻量级进程&#…

在多台服务器上运行相同命令(二)、clush

介绍安装配置互信认证参数含义基本使用节点组拷贝文件 介绍 Clush&#xff08;Cluster Shell&#xff09;是一个用于管理和执行集群操作的工具&#xff0c;它允许你在多台远程主机上同时执行命令&#xff0c;以便批量管理服务器。Clush 提供了一种简单而强大的方式来管理大规模…

“押宝高手”乐视视频再出手,看中商业传奇剧《大盛魁》

作为最早开始版权采购的长视频平台&#xff0c;乐视视频一向擅长“押宝”优质内容。从《甄嬛传》到《白鹿原》等&#xff0c;乐视拿下了众多经典古装剧、年代剧的版权。 9月&#xff0c;乐视视频再次出手拿下的历史传奇剧《大盛魁》开始热播。该剧由王新民导演执导&#xff0c…

全渠道客服体验:Rocket.Chat 的无缝互动 | 开源日报 No.41

RocketChat/Rocket.Chat Stars: 36.9k License: NOASSERTION Rocket.Chat 是一个完全可定制的开源通信平台&#xff0c;适用于具有高标准数据保护要求的组织。我们是团队沟通场景下的最终免费开源解决方案&#xff0c;可以实现同事之间、公司之间或客户之间的实时对话。提高生…