大纲
- 为什么要记录日志
- logback 简介
- logback 使用
- 日志记录“最佳”实践
为什么要记录日志
在开发中,会经常使用System.out.println
方法调用来帮助观察程序运行的操作过程。system.out.printIn
打印日志的方式太过原始,影响性能,而且只能输出在控制台,而在生产环境下我们往往需要将日志存储下来。
所以我们一般选择日志框架来做这些事情,使用日志框架有以下几点好处:
- 可以设置输出样式,避免自己每次都写"ERROR: " + var;
- 可以全局设置输出级别,禁止某些级别输出。例如,只输出错误日志;
- 可以被重定向到文件,这样可以在程序运行结束后查看日志;
- 可以按包名控制日志级别,比如只输出某些包打的日志;
java 生态下的日志框架有很多,首先 java 标准库也提供了日志包java.util.logging
,但 java标准库内置的Logging 使用并不广泛。java 生态下比较流行的日志框架有log4j
、log4j 的升级版Log4j 2
,还有 spring boot 内置的logback
。那我们当然选择最流行的 logback 啦~
log4j
、logback
这些日志框架都采用门面模式。
门面模式是什么?其实就是面向接口编程,我们在应用程序中编写日志代码使用“门面框架”提供的 api,而底层“门面框架”会去自动在 classPath 中寻找“日志实现”框架,这样一来,如果我们
比如commons logging
是一个门面框架,提供了一套日志接口,而真正的日志实现可以使用 Log4j。
slf4j
也是一个门面框架,提供了一套日志接口,而真正的日志实现可以使用 LogBack。
Logback 使用
Logback 的使用很简单,一般需要先获取到 Logger 日志对象,然后调用 http://logger.xxx(比如logger.info
)就能输出日志了。
最传统的方法就是通过 LoggerFactory 手动获取 Logger,示例代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void doSomething() {
logger.info("执行了一些操作");
}
}
LoggerFactory 是一个工厂类,调用getLogger
函数可以返回一个 logger 实例,getLogger
函数参数可以是一个 class 或是字符串,这样 logger 输出的时候,就可以输出传入的类参数了。
Logback
(官网)是一款为Java应用程序设计的日志框架,旨在提供高性能、灵活性和可扩展性。它是log4j项目的继任者,并被广泛用于Java应用程序的日志记录。
logback 的作者也是 log4j 的作者
Logback 源码包分为三个主要的模块:logback-core、logback-classic、和logback-access。
- logback-core提供了基本的日志功能。
- logback-classic建立在logback-core之上,兼容SLF4J和log4j API,提供了一套强大的日志框架。
- logback-access允许通过servlet容器的访问日志功能来记录HTTP请求。()
Spring Boot默认集成了Logback,并用INFO级别输出到控制台。 由于Spring Boot通常使用嵌入式Servlet容器,并且这些容器已经具备了记录访问日志的功能,因此在默认情况下不需要引入logback-access。
Logback 具有许多优点,其中一些包括:
- 性能高效
Logback被设计为高性能的日志框架,具有较低的运行时开销。
异步日志记录和可配置的缓冲机制有助于提高性能。
- 灵活的配置
Logback的配置文件采用XML格式(通常命名为logback.xml),允许用户以声明式的方式配置日志输出。
支持通过Groovy脚本进行配置,提供更灵活的选项。
- 丰富的Appender
提供多种Appender用于将日志输出到不同的目的地,如控制台、文件、数据库等。
支持异步Appender以提高性能。
- 简单而强大的 API
Logback 的 API 设计简单易用,同时具备强大的功能。它继承了 Log4j 的 API,但在设计上进行了改进和优化,使得开发者可以更容易地集成和使用。
- 广泛的社区支持
作为一个成熟而受欢迎的日志框架,Logback 拥有广泛的社区支持和活跃的开发者社群。这意味着开发者可以在社区中获取丰富的资源、文档和支持。
Logback被划分为三个模块:logback-core
、logback-classic
和logback-access
。其中,logback-core
是基础模块,为其他两个模块提供支持;logback-classic
是log4j的一个显著改进版本,原生实现了SLF4J API,便于在不同日志系统间切换;logback-access
则与Servlet容器集成,提供HTTP访问日志功能。
Logback的架构基于三个核心类:Logger
、Appender
和Layout
。这些组件协同工作,使开发者能够根据消息类型和级别记录日志,并在运行时控制日志的格式化和输出位置。
- Logger:属于logback-classic模块,是命名实体,遵循层次化命名规则。每个Logger都附加到一个LoggerContext,后者负责创建和组织Logger的树形层次结构。
- Appender 和 Layout:属于logback-core模块,Appender负责将日志输出到不同目的地,如控制台、文件、数据库等,而Layout则负责格式化日志请求。
````
Logback允许将日志请求输出到多个目的地,这些目的地被称为Appenders。可以将多个Appender附加到一个Logger上,并且Appender在Logger层次结构中是累加的。通过设置Logger的additivity标志为false,可以覆盖这一默认行为。
用户可以通过为Appender关联Layout来定制输出格式。PatternLayout是标准Logback发行版的一部分,允许用户根据类似于C语言printf函数的转换模式来指定输出格式。
Logback-classic中的Logger实现了SLF4J的Logger接口,支持参数化的打印方法,以提高性能并减少对代码可读性的影响。例如,使用logger.debug("The entry is {}.", entry);可以在不构建参数的情况下记录日志,仅在需要记录时才格式化消息。
logback 基于三个主要的类构建:logger
、appender
、layout
通过 logback.xml 配置,其实也是在配置这三个类
- logger context
- effective level 继承
- appender: 在 logback 中,appender 决定日志的输出位置 ,appender 可以存在于 控制台、文件、数据库等等。
Layout
决定了日志信息的输出格式- 打印 logback 是否正确配置日志
- 配置文件 logback.xml 中的 root 节点其实就代表了项目的根 logger 实例(考虑 logger 继承关系)
日志级别
日志级别的作用是标识日志的重要程度,日志级别粒度从细到组分为:
TRACE
:最细粒度的信息,通常只在开发过程中使用,用于跟踪程序的执行路径。DEBUG
:调试信息,记录程序运行时的内部状态和变量值。INFO
:一般信息,记录系统的关键运行状态和业务流程。WARN
:警告信息,表示可能存在潜在问题,但系统仍可继续运行。ERROR
:错误信息,表示出现了影响系统功能的问题,需要及时处理。FATAL
:致命错误,表示系统可能无法继续运行,需要立即关注。
其中,用的最多的当属 DEBUG、INFO、WARN 和 ERROR 了。
建议在开发环境使用低级别日志(比如 DEBUG),以获取详细的信息;生产环境使用高级别日志(比如 INFO 或 WARN),减少日志量,降低性能开销的同时,防止重要信息被无用日志淹没。
在src/main/resources目录下创建名为logback-spring.xml或者logback.xml的Logback配置文件。Spring Boot默认会加载类路径下的logback-spring.xml文件。如果该文件不存在,则会尝试加载logback.xml。如果我们使用的是别的配置名称,则需要在springboot 的application.yml配置文件中经行配置:
# 日志配置
logging:
config: classpath:mylogback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/data/xjdoc/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="cn.xj" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/data/xjdoc/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="async_file_info" class="ch.qos.logback.classic.AsyncAppender">
<!-- 当队列的剩余容量小于这个阈值并且当前日志level为 TRACE, DEBUG or INFO ,则丢弃这些日志。 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>1024</queueSize>
<!-- 新增这行为了打印栈堆信息 -->
<includeCallerData>true</includeCallerData>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_info"/>
</appender>
<appender name="async_file_error" class="ch.qos.logback.classic.AsyncAppender">
<!-- 当队列的剩余容量小于这个阈值并且当前日志level为 TRACE, DEBUG or INFO ,则丢弃这些日志。 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>1024</queueSize>
<!-- 新增这行为了打印栈堆信息 -->
<includeCallerData>true</includeCallerData>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_error"/>
</appender>
<appender name="async_file_debug" class="ch.qos.logback.classic.AsyncAppender">
<!-- 当队列的剩余容量小于这个阈值并且当前日志level为 TRACE, DEBUG or INFO ,则丢弃这些日志。 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>1024</queueSize>
<!-- 新增这行为了打印栈堆信息 -->
<includeCallerData>true</includeCallerData>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="file_debug"/>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="cn.xj" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<!--系统操作日志-->
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="async_file_info" />
<appender-ref ref="async_file_error" />
</root>
</configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="/data/xjdoc/logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);"><property></font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%d{HH:mm:ss.SSS}</font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">[%thread]</font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%-5level</font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%logger{20}</font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">[%method,%line]</font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%msg</font>
<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">%n</font>
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
appender
元素
**<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">rollingPolicy</font>**
**<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">encoder</font>**
**<font style="color:rgb(25, 27, 31);background-color:rgb(248, 248, 250);">filter</font>**
<!-- 系统模块日志级别控制 -->
<logger name="cn.xj" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
level 属性定义了日志级别,表示记录的日志消息级别。可选值包括 trace、debug、info、warn、error。
- TRACE(追踪)
TRACE 是最低级别的日志,用于记录程序的详细执行信息。
这个级别通常用于调试,输出对程序执行流程的跟踪信息。
TRACE 级别的日志量较大,一般情况下不会在生产环境中启用。
- DEBUG(调试)
DEBUG 级别用于输出调试信息,有助于开发者定位和解决问题。
DEBUG 日志通常包含详细的变量信息、方法调用堆栈等。
在开发和测试阶段中,可以启用 DEBUG 日志以获取更多的信息。
- INFO(信息)
INFO 级别用于记录一般性的信息,表示程序执行的正常流程。
INFO 日志用于显示重要的运行时信息,通常在生产环境中启用。
这是默认的日志级别,如果没有明确指定级别,则使用 INFO。
- WARN(警告)
WARN 级别用于记录一些可能需要关注的问题,但不会导致程序失败。
WARN 日志表明程序可能遇到了某些问题或潜在的错误,但仍然可以继续执行。
这是一个提醒性的级别。
- ERROR(错误)
ERROR 级别用于记录程序的错误和异常情况。
ERROR 日志表示程序遇到了严重的问题,可能导致程序崩溃或无法正常运行。
ERROR 级别的日志通常需要开发者及时关注和处理。
在 Logback 配置文件中,<root>
元素用于配置根 Logger,它是整个日志系统的根节点。根 Logger 拥有最高级别,通常用于设置全局的日志级别和全局的 Appender(附加器)。
<root level="info">
<appender-ref ref="console" />
<appender-ref ref="async_file_info" />
<appender-ref ref="async_file_error" />
</root>
level 属性
level 属性定义了根 Logger 的默认日志级别,表示整个日志系统的最低输出级别。例如,上例中设置为 INFO,表示只输出 INFO 级别及以上的日志。
appender-ref
元素
appender-ref
元素用于引用一个或多个 Appender,将其关联到根 Logger,即设置根 Logger 的输出目的地。可以有多个appender-ref
元素,表示将日志输出到多个目的地。