介绍
作为一名开发人员应该了解日志在日常开发程序的调试和监控过程中的重要性。本章节通过介绍常用的 Logback 日志框架再来具体讲述我们在一个 Spring Boot 的项目中应该怎样来集成这样的一个日志框架。实验的最终效果记录了程序运行过程中的日志,同时生成了控制台日志和文件日志记录。
知识点
- 日志的重要性及常用的日志框架
- Logback 的工作原理
- Spring Boot 整合 Logback 框架过程
本课程内容中涉及 Spring Boot 项目源代码来自 xkcoding/spring-boot-demo,该项目使用 MIT License 开源协议。
日志的重要性及常用的日志框架
日志的重要性
日志记录了系统行为的时间、地点等很多细节的具体信息,可以帮助开发人员了解并且监控系统的状态,在发生错误或者接近某种危险状态时能够及时提醒开发人员处理,往往在系统产生问题时承担问题定位与诊断和解决的重要角色。一般我们的项目出现问题的时候首先会想到 debug,但是很多线上的问题是只能通过我们进行日志分析才可以解决的,所以作为一名开发人员需要明确日志在日常开发环节中的重要性。
常用的日志框架
下面列举一些常用的日志框架:Commons Logging、Slf4j、Log4j,Log4j2,Logback。
上述的这些常用日志框架中,Log4j2 和 Log4j 完全不兼容,Commons Logging 和 Slf4j 是日志门面,这里需要介绍一下日志门面的概念,所谓日志门面就是门面模式的一个典型的应用,门面模式也称为外观模式,而日志门面框架就是一套提供了日志相关功能的接口而无具体实现的框架,日志记录是通过调用具体的实现框架来进行的。所以日志门面框架是兼容具体日志实现框架的。典型的日志门面就是 Commons Logging、SLF4J。比较常用的组合方式是 Slf4j 和 Logback 的组合使用,Commons Logging 与 Log4j 的组合使用,并且 Logback 必须得配合 Slf4j 使用。
- Commons Logging 是一个基于 Java 的日志记录实用程序,是用于日志记录和其他工具包的编程模型。它通过其他一些工具提供 API,日志实现和包装器实现。
- Slf4j(Simple Logging Facade for Java),是一套包装了 Logging 框架的界面程式,以外观模式实现。可以在软件部署的时候决定要使用的 Logging 框架,像 Log4j 及 Logback 等框架都对其提供了支持。
- Log4j 是一个基于 Java 的日志记录工具。
- Log4j2 是 Apache 开发的一款 Log4j 的升级产品,并且不兼容 Log4j。
- Logback 和 Log4j 是同一个作者的作品,继承了 Log4j 的优点并改良了它的不足的地方。实现了对于 Tomcat 和 Jetty 等服务器的集成,提供了 HTTP 访问日志的功能,并且可以让开发人员很方便的拓展构建自己的模块。
以下是官方列举的一些 Logback 的优势:
- 更快的实施:Logback 重写了 Log4j 内部实现,在关键路径提升了十倍速度,减少了内存损耗。
- 更广泛的测试:Logback 比 Log4j 进行了更长时间、更高级别的测试。
- 更广泛的文档:Logback 具有更详细并且不断更新的文档。
- Logback 可以自动扫描并加载修改后的配置文件,并且不启用新的线程资源。
- Logback 在 I/O 失败的情况下,不需要重新应用即可在 server 正常后恢复正常输出。
- Logback 通过对于 maxHistory 属性的设置可以自动删除旧的日志文件。
- Logback 的 RollingFileAppender 可以异步的对旧的日志文档进行压缩。
- Logback 的谨慎模式下多个 FileAppender 可以写入同一个日志文件。
- Logback 具有 Lilith 可以查看大数据量级的日志与访问事件查看器工具。
- Logback 配置文件中支持条件判断语句的写法,从而实现不同环境个别配置自动替换。
- Logback 中支持对于特定用户显示较低与配置日志等级的日志。
- Logback 提供了可以通过给定的属性对日志进行分离的 SiftingAppender。
- Logback 的异常堆栈信息将会打印出相应包的信息。
- Logback 的 logback-access 提供了 HTTP 访问日志功能。
Logback 的工作原理
Logback 是由 log4j 的创始人编写的,但是性能上的话会比 log4j 更好一些,他主要分为有三个模块:
- logback-core:核心代码模块。
- logback-classic:log4j 的一个改良版本,同时实现了 slf4j 的接口。
- logback-access:这是 logback 的访问模块,它与 Servlet 容器集成并且提供通过 Http 来访问日志的功能。
slf4j 是标准,对用户提供统一的 API,而下方对接各个日志框架。这有点类似 JVM,我们 Java 开发者使用统一的 API,而 JVM 对接各个操作系统。严格意义上说 slf4j 自身并不提供日志具体实现。在 Logback 的模块里面,logback-classic 就是实现了 slf4j 具体接口的核心模块。它可以自动重新加载配置文件,如果开发过程中配置文件修改了,logback-classic 能自动重新加载配置文件,扫描过程快且安全,它并不需要另外创建一个扫描线程。
更多的关于 Logback 具体的工作使用流程可以通过具体的配置文件信息来进行相应的配置。所以接下来我们在具体的一个实验中来看看 Logback 日志框架在 Spring Boot 中是怎样整合使用的。
Spring Boot 整合 Logback 日志框架
首先我们需要新建一个 Maven 工程,打开我们的 WebIDE 环境,在终端输入以下内容:
mvn archetype:generate \
-DgroupId=com.example.logback \
-DartifactId=spring-boot-log-logback \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
在环境中构建出来的项目的结构的效果如下,大家需要注意一下,通过上面的方式我们构建出来的项目启动文件是 App.java
,但是为了命名上更贴近本章节课程,我们修改成了 SpringBootDemologbackApplication.java
文件,后面大家需要手动修改一下,当然也可以保留,但是大家一定要注意项目启动类书写的规范不要出错,下面贴的有修改过后的项目启动类的代码。
上面需要注意的是图中的 logs 用于存放日志的文件夹不需要自己手动来创建的,target 文件夹是编译过后生成的文件,因为我们在配置文件中进行了相应的配置之后是可以自动在项目路径下生成的。所以我们需要创建的项目结构是 src/main/java/com/example/logback
和 src/main/resources
。
接下来我们需要添加这个项目的依赖,大家可以将下面的代码复制粘贴到项目结构中的 pom.xml
文件中:仔细观察这个依赖你会发现 dependency 依赖组中并没有 Logback 的依赖说明。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.logback</groupId>
<artifactId>spring-boot-log-logback</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-log-logback</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>spring-boot-demo-logback</finalName>
<plugins>
<!--Spring Boot maven插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其实因为 Spring Boot 默认已经整合了 Logback 的,所以在创建 Spring Boot 工程时引入的 spring-boot-starter 就已经包含了 spring-boot-starter-logging,而 spring-boot-starter-logging 中引用了 Logback 的依赖。如果我们点开 spring-boot-starter-parent 的包查看源码的话会发现 Logback 所需要引用的三个包已经被 Spring Boot 整合了,所以我们无需再在 pom.xml
文件中重新引入这三个包。
另外我们在 pom.xml
文件中添加进去了 Lombok 相关的依赖这样子我们就可以使用 @Slf4j
注解来直接使用 log()方法了。
上面处理完 pom.xml
文件之后,我们再在 src/main/resources
路径下新增并配置 application.yml
和 Logback 相关的配置文件,首先是 application.yml
文件。这是我们在实验中的常规配置,但是在本实验中我们不需要发起 Web 端的请求,我们只需要在控制台界面查看我们需要打印和记录的日志信息。
server:
port: 8080
servlet:
context-path: /demo
Logback 相关的配置文件 logback-spring.xml
我们需要在 resources 文件夹下面新建如下的配置文件信息,来配置我们的 Logback。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="FILE_ERROR_PATTERN"
value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} %file:%line: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Info 级别的日志,只是过滤 info 还是会输出 Error 日志,因为 Error 的级别高, 所以我们使用下面的策略,可以避免输出 Error 的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤 Error-->
<level>ERROR</level>
<!--匹配到就禁止-->
<onMatch>DENY</onMatch>
<!--没有匹配到就允许-->
<onMismatch>ACCEPT</onMismatch>
</filter>
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
<!--<File>logs/info.spring-boot-demo-logback.log</File>-->
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>logs/spring-boot-demo-logback/info.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!--<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">-->
<!--<maxFileSize>1KB</maxFileSize>-->
<!--</triggeringPolicy>-->
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>Error</level>
</filter>
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天的日志改名为今天的日期。即,<File> 的日志都是当天的。-->
<!--<File>logs/error.spring-boot-demo-logback.log</File>-->
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
<FileNamePattern>logs/spring-boot-demo-logback/error.created_on_%d{yyyy-MM-dd}.part_%i.log</FileNamePattern>
<!--只保留最近90天的日志-->
<maxHistory>90</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为1KB,只是为了演示 -->
<maxFileSize>2MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>${FILE_ERROR_PATTERN}</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</configuration>
在 logback-spring.xml
配置文件中,configuration 标签表示了日志配置文件的配置信息,其包含有一些子节点包括 property,用于设置一些变量,另外比较重要的就是 appender 标签了,appender 是负责日志写的组件。
主要标签节点可以归纳为如下图所示:
- ConsoleAppender:主要的任务是将日志添加到控制台上面,它包含的有几个子标签,比如 encoder,就是将日志的信息具体格式化。LevelFilter 是日志的级别过滤器,我们可以通过它的子标签 level 设置过滤级别,onMatch 用于配置符合过滤条件的操作,onMismatch 用于配置不符合过滤条件的操作。所以如果日志级别等于配置级别,过滤器会根据 onMatch 和 onMismatch 接收或拒绝日志。
- RollingFileAppender:用于滚动记录文件,也就是先将日志记录到指定的文件上,当满足某种条件的情况下再记录到其他的文件下。所以我们会在这个标签下面选择并定义滚动的策略。比如这里我们就可以使用按照时间滚动的策略,通过 rollingPolicy 指定具体的策略选择。这里需要对比的是 File 和 FileNamePattern 两个标签,虽然都是用于保存日志具体路径的标签,但是这里有个细节,就是如果没有 File 标签制定日志保存的路径的话就会默认保存在 FileNamePattern 路径下面,如果两者都同时存在的时候就会保存在 File 制定的路径下面。除了按照时间滚动的策略还有按照日志大小滚动的策略,这些都是可以选用并在配置文件中配置说明的。
配置文件最后的 root 标签的相关内容就是说明的默认的日志配置信息,比如日志默认的级别是 info 级别的。
另外我们多次谈到了日志的级别,这里我们对日志的级别再进行一个集中的总结:
关于日志的级别,Logback 有 5 种级别,分别是 TRACE < DEBUG < INFO < WARN < ERROR,定义于 ch.qos.logback.classic.Level 类中。<符号代表了这些日志的等级顺序。其中:
- Trace:是追踪,很低的日志级别,我们可以设置最低日志级别不让他输出。
- Debug:指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。
- Info:消息在粗粒度级别上突出强调应用程序的运行过程,可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
- Warn:输出警告及 warn 以下级别的日志,表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
- Error:输出错误信息日志,指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。 此外 OFF 表示关闭全部日志,这是最高等级的日志级别,而 ALL 是最低的日志级别,表示开启全部日志。
编写日志测试启动类 SpringBootDemologbackApplication
我们需要修改我们的启动类如上所示,因为原来 mvn 构建项目的命令过后是生成的一个 App.java
的启动类,我们将它改变成下面代码所示:构建好后的 SpringBootDemologbackApplication.java
的路径是在 src/main/java/com/example/logback/SpringBootDemologbackApplication.java
下面的。
package com.example.logback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@Slf4j
public class SpringBootDemologbackApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemologbackApplication.class, args);
int length = context.getBeanDefinitionNames().length;
log.trace("Spring boot启动初始化了 {} 个 Bean", length);
log.debug("Spring boot启动初始化了 {} 个 Bean", length);
log.info("Spring boot启动初始化了 {} 个 Bean", length);
log.warn("Spring boot启动初始化了 {} 个 Bean", length);
log.error("Spring boot启动初始化了 {} 个 Bean", length);
try {
int i = 0;
int j = 1 / i;
} catch (Exception e) {
log.error("【SpringBootDemologbackApplication】启动异常:", e);
}
}
}
我们除了 Spring Boot 项目启动添加的常用注解 @SpringBootApplication
以外需要注意的是,我们使用了 @Slf4j2
这个注解,我们之前在 pom.xml
文件中是引进了 Lombok 插件的,回想如果我们之前每次使用日志 logger 的时候,都是要写上这样的一句话:
private final Logger logger = LoggerFactory.getLogger(当前类名.class);
但是其实我们加上了 @Slf4j2
这个注解之后就可省略掉这句话的描述,这样是很方便的,大家平时在自己的环境里面使用 lombok 的时候记得要在自己的 IDE(如果大家使用的是 idea 的话)里面安装 lombok 插件使用。这样子我们可以使用 log 来打印日志了。
另外在功能模块测试上我们分为了两个方面来设计,首先包含了 log.trace、log.debug、log.info、log.warn、log.error 这几个日志打印的函数,这是我们指定的打印的日志的级别,我们通过 context.getBeanDefinitionNames()来获取 bean 的名称,日志打印它的长度,另外我们在 try-catch 的循环体中利用 i=0 不能作为除数来捕获这个异常,并通过 log.error 打印这样的一场信息。
我们在终端命令中输入如下的命令,但是需要注意的是我们已经将环境的工作空间切换到了项目目录包含了 pom.xml
文件下面,即将此时的工作空间切换到 /home/project/spring-boot-log-logback
下,我们可以在终端中输入 cd spring-boot-demo
并且看终端中是否显示当前的工作目录在 spring-boot-demo 下面。
mvn spring-boot:run
来运行程序,运行的效果如下:
可以看出最后我们是抛出并且捕获了这个算术异常的:java.lang.ArithmeticException: / by zero
总结
通过初始化一个 Spring Boot 和 Logback 日志框架整合的项目 demo 了解并学习了 Spring Boot 支持的高性能的日志框架 Logback,通过类比其他常用的日志框架对比分析 Logback 的优势,并通过实际操作熟悉了 Logback 框架下面的常用的配置文件,日志在项目的开发环节中起着非常重要的作用,这是每一个开发人员都要掌握的内容。
本文由 liyunfei 创作,采用 知识共享署名4.0
国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Jun 29,2022