在Java开发中,日志记录是不可或缺的一部分,它帮助开发者监控应用程序的运行状态和调试问题。Logback作为一种强大的日志框架,与SLF4J(Simple Logging Facade for Java)紧密结合,提供了一种灵活的日志解决方案。SLF4J是一个简单的日志门面,允许开发者使用统一的API,而不必依赖具体的日志实现。Logback是SLF4J的默认实现,它不仅提供了丰富的功能,还具备高性能和低延迟的特点。
通过使用SLF4J,开发者可以在不同的环境中灵活地切换日志实现,例如Logback、Log4j或Java Util Logging,而无需修改代码。这种解耦设计使得应用程序更具可维护性和可扩展性。
1. 启动自动扫描
<configuration scan="true" scanPeriod="60 seconds" debug="false">
scan
:启用自动扫描配置文件的更改,主要是针对logback.xml
文件本身的更改,当logback.xml
文件有任何修改时,Logback 会自动加载新的配置,而不需要重启应用程序。
2. 引入默认配置
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
- 引入默认配置后,只需在
logback.xml
中覆盖需要更改的部分。例如,如果您想修改某个日志级别或输出格式,只需重新定义相应的appender
或logger
,就会覆盖 Spring Boot 提供的默认配置。
3. 自定义转换规则
<conversionRule conversionWord="ip" converterClass="com.longfor.gaia.gfs.core.utils.IPAddressConverter" />
<conversionRule conversionWord="m" converterClass="com.property.commons.log.sensitive.SensitiveMessageConverter"/>
- 这两个规则表示当日志中出现
ip
和m
这两个字符时,Logback 将调用相应的转换器IPAddressConverter
和SensitiveMessageConverter
进行处理。具体来说,IPAddressConverter
可以将 IP 地址格式化或隐匿,而SensitiveMessageConverter
可以处理敏感信息,例如替换或掩码。
4. Spring 属性
<springProperty scope="context" name="spring_application_name" source="spring.application.name" />
-
这个定义的属性表示从
application.properties
或application.yml
中获取配置,例如spring.application.name=myApplication
,将取代spring_application_name
。这样在日志输出中可以动态使用应用名称。 -
使用示例:
<property name="CONSOLE_LOG_PATTERN" value="%clr(${spring_application_name}){cyan}||%clr(%d{ISO8601}){faint}|%clr(%p)|%X{requestId}|%X{X-B3-TraceId:-}|%X{requestIp}|%X{userIp}|%ip|${server_port}|${PID}|%clr(%t){faint}|%clr(%.40logger{39}){cyan}.%clr(%method){cyan}:%L|%m|%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
- 在日志输出中,
${spring_application_name}
会被替换为myApplication
。经过转换后的日志示例可能是:myApplication||2024-10-28 10:00:00|INFO|requestId|X-B3-TraceId|192.168.0.1|192.168.0.1|0.0.0.0|8080|12345|main|com.example.MyClass.myMethod:42|Log message here
- 在日志输出中,
-
CONSOLE_LOG_PATTERN
的运用:CONSOLE_LOG_PATTERN
会在配置的appender
中使用,尤其是在控制台输出时,确保日志格式统一和清晰。
5. 定义appender
在 Logback 中,Appender 是日志记录的输出目的地。您可以将日志输出到控制台、文件、数据库、消息队列等。通过配置不同的 Appender,您可以实现灵活的日志管理。
Appender 的具体运用
-
定义日志文件大小、保存时间和归档策略
<!--定义日志文件大小 超过这个大小会压缩归档 --> <property name="DEBUG_MAX_FILE_SIZE" value="100MB"/> <property name="INFO_MAX_FILE_SIZE" value="100MB"/> <property name="ERROR_MAX_FILE_SIZE" value="100MB"/> <property name="TRACE_MAX_FILE_SIZE" value="100MB"/> <property name="WARN_MAX_FILE_SIZE" value="100MB"/> <!--定义日志文件最长保存时间 --> <property name="DEBUG_MAX_HISTORY" value="9"/> <property name="INFO_MAX_HISTORY" value="9"/> <property name="ERROR_MAX_HISTORY" value="9"/> <property name="TRACE_MAX_HISTORY" value="9"/> <property name="WARN_MAX_HISTORY" value="9"/> <!--定义归档日志文件最大保存大小 --> <property name="DEBUG_TOTAL_SIZE_CAP" value="5GB"/> <property name="INFO_TOTAL_SIZE_CAP" value="5GB"/> <property name="ERROR_TOTAL_SIZE_CAP" value="5GB"/> <property name="TRACE_TOTAL_SIZE_CAP" value="5GB"/> <property name="WARN_TOTAL_SIZE_CAP" value="5GB"/>
- 最大文件大小:这些属性定义了每个日志文件的最大大小(例如 100MB)。当日志文件超过这个大小时,Logback 会进行压缩归档。
- 最大保存时间:这些属性定义了每个日志文件的保存历史(例如 9 个备份)。这意味着系统会保留最近的 9 个日志文件,超出部分会被删除。
- 总大小限制:这些属性限制了所有归档日志的总大小(例如 5GB)。如果总大小超过此限制,Logback 将删除旧的日志文件以腾出空间。
-
RollingFileAppender 的配置
例如,下面是一个
DEBUG_FILE
的配置:<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_HOME}/debug.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/backup/debug/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> <maxHistory>${DEBUG_MAX_HISTORY}</maxHistory> <maxFileSize>${DEBUG_MAX_FILE_SIZE}</maxFileSize> <totalSizeCap>${DEBUG_TOTAL_SIZE_CAP}</totalSizeCap> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> </appender>
- Appender 类型:
RollingFileAppender
允许您根据大小和时间对日志文件进行滚动管理。 - 当前日志文件:
<file>${LOG_HOME}/debug.log</file>
定义了当前的日志文件名。 - Rolling Policy:
SizeAndTimeBasedRollingPolicy
指定了日志文件滚动的策略,这里是根据大小和时间进行滚动。 - 文件名模式:
<fileNamePattern>
定义了归档文件的命名模式,其中%d{yyyy-MM-dd}
表示日期,%i
表示索引(以防当天有多个文件)。 - Encoder:
<encoder>
定义了日志输出的格式,使用PatternLayoutEncoder
进行格式化。 - Filter:
<filter>
用于过滤日志消息,只有DEBUG
级别的日志会被记录。
- Appender 类型:
-
其他类型的 Appender
INFO_FILE
、WARN_FILE
和ERROR_FILE
的配置与DEBUG_FILE
类似,只是它们的过滤级别不同(分别是INFO
、WARN
和ERROR
)。- 这些配置确保不同级别的日志被输出到不同的文件中,方便进行管理和查看。
- 有些Appender在
<filter>
加上了<onMatch>
和<onMismatch>
标签,若是没有加则会记录级别大于当前定义<level>
的日志级别。若是加伤了则只会记录当前定义的<level>
的日志级别。
<filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>WARN</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter>
-
Kafka Appender
<appender name="KAFKA" class="com.github.danielwegener.logback.kafka.KafkaAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> <topic>${kafka_env}applog_${spring_application_name}</topic> <keyingStrategy class="com.github.danielwegener.logback.kafka.keying.NoKeyKeyingStrategy" /> <deliveryStrategy="com.github.danielwegener.logback.kafka.delivery.AsynchronousDeliveryStrategy" /> <producerConfig>bootstrap.servers=${kafka_broker}</producerConfig> <producerConfig>acks=0</producerConfig> <producerConfig>linger.ms=1000</producerConfig> <producerConfig>max.block.ms=0</producerConfig> </appender>
- 这个 Appender 将日志发送到 Kafka 消息队列,以便进行异步处理或集中管理。
- 配置了主题、键策略、交付策略和生产者配置,以确保日志消息能被有效地发送到 Kafka。
-
AsyncAppender
<!-- 异步传递策略,建议选择异步,不然连接kafka失败,会阻挡服务启动 --> <appender name="KAFKA_ASYNC" class="ch.qos.logback.classic.AsyncAppender"> <appender-ref ref="KAFKA" /> </appender>
AsyncAppender
用于将日志记录异步化,以提高应用程序的性能。通过将日志消息发送到异步处理队列,应用程序可以更快地响应请求而不被日志记录阻塞。
6、根日志配置
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<if condition='"true".equals(property("kafka_enabled"))'>
<then>
<appender-ref ref="KAFKA_ASYNC"/>
</then>
</if>
</root>
-
<root level="INFO">
:- 设置根日志记录器的级别为 INFO。这意味着所有 INFO 及以上级别的日志(如 WARN、ERROR 等)才会被记录。
-
<appender-ref ref="CONSOLE"/>
:- 指定输出到控制台的 appender,所有匹配级别的日志将会输出到控制台。
-
<appender-ref ref="INFO_FILE"/>
:- 指定输出到
INFO_FILE
appender,这个 appender 会记录 INFO 及以上级别的日志到指定文件中。
- 指定输出到
-
<appender-ref ref="WARN_FILE"/>
:- 指定输出到
WARN_FILE
appender,这个 appender 会记录 WARN 及以上级别的日志到指定文件中。
- 指定输出到
-
<appender-ref ref="ERROR_FILE"/>
:- 指定输出到
ERROR_FILE
appender,这个 appender 会记录 ERROR 级别的日志到指定文件中。
- 指定输出到
-
条件判断:
<if condition='"true".equals(property("kafka_enabled"))'> <then> <appender-ref ref="KAFKA_ASYNC"/> </then> </if>
- 这段代码检查一个名为
kafka_enabled
的属性。如果这个属性的值为"true"
,那么就会引用KAFKA_ASYNC
appender。 - 这意味着,如果你希望将日志信息发送到 Kafka,需要在配置中将
kafka_enabled
属性设置为true
。
- 这段代码检查一个名为
所以只有日志级别大于或等于 中指定的级别的日志(即 INFO、WARN 和 ERROR)才会被记录。同时,这些日志必须通过在 标签中引用的 appender(如 CONSOLE、INFO_FILE、WARN_FILE 和 ERROR_FILE)进行输出。
因此,只有符合这两个条件的日志才会被实际记录和输出。这样的配置确保了日志的精确管理和灵活输出。
7、完整logback.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- Override CONSOLE_LOG_PATTERN from defaults.xml -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<conversionRule conversionWord="ip" converterClass="com.longfor.gaia.gfs.core.utils.IPAddressConverter" />
<conversionRule conversionWord="m" converterClass="com.property.commons.log.sensitive.SensitiveMessageConverter"/>
<springProperty scope="context" name="spring_application_name" source="spring.application.name" />
<springProperty scope="context" name="server_port" source="server.port" />
<springProperty scope="context" name="LOG_HOME" source="logging.path" defalutValue="../logs"/>
<springProperty scope="context" name="env" source="env" defalutValue="sit"/>
<springProperty scope="context" name="console_switch" source="log.console.switch" defalutValue="false"/>
<property name="CONSOLE_LOG_PATTERN" value="%clr(${spring_application_name}){cyan}||%clr(%d{ISO8601}){faint}|%clr(%p)|%X{requestId}|%X{X-B3-TraceId:-}|%X{requestIp}|%X{userIp}|%ip|${server_port}|${PID}|%clr(%t){faint}|%clr(%.40logger{39}){cyan}.%clr(%method){cyan}:%L|%m|%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<property name="FILE_LOG_PATTERN" value="${spring_application_name}||%d{ISO8601}|%p|%X{requestId}|%X{X-B3-TraceId:-}|%X{requestIp}|%X{userIp}|%ip|${server_port}|${PID}|%t|%.40logger{39}.%method:%L|%m|%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<springProperty scope="context" name="spring_application_name" source="spring.application.name" />
<springProperty scope="context" name="kafka_enabled" source="longfor.web.logging.kafka.enabled"/>
<springProperty scope="context" name="kafka_broker" source="longfor.web.logging.kafka.broker"/>
<springProperty scope="context" name="kafka_env" source="longfor.web.logging.kafka.env"/>
<!--定义日志文件大小 超过这个大小会压缩归档 -->
<property name="DEBUG_MAX_FILE_SIZE" value="100MB"/>
<property name="INFO_MAX_FILE_SIZE" value="100MB"/>
<property name="ERROR_MAX_FILE_SIZE" value="100MB"/>
<property name="TRACE_MAX_FILE_SIZE" value="100MB"/>
<property name="WARN_MAX_FILE_SIZE" value="100MB"/>
<!--定义日志文件最长保存时间 -->
<property name="DEBUG_MAX_HISTORY" value="9"/>
<property name="INFO_MAX_HISTORY" value="9"/>
<property name="ERROR_MAX_HISTORY" value="9"/>
<property name="TRACE_MAX_HISTORY" value="9"/>
<property name="WARN_MAX_HISTORY" value="9"/>
<!--定义归档日志文件最大保存大小,当所有归档日志大小超出定义时,会触发删除 -->
<property name="DEBUG_TOTAL_SIZE_CAP" value="5GB"/>
<property name="INFO_TOTAL_SIZE_CAP" value="5GB"/>
<property name="ERROR_TOTAL_SIZE_CAP" value="5GB"/>
<property name="TRACE_TOTAL_SIZE_CAP" value="5GB"/>
<property name="WARN_TOTAL_SIZE_CAP" value="5GB"/>
<!-- 按照每天生成日志文件 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前Log文件名 -->
<file>${LOG_HOME}/debug.log</file>
<!-- 压缩备份设置 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/backup/debug/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>${DEBUG_MAX_HISTORY}</maxHistory>
<maxFileSize>${DEBUG_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${DEBUG_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前Log文件名 -->
<file>${LOG_HOME}/info.log</file>
<!-- 压缩备份设置 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/backup/info/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>${INFO_MAX_HISTORY}</maxHistory>
<maxFileSize>${INFO_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${INFO_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前Log文件名 -->
<file>${LOG_HOME}/warn.log</file>
<!-- 压缩备份设置 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/backup/warn/warn.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>${WARN_MAX_HISTORY}</maxHistory>
<maxFileSize>${WARN_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${WARN_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前Log文件名 -->
<file>${LOG_HOME}/error.log</file>
<!-- 压缩备份设置 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/backup/error/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>${ERROR_MAX_HISTORY}</maxHistory>
<maxFileSize>${ERROR_MAX_FILE_SIZE}</maxFileSize>
<totalSizeCap>${ERROR_TOTAL_SIZE_CAP}</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_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="KAFKA" class="com.github.danielwegener.logback.kafka.KafkaAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<topic>${kafka_env}applog_${spring_application_name}</topic>
<keyingStrategy class="com.github.danielwegener.logback.kafka.keying.NoKeyKeyingStrategy" />
<deliveryStrategy class="com.github.danielwegener.logback.kafka.delivery.AsynchronousDeliveryStrategy" />
<producerConfig>bootstrap.servers=${kafka_broker}</producerConfig>
<!-- don't wait for a broker to ack the reception of a batch. -->
<producerConfig>acks=0</producerConfig>
<!-- wait up to 1000ms and collect log messages before sending them as a batch -->
<producerConfig>linger.ms=1000</producerConfig>
<!-- even if the producer buffer runs full, do not block the application but start to drop messages -->
<producerConfig>max.block.ms=0</producerConfig>
<!-- Optional parameter to use a fixed partition -->
<!-- <partition>8</partition>-->
</appender>
<appender name="KAFKA_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="KAFKA" />
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
<if condition='"true".equals(property("kafka_enabled"))'>
<then>
<appender-ref ref="KAFKA_ASYNC"/>
</then>
</if>
</root>
</configuration>
8、如何使用@SLF4j注解进行日志记录
在使用@Slf4j注解之前,请确保项目中已经添加了SLF4J和Logback的相关依赖。使用@Slf4j注解可以简化日志记录的过程,自动为类生成一个名为log的Logger实例,开发者无需手动创建Logger对象。
下面是一个简单的示例:
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyService {
public void execute() {
log.info("执行方法开始");
try {
// 执行一些操作
} catch (Exception e) {
log.error("发生错误", e);
}
log.info("执行方法结束");
}
}
在这个示例中,我们通过@Slf4j注解为MyService类添加了一个Logger实例。然后,在execute方法中,我们使用log.info和log.error记录了不同级别的日志信息。这种方式不仅简化了代码,还提高了代码的可读性。