Log日志配置
前言
Github:https://github.com/HealerJean
一、日志框架的选择
1、日志选择
Slf4j
是为Java
提供的简单日志门面。它允许用户以自己的喜好,在工程中通过Slf4j
接入不同的日志系统。现在常用的是
logback
和log4j2
,其中logback
是slf4j
的原生实现框架,与log4j
相比性能更加出众,
spring boot
默认是支持logback
的;log4j2
出生更晚,已经不仅仅是og4j
的升级,它还参考了logback
的设计,并且据说在异步方面性能更加出众。如果需要用log4j2
,需要在相关的 `spring
包里剔除log
相关的依赖。
<!-- Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 如果在使用自带tomcat请注释下面,如果使用第三方tomcat不要注释下面 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
</exclusions>
</dependency>
二、Logback
1、logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--学习 https://blog.csdn.net/ZYC88888/article/details/85060315-->
<!--
格式化输出:%d表示日期,
%thread表示线程名,
%-5level:级别从左显示5个字符宽度,
%logger{50} 表示 Logger 名字最长36个字符,
%msg:日志消息,
%M : 日志输出所在方法名
%L : 日志输出所在行数
%n是换行符 -->
-->
<property name="log_pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level -[%-32X{REQ_UID}] - %msg -%logger{50}.%M[%L]%n "/>
<property name="log_path" value="/Users/zhangyujin1/Desktop/logs/hlj-logback"/>
<property name="info_file_path" value="${log_path}/hlj-logback.log"/>
<property name="error_file_path" value="${log_path}/hlj-logback-error.log"/>
<property name="biz_file_path" value="${log_path}/hlj-logback-biz.log"/>
<!--控制台-->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.hlj.proj.controller.config.LogbackJsonFilter"/>
<encoder charset="UTF-8" >
<pattern>${log_pattern}</pattern>
</encoder>
</appender>
<appender name="default_appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志文件输出的文件名 -->
<File>${info_file_path}</File>
<!--滚动日志 是基于时间和大小的滚动策略,它可以满足几乎所有的日志滚动需求场景。这种策略允许您根据日志文件的大小和时间来触发滚动,从而有效地管理日志文件。 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 滚动日志文件保存格式 i是超出文件大小MaxFileSize 讲历史日志后缀名从0开始起步,如果超过了最大的totalSizeCap,删除最旧的文件,直到满足条件-->
<FileNamePattern>${info_file_path}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!-- 单个日志文件的最大:单个文件500MB更易于查看和分析 -->
<MaxFileSize>512MB</MaxFileSize>
<!-- 所有日志文件最大: 限制日志文件占用的总磁盘空间,建议设置为磁盘目录大小的`80%`或更小,以留出一定的磁盘空间用于其他操作。这样可以避免日志文件占用过多的磁盘空间,导致系统性能下降或磁盘空间不足的问题B -->
<!-- 说明:磁盘空间共计50GB,保留30%的磁盘空间(15GB)给系统和其他应用 30G-->
<totalSizeCap>30GB</totalSizeCap>
<!--日志最大的历史 10天 -->
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<!-- 按临界值过滤日志:低于INFO以下级别被抛弃 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>${log_pattern}</pattern>
</encoder>
</appender>
<appender name="default_async_appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 设置异步日志队列的容量:更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>4096</queueSize>
<!-- 定义何时开始丢弃低级别日志:队列剩余容量小于discardingThreshold,则会丢弃TRACT、DEBUG、INFO级别的日志;默认值-1,为queueSize的20%;0不丢失日志 -->
<discardingThreshold>-1</discardingThreshold>
<!-- 是否阻塞,默认:false,如果系统异常之后,error级别的日志把队列占满了,会出现阻塞情况。可以设置为true,丢弃日志,从而不影响业务线程。 -->
<neverBlock>true</neverBlock>
<!--是否收集调用者信息(类名、方法名、行号等)-->
<includeCallerData>true</includeCallerData>
<!-- 一个异步appender 只能对应一个同步的appender。 -->
<appender-ref ref="default_appender"/>
</appender>
<appender name="error_appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${error_file_path}</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${error_file_path}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!-- 单个日志文件的最大:单个文件500MB更易于查看和分析 -->
<MaxFileSize>512MB</MaxFileSize>
<!-- 所有日志文件最大: 限制日志文件占用的总磁盘空间,建议设置为磁盘目录大小的`80%`或更小,以留出一定的磁盘空间用于其他操作。这样可以避免日志文件占用过多的磁盘空间,导致系统性能下降或磁盘空间不足的问题B -->
<!-- 说明:磁盘空间共计50GB,保留30%的磁盘空间(15GB)给系统和其他应用 30G-->
<totalSizeCap>30GB</totalSizeCap>
<!--日志最大的历史 10天 -->
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>${log_pattern}</pattern>
</encoder>
</appender>
<appender name="async_error_appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 设置异步日志队列的容量:更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>4096</queueSize>
<!-- 定义何时开始丢弃低级别日志:队列剩余容量小于discardingThreshold,则会丢弃TRACT、DEBUG、INFO级别的日志;默认值-1,为queueSize的20%;0不丢失日志 -->
<discardingThreshold>-1</discardingThreshold>
<!-- 是否阻塞,默认:false,如果系统异常之后,error级别的日志把队列占满了,会出现阻塞情况。可以设置为true,丢弃日志,从而不影响业务线程。 -->
<neverBlock>true</neverBlock>
<!--是否收集调用者信息(类名、方法名、行号等)-->
<includeCallerData>true</includeCallerData>
<!-- 一个异步appender 只能对应一个同步的appender。 -->
<appender-ref ref="error_appender"/>
</appender>
<!--自定义业务日志目录-->
<appender name="biz_appender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${biz_file_path}</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${biz_file_path}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<!-- 单个日志文件的最大:单个文件500MB更易于查看和分析 -->
<MaxFileSize>512MB</MaxFileSize>
<!-- 所有日志文件最大: 限制日志文件占用的总磁盘空间,建议设置为磁盘目录大小的`80%`或更小,以留出一定的磁盘空间用于其他操作。这样可以避免日志文件占用过多的磁盘空间,导致系统性能下降或磁盘空间不足的问题B -->
<!-- 说明:磁盘空间共计50GB,保留30%的磁盘空间(15GB)给系统和其他应用 30G-->
<totalSizeCap>30GB</totalSizeCap>
<!--日志最大的历史 10天 -->
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${log_pattern}</pattern>
</encoder>
</appender>
<appender name="async_biz_appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 设置异步日志队列的容量:更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>4096</queueSize>
<!-- 定义何时开始丢弃低级别日志:队列剩余容量小于discardingThreshold,则会丢弃TRACT、DEBUG、INFO级别的日志;默认值-1,为queueSize的20%;0不丢失日志 -->
<discardingThreshold>-1</discardingThreshold>
<!-- 是否阻塞,默认:false,如果系统异常之后,error级别的日志把队列占满了,会出现阻塞情况。可以设置为true,丢弃日志,从而不影响业务线程。 -->
<neverBlock>true</neverBlock>
<!--是否收集调用者信息(类名、方法名、行号等)-->
<includeCallerData>true</includeCallerData>
<!-- 一个异步appender 只能对应一个同步的appender。 -->
<appender-ref ref="biz_appender"/>
</appender>
<!-- 自定义输出 -->
<appender name="mq_appender" class="com.hlj.proj.controller.log.plugin.MqAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
<marker>marker_mq_log</marker>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 日志收集最低日志级别 -->
<level>INFO</level>
</filter>
<layout
class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</layout>
<!-- 自定义参数 -->
<params>healer_jean:</params>
</appender>
<logger name="biz_log" additivity="false" level="INFO">
<appender-ref ref="async_biz_appender"/>
</logger>
<!--以配置文件application.properties 中为主,如果配置文件中不存在以它为主-->
<root level="debug">
<appender-ref ref="stdout" />
<appender-ref ref="default_async_appender"/>
<appender-ref ref="async_error_appender"/>
<appender-ref ref="mq_appender"/>
</root>
</configuration>
2、Logback
日志到数据库
1)创建数据库表
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
)
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(150) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
2)日志配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!--日志异步到数据库 -->
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
<!--日志异步到数据库-->
<connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
<driverClass>com.mysql.jdbc.Driver</driverClass>
<url>jdbc:mysql://localhost:3306/healerjean?useUnicode=true&allowMultiQueries=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false</url>
<user>healerjean</user>
<password>healerjean</password>
</connectionSource>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="DB"/>
</root>
</configuration>
3)查看日志
SELECT * from logging_event;
3、常见功能
1)LogBack
打印 Json
数据
<!--控制台-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.hlj.proj.controller.config.LogbackJsonFilter"/>
<encoder charset="UTF-8" >
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
package com.hlj.proj.controller.config;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;
import com.hlj.proj.controller.utils.JsonUtils;
public class LogbackJsonFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if (event.getLoggerName().startsWith("com.hlj")) {
Object[] params = event.getArgumentArray();
for (int index = 0; index < params.length; index++) {
Object param = params[index];
// class.isPrimitive() 8种基本类型的时候为 true,其他为false
if (!param.getClass().isPrimitive()) {
params[index] = JsonUtils.toJsonString(param);
}
}
}
return FilterReply.ACCEPT;
}
}
2)行号打印是问号
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!- 加上下面这行 -->
<includeCallerData>true</includeCallerData>
</appender>
3)自定义 Appender
发送到 MQ
@EqualsAndHashCode(callSuper = true)
@Data
public class MqAppender extends UnsynchronizedAppenderBase<ILoggingEvent>{
/**
* layout
*/
private Layout<ILoggingEvent> layout;
/**
* params
*/
private String params;
/**
*
*/
@Override
public void start(){
//这里可以做些初始化判断 比如layout不能为null ,
if(layout == null) {
addWarn("Layout was not defined");
}
//或者写入数据库 或者redis时 初始化连接等等
super.start();
}
@Override
public void stop()
{
//释放相关资源,如数据库连接,redis线程池等等
System.out.println("logback-stop方法被调用");
if(!isStarted()) {
return;
}
super.stop();
}
@Override
public void append(ILoggingEvent event) {
if (event == null || !isStarted()){
return;
}
// 此处自定义实现输出
// 日志信息
String logInfo = layout.doLayout(event);
// 打印信息
String message = event.getMessage();
System.out.print(params + ":" + logInfo);
System.out.print(params + ":" + message);
}
}
<!-- 自定义输出 -->
<appender name="MQ_APPENDER" class="com.hlj.proj.controller.log.plugin.MqAppender">
<filter class="ch.qos.logback.core.filter.EvaluatorFilter">
<evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
<marker>MARKER_MQ_LOG</marker>
</evaluator>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 日志收集最低日志级别 -->
<level>INFO</level>
</filter>
<layout
class="ch.qos.logback.classic.PatternLayout">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</layout>
<!-- 自定义参数 -->
<params>HEALER_JEAN:</params>
</appender>
<!--以配置文件application.properties 中为主,如果配置文件中不存在以它为主-->
<root level="debug">
<appender-ref ref="STDOUT" />
<appender-ref ref="ASYNC_ERROR_FILE"/>
<appender-ref ref="ASYNC_FILE_INFO"/>
<appender-ref ref="MQ_APPENDER"/>
</root>
4)自定义某个 log
到固定文件
private static final Logger BIZ_LOG = LoggerFactory.getLogger("BIZ_LOG");
<!--自定义业务日志目录-->
<appender name="BIZ_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${FILE_BIZ}</File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${FILE_BIZ}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxFileSize>60MB</MaxFileSize>
<totalSizeCap>5GB</totalSizeCap>
<MaxHistory>10</MaxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="ASYNC_BIZ_FILE" class="ch.qos.logback.classic.AsyncAppender">
<!-- 1. 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>8192</queueSize>
<!-- 2. 队列剩余容量小于discardingThreshold,则会丢弃TRACT、DEBUG、INFO级别的日志;默认值-1,为queueSize的20%;0不丢失日志 -->
<discardingThreshold>-1</discardingThreshold>
<!-- 3. 默认:false,如果系统异常之后,error级别的日志把队列占满了,会出现阻塞情况。可以设置为true,丢弃日志,从而不影响业务线程。 -->
<neverBlock>true</neverBlock>
<!--是否提取调用者数据-->
<includeCallerData>false</includeCallerData>
<!-- 一个异步appender 只能对应一个同步的appender。 -->
<appender-ref ref="BIZ_FILE"/>
</appender>
<logger name="BIZ_LOG" additivity="false" level="INFO">
<appender-ref ref="BIZ_FILE"/>
</logger>
4、使用说明
1)LOGBACK
_日志滚动建议:
推荐使用
sizeAndTimeBasedRollingPolicy
,maxFileSize
小于等于1G
,totalSizeCap
小于等于export
目录大小80%
⬤ SizeAndTimeBasedRollingPolicy
是基于时间和大小的滚动策略,它可以满足几乎所有的日志滚动需求场景。这种策略允许您根据日志文件的大小和时间来触发滚动,从而有效地管理日志文件。
⬤ maxFileSize
:建议设置为小于等于 1G
,以确保单个日志文件不会过大,从而便于管理和分析。
⬤ totalSizeCap
:建议设置为 export
目录大小的 80%
或更小,以留出一定的磁盘空间用于其他操作。这样可以避免日志文件占用过多的磁盘空间,导致系统性能下降或磁盘空间不足的问题。
2)Appender
:
a、多个 Appender
说明
不能为以下
Appender
类型(ConsoleAppender
,ServerSocketAppender
,SMTPAppender
,SocketAppender
,SSLSocketAppender
,DBAppender
,SSLServerSocketAppender
,SyslogAppende
r)
⬤ ServerSocketAppender
、SocketAppender
、SSLSocketAppender
:这些 Appender
通过网络发送日志数据,可能存在网络安全和性能问题。此外,它们依赖于网络连接的稳定性,如果网络出现故障,可能会导致日志丢失。
⬤ SMTPAppender
:通过电子邮件发送日志数据,不适合用于大量日志的实时记录。此外,电子邮件的发送可能会受到邮件服务器和网络等因素的影响,导致日志发送失败或延迟。
⬤ DBAppender
:将日志记录到数据库中,可能会增加数据库的负载和复杂性。此外,如果数据库出现故障或性能问题,可能会影响日志的可靠性和完整性。
⬤ SyslogAppender
:虽然 Syslog
是一种标准的日志记录协议,但 SyslogAppender
的配置和使用可能相对复杂,且依赖于Syslog
服务器的可用性和性能。
⬤ ConsoleAppender
将日志输出到控制台,这一特性在开发和调试阶段是非常有用的,因为它允许开发人员即时看到应用程序的日志输出,从而快速定位和解决问题。然而,在生产环境中,这种日志记录方式存在几个主要的缺点,使得它不太适合:日志管理困难,日志访问限制:日志量处理:
b、日志级别优先级
1、避免冗余配置:如果
root level
和filter
的级别相同(如都是INFO
),可以删除<filter>
2、需要差异化过滤时:如果某些
Appender
需要特殊级别(如一个记录INFO
,另一个记录ERROR
),才需要<filter>
<root level="INFO"> <!-- 全局最低门槛 -->
<appender-ref ref="FILE_INFO"/> <!-- 记录 INFO+ -->
<appender-ref ref="FILE_ERROR"/> <!-- 记录 ERROR -->
</root>
<appender name="FILE_ERROR" class="...">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level> <!-- 覆盖 root 的 INFO -->
</filter>
</appender>
配置位置 | 作用范围 | 优先级 | 最终效果 |
---|---|---|---|
<root level> |
全局日志级别门槛 | 高 | 先过滤掉低于设定级别的日志 |
<filter> |
单个 Appender 级别 |
低 | 在 Appender 层面二次过滤 |
场景 | root |
filter |
结果 |
---|---|---|---|
如果 <root level> 比 <filter> 更严格 |
WARN |
INFO |
1、只有 WARN/ERROR 会被记录(以 root level 为准)2、 INFO 日志根本不会到达 Appender ,<filter> 的配置无意义 |
如果 <filter> 比 <root level> 更严格 |
DEBUG |
INFO |
1、日志先通过 root level 放行 DEBUG/INFO/WARN/ERROR 2、到达 Appender 后,<filter> 会过滤掉 DEBUG ,最终记录 INFO/WARN/ERROR |
c、日志记录参数
- 日志文件达到
MaxFileSize
(如1GB
)后会滚动(生成新文件)。- 超出
MaxHistory
(如5
天)的旧文件会被删除。- 如果总大小仍超过
totalSizeCap
,继续删除最旧的文件,直到满足条件。
参数 | 作用 | 与 totalSizeCap 的关系 |
---|---|---|
MaxFileSize |
单个日志文件的最大大小(如 1GB ) |
影响单个文件大小,但不影响总日志量 |
MaxHistory |
保留的历史日志天数(如 5 天) |
按时间清理旧文件,但可能仍超出 totalSizeCap |
totalSizeCap |
所有日志文件的总大小上限 | 最终兜底,确保磁盘不会爆满 |
d、ThresholdFilter
ThresholdFilter
是 Logback 的日志级别过滤器,参数说明:
level
:过滤的阈值级别onMatch
:当日志级别匹配阈值时的动作(ACCEPT
/DENY
/NEUTRAL
)onMismatch
:当日志级别不匹配阈值时的动作
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
参数 | 说明 |
---|---|
level : |
过滤的阈值级别 |
onMatch : |
当日志级别匹配阈值时的动作(ACCEPT /DENY /NEUTRAL ) |
onMismatch : |
当日志级别不匹配阈值时的动作 |
第一个过滤器:
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
行为:
- 如果日志级别 ≥
ERROR
:ACCEPT
(立即通过) - 其他级别(
WARN
/INFO
/DEBUG
等):NEUTRAL
(交给下一个过滤器决定)
第二个过滤器:
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
行为:
- 如果日志级别 ≥
INFO
:ACCEPT
(通过) - 其他级别(
DEBUG
/TRACE
):DENY
(拒绝)
3、 组合后的实际效果
日志级别 | 第一个过滤器结果 | 第二个过滤器结果 | 最终结果 |
---|---|---|---|
ERROR |
ACCEPT |
- | 通过 |
WARN |
NEUTRAL |
ACCEPT (WARN ≥INFO ) |
通过 |
INFO |
NEUTRAL |
ACCEPT |
通过 |
DEBUG |
NEUTRAL |
DENY (DEBUG < INFO ) |
拒绝 |
TRACE |
NEUTRAL |
DENY |
拒绝 |
结论:
- 该配置会记录
INFO
/WARN
/ERROR
级别的日志,拒绝DEBUG
/TRACE
。 - 第一个过滤器对
ERROR
的快速通过是优化(减少不必要的判断)。
4、生产建议:
1)如果不需要特殊处理 ERROR:直接用单个 ThresholdFilter
更简洁:
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
2)分级日志处理:如果需要区分 ERROR
和其他级别(例如发送告警):使用 LevelFilter
更合适-**:
ERROR
日志 → 进入error-alert.log
INFO/WARN
日志 → 进入app.log
DEBUG/TRACE
→ 被完全过滤掉
<appender name="ERROR_ALERT" class="ch.qos.logback.core.FileAppender">
<file>error-alert.log</file>
<!-- 只接受 ERROR -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
...
</appender>
<appender name="MAIN_LOG" class="ch.qos.logback.core.FileAppender">
<file>app.log</file>
<!-- 先过滤 ERROR -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch> <!-- ERROR 已经由 ERROR_ALERT 处理了 -->
<onMismatch>NEUTRAL</onMismatch>
</filter>
<!-- 然后处理其他级别 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
...
</appender>
3)AsyncAppender
参数详解
参数 | 作用 | 默认值 | 说明 |
---|---|---|---|
queueSize |
设置异步日志队列的容量 | 256 |
1、值越大,能缓冲的日志消息越多,在高负载时减少日志丢失 2、但会消耗更多内存(每条日志消息都会占用内存) 3、8192 是一个较大的值,适合高吞吐量系统,但需注意内存使用 |
discardingThreshold |
定义何时开始丢弃低级别日志 | -1(表示队列大小的 20% ) |
1、当队列剩余空间小于阈值时,会丢弃TRACE 、DEBUG 、INFO级 别的日志 2、设置为 0 表示永不丢弃日志 3、 -1 表示当队列剩余空间小于20% (8192 ×20% =1638 )时开始丢弃低级别日志 |
neverBlock |
是否阻塞 | 1、false (默认):队列满时,生产者线程会阻塞,直到有空间可用 2、 true :队列满时直接丢弃新日志,不阻塞业务线程 3、设置为 true 可避免日志系统影响业务性能,但可能丢失重要日志 |
|
includeCallerData |
是否收集调用者信息(类名、方法名、行号等) | false |
1、true 会显著降低性能(因为要获取堆栈信息) 2、 false 性能更好,但日志中不会有调用者详细信息 3、生产环境通常设为 false ,开发环境可设为true |
appender-ref |
指定实际执行日志写入的同步 Appender |
1、每个AsyncAppender只能引用一个同步Appender 2、同步 Appender (如FileAppender )负责最终的日志写入操作 |
常规场景:
<queueSize>2048</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>false</neverBlock>
高性能场景(接受少量日志丢失):
<queueSize>4096</queueSize>
<discardingThreshold>1024</discardingThreshold>
<neverBlock>true</neverBlock>
高可靠性场景(不允许丢失日志):
<queueSize>1024</queueSize>
<discardingThreshold>0</discardingThreshold>
<neverBlock>false</neverBlock>
a、queueSize
一、如何合理设置:
1、一般情况:在大多数应用场景中,queueSize
的 默认值(通常是256
)可能就足够了。如果应用程序的日志输出量适中,没有大量的并发日志请求,默认值可以保证日志的异步处理能够平滑进行。
2、高并发场景:对于高并发的应用程序,例如每秒有大量请求的 Web
服务,可能需要将queueSize
设置得更大,比如1024
或更高。这样可以避免队列在高并发下迅速填满,导致日志丢失或阻塞应用程序的执行。
3、根据系统资源调整:还需要考虑服务器的资源情况。如果服务器有足够的内存,并且希望尽可能提高日志处理的性能,可以适当增大queueSize
。但如果内存有限,过大的queueSize
可能会导致内存溢出。
二、如何观察
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.async.AsyncAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class LogbackAsyncAppenderMonitor {
private static final Logger logger = LoggerFactory.getLogger(LogbackAsyncAppenderMonitor.class);
private static final String APPENDER_NAME = "AsyncAppender";
private static final long MONITOR_INTERVAL = 5; // 监控间隔,单位:秒
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(() -> monitorAsyncAppenderQueue(), 0, MONITOR_INTERVAL,
TimeUnit.SECONDS);
}
private static void monitorAsyncAppenderQueue() {
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
Appender<ILoggingEvent> appender = context.getLoggerList().get(0).getAppender(APPENDER_NAME);
if (appender instanceof AsyncAppender) {
AsyncAppender<ILoggingEvent> asyncAppender = (AsyncAppender<ILoggingEvent>) appender;
// 这里通过反射获取队列大小,因为Logback的AsyncAppender没有直接提供获取队列大小的方法
try {
java.lang.reflect.Field queueField = asyncAppender.getClass().getDeclaredField("queue");
queueField.setAccessible(true);
java.util.concurrent.BlockingQueue<?> queue = (java.util.concurrent.BlockingQueue<?>)
queueField.get(asyncAppender);
int queueSize = queue.size();
int remainingCapacity = queue.remainingCapacity();
logger.info("AsyncAppender 队列大小: {}, 剩余容量: {}", queueSize, remainingCapacity);
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.error("获取AsyncAppender队列信息失败", e);
}
}
}
}
4)logger
a、additivity
additivity
是Logback
中控制 日志事件是否向上传递 的重要属性,主要影响日志的继承性和重复输出问题。:合理使用additivity
可以精准控制日志流向,避免重复记录和资源浪费。定义:是否将日志事件传递给父 Logger(通常是
root
Logger)。取值:
true
(默认值):日志事件会传递给当前Logger
和所有祖先Logger
。false
:日志事件 仅由当前Logger
处理,不向上传递。
场景 | 推荐设置 | 效果 |
---|---|---|
需要日志继承 | additivity="true" (默认) |
日志会传递给父 Logger |
避免重复输出 | additivity="false" |
日志仅由当前 Logger 处理 |
定制化日志存储 | additivity="false" |
特定包的日志独立存储 |
(1) 默认情况 (additivity="true"
)
<logger name="com.example.service" level="DEBUG">
<appender-ref ref="SERVICE_FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
当 com.example.service
包下的代码打印日志时:
- 先由
SERVICE_FILE
Appender
处理(记录到文件)。 - 继续传递给
root
Logger,由STDOUT
Appender
再次处理(控制台输出)。
结果:同一条日志会同时出现在文件和控制台(可能重复)。
(2) 禁用继承 (additivity="false"
)
<logger name="com.example.service" level="DEBUG" additivity="false">
<appender-ref ref="SERVICE_FILE"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
当 com.example.service
包下的代码打印日志时:
- 日志事件 仅由
SERVICE_FILE
Appender
处理,不会传递给root
。
结果:日志只写入文件,不会输出到控制台。
(3) 错误配置:日志不会输出到 default_async_appender
<logger name="com.jd.merchant.sign" level="INFO" additivity="false" />
<root level="INFO">
<appender-ref ref="default_async_appender"/>
</root>
三、Log4j
1、log4j.properties
## 必填内容,info/all/.., stdout 为必填,后面的根据log4j.appender.内容进行填写,如果下面有内容则,这里必须加上,
# level 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。
log4j.rootLogger=info, stdout, log, errorlog,proj
#%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
#%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
#%t: 输出产生该日志事件的线程名
#%C: 输出日志信息所属的类目,通常就是所在类的全名
#%M: 输出代码中指定的消息,产生的日志具体信息
#%F: 输出日志消息产生时所在的文件名称
#%L: 输出代码中的行号
#%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
#%r: 输出自应用启动到输出该log信息耗费的毫秒数
#%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
#%%: 输出一个”%”字符
#%n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行
#%hostName : 本地机器名
#%hostAddress : 本地ip地址-->
#可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
#1) c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
#2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,”-”号指定左对齐。
#3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
#4) .30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉
# RollingFileAppender按log文件最大长度限度生成新文件
# DailyRollingFileAppender按日期生成新文件,不能根据大小清除历史日志,但是我们可以自定义来实现
# %d{yyyy-MM-dd HH:mm:ss,SSS}日期 %p級別 %t当前线程名称 %m日志信息 [%C.%M]类名加方法 %L行数 %n换行
# 举例 # 2019-07-13 09:05:14,674 [INFO]-[http-nio-8888-exec-1] info日志================== com.hlj.proj.controler.Log4jController.log4j][25]
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n
## 根据日期生成配置文件当前log.log 如果时间超过了设置的格式的时间DatePattern 则会在后面加上 log.log.2019-07-12.log
# 解释:也就是说log文件会暂存每天的日志,到第二天时会再加上yyyy-MM,产生当天的完整日志文件
### Log info
log4j.appender.log = org.apache.log4j.DailyRollingFileAppender
log4j.appender.log.File = /Users/healerjean/Desktop/logs/hlj-log4j.log
log4j.appender.log.Append = true
log4j.appender.log.Threshold = INFO
#超过日期则讲历史日志加上后缀日期用于区分
log4j.appender.log.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.log.layout = org.apache.log4j.PatternLayout
log4j.appender.log.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n
## 5 按照文件大小进行日志切分 文件历史日志依次 error.log.1 error.log.2
log4j.appender.errorlog=org.apache.log4j.RollingFileAppender
log4j.appender.errorlog.File=/Users/healerjean/Desktop/logs/error.log
log4j.appender.errorlog.Append=true
log4j.appender.errorlog.Threshold=error
#设置日志文件的大小
log4j.appender.errorlog.MaxFileSize=2000KB
#保存200个备份文件
log4j.appender.errorlog.MaxBackupIndex=200
log4j.appender.errorlog.layout=org.apache.log4j.PatternLayout
log4j.appender.errorlog.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L %n
log4j.appender.proj=com.hlj.proj.utils.RoolingAndDateFileAppender
log4j.appender.proj.file=/Users/healerjean/Desktop/logs/logRecoed.log
log4j.appender.proj.Append=true
log4j.appender.proj.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.proj.Threshold=error
#设置日志文件的大小
log4j.appender.proj.MaxFileSize=5KB
#最大保留多少个文件,超过之后会进行重新命名,所以尽量不要超过
log4j.appender.proj.maxIndex=10
#只保留多长时间的
log4j.appender.proj.expirDays=1
log4j.appender.proj.layout=org.apache.log4j.PatternLayout
log4j.appender.proj.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%p]-[%t] %m %C.%M][%L] %n
四、Log4j2
log4j
一直存在两个问题,一是打日志影响到系统性能效率,二是有多线程的时候,日志会比较乱
log4j2
是log4j 1.x
的升级版,参考了logback
的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有:
异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,提供了一些异常处理机制。
性能提升, log4j2相较于log4j 1和logback都具有很明显的性能提升,后面会有官方测试的数据。
自动重载配置,参考了logback的设计,当然会提供自动刷新参数配置,最实用的就是我们在生产上可以动态的修改日志的级别而不需要重启应用——那对监控来说,是非常敏感的。
无垃圾机制,log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的jvm gc。
1、log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--status Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,
你会看到log4j2内部各种详细输出。可以设置成OFF(关闭)或Error(只输出错误信息)-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="error" monitorInterval="30">
<!-- %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间-->
<!-- %p : 日志输出格式-->
<!-- %thread表示线程名,-->
<!-- %c : logger的名称-->
<!-- %m : 日志内容,即 logger.info("message")-->
<!-- %n : 换行符-->
<!-- %C : Java类名-->
<!-- %L : 日志输出所在行数-->
<!-- %M : 日志输出所在方法名-->
<!-- hostName : 本地机器名-->
<!-- hostAddress : 本地ip地-->
<!-- 日志文件目录和压缩文件目录配置 -->
<Properties>
<Property name="level">info</Property>
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level -[%-32X{REQ_UID}]- %msg%xEx %logger{36}.%M[%L]%n
</Property>
<Property name="logDri">/Users/healerjean/Desktop/logs</Property>
<Property name="logFileName">hlj-client</Property>
<Property name="infoLogDri">${logDri}/info</Property>
<Property name="infoLogGz">${infoLogDri}/gz</Property>
<Property name="infoLogFileName">${logFileName}.log</Property>
<Property name="errorLogDri">${logDri}/error</Property>
<Property name="errorLogGz">${errorLogDri}/gz</Property>
<Property name="errorLogFileName">${logFileName}.error</Property>
</Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
<ThresholdFilter level="${level}" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<!--输出日志的格式-->
<PatternLayout pattern="${LOG_PATTERN}"/>
</console>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingRandomAccessFile name="infoFile" fileName="${infoLogDri}/${infoLogFileName}"
filePattern="${infoLogGz}/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.${infoLogFileName}.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!-- 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小 -->
<SizeBasedTriggeringPolicy size="500 MB"/>
<!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour -->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
</Policies>
<Filters>
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)
onMatch属性设置为DENY,过滤掉高等级的日志;onMismatch设置为NEUTRAL,把低等级的日志放行,
-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<!-- 指定每天(文件夹是以天的,看上面的)的最大压缩包个数,默认7个,超过了会覆盖之前的(用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)) -->
<DefaultRolloverStrategy max="2000"/>
</RollingRandomAccessFile>
<!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
<RollingRandomAccessFile name="errorFile" fileName="${errorLogDri}/${errorLogFileName}"
filePattern="${errorLogGz}/$${date:yyyy-MM}/%d{yyyy-MM-dd}-%i.${errorLogFileName}.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<!-- 基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小 -->
<SizeBasedTriggeringPolicy size="500 MB"/>
<!-- 基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour -->
<TimeBasedTriggeringPolicy interval="6" modulate="true"/>
</Policies>
<Filters>
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)
onMatch属性设置为DENY,过滤掉高等级的日志;onMismatch设置为NEUTRAL,把低等级的日志放行,
-->
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<!-- 指定每天(文件夹是以天的,看上面的)的最大压缩包个数,默认7个,超过了会覆盖之前的(用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)) -->
<DefaultRolloverStrategy max="2000"/>
</RollingRandomAccessFile>
</appenders>
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<!-- AsyncRoot - 异步记录日志 - 需要LMAX Disruptor的支持 -->
<!-- additivity如果设置为true将会输出两次日志,意思和log4j里面意思是否追加 -->
<AsyncRoot level="${level}" additivity="false" includeLocation="true">
<AppenderRef ref="Console"/>
<AppenderRef ref="infoFile"/>
<AppenderRef ref="errorFile"/>
</AsyncRoot>
</loggers>
</configuration>
1)日志打印位置
main方法和服务器日志都在一起
2、修改日志级别
1)修改包路径日志级别
a、properties
修改:
server.port=8888
logging.config=classpath:log4j2.xml
logging.level.com.hlj.proj.controller.Log4j2Controller: error
b、log4j2.xml
修改
<!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
<loggers>
<logger name="com.hlj.proj.controller" level="ERROR"/>
<!-- AsyncRoot - 异步记录日志 - 需要LMAX Disruptor的支持 -->
<!-- additivity如果设置为true将会输出两次日志,意思和log4j里面意思是否追加 -->
<AsyncRoot level="${level}" additivity="false" includeLocation="true">
<AppenderRef ref="Console"/>
<AppenderRef ref="infoFile"/>
<AppenderRef ref="errorFile"/>
</AsyncRoot>
<logger name="businessLog" additivity="false" level="INFO">
<appender-ref ref="businessLog"/>
</logger>
</loggers>
2)动态修改日志级别
@LogIndex
@GetMapping("updateLevel")
public String updateLevel(String name, String level) {
Configurator.setLevel(name, Level.toLevel(level));
log.debug("debug日志================{},level:{}", name,level);
log.info("info日志=================={},level:{}", name,level);
log.warn("warn日志=================={},level:{}", name,level);
log.error("error日志================{},level:{}", name,level);
return "修改日志级别成功";
}
// 全局 http://localhost:8888/hlj/updateLevel?name=&level=INFO
// 包路径 http://localhost:8888/hlj/updateLevel?name=com.hlj.proj.controller&level=DEBUG
// 测试 http://localhost:8888/hlj/log4j2