java日志基础

参考:

  1. SLF4J user manual
  2. The logback manual
  3. Log4j2 manual

日志在我们项目生命中非常重要的,不管是开发过程中还是线上问题排查都能够提供有效的信息,进而提高解决问题的效率。java生态中有许多的日志框架实现,主流的实现思想:日志门面 + 具体实现,基于此思想能够降低我们切换实现框架的成本。在日常开发中,主流的日志打印实践:SLF4J + LOG4J/LOG4J2 或 SLF4J + LOGBACKSLF4J(Simple Logging Facade for Java) 是Ceki Gülcü开发的一个日志门面接口,它为Java应用程序提供了统一的日志抽象,使开发人员可以使用一致的API进行日志记录,而不需要直接依赖于特定的日志实现。

日志门面主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由具体的日志实现框架。

日志门面 日志实现
JCL(Jakarta Commons Logging) JUL(java.util.logging)
SLF4j(Simple Logging Facade for Java) log4j、log4j2、logback

slf4j所提供的核心API是一些接口以及一个LoggerFactory的工厂类。在使用slf4j的时候,不需要在代码中或配置文件中指定你打算使用那个具体的日志系统,只需要依赖slf4j和日志实现框架以及中间桥接的jar包。在SLF4J定义了如下日志级别:

  • TRACE
    是最低级别的日志记录,用于输出最详细的调试信息,通常用于开发调试目的。在生产环境中,应该关闭 TRACE 级别的日志记录,以避免输出过多无用信息。
  • DEBUG
    是用于输出程序中的一些调试信息,通常用于开发过程中。像 TRACE 一样,在生产环境中应该关闭 DEBUG 级别的日志记录。
  • INFO
    用于输出程序正常运行时的一些关键信息,比如程序的启动、运行日志等。通常在生产环境中开启 INFO 级别的日志记录。
  • WARN
    是用于输出一些警告信息,提示程序可能会出现一些异常或者错误。在应用程序中,WARN 级别的日志记录通常用于记录一些非致命性异常信息,以便能够及时发现并处理这些问题。
  • ERROR
    是用于输出程序运行时的一些错误信息,通常表示程序出现了一些不可预料的错误。在应用程序中,ERROR 级别的日志记录通常用于记录一些致命性的异常信息,以便能够及时发现并处理这些问题。

logback

Logback分为三个模块,logback-core、logback-classic 和 logback-access。logback-core模块为其他两个模块奠定了基础,logback-classic模块扩展了core模块,相当于log4j的一个显著改进版本。logback-classic原生实现了slf4j API,可以和其他日志系统随意地来回切换。

  • Logger(消息类型和级别来记录消息)
  • Appender(输出目的)
  • Layout(输出格式)

Logger 类作为 logback-classic 模块的一部分。Appender 与 Layouts 接口作为 logback-core 的一部分。作为一个通用的模块,logback-core 没有 logger 的概念。

Logger

在日志实现中通过定义一个日志空间,这个空间包含所有可能的日志语句,这些日志语句根据具体配置的标准来进行分类。在 logback-classic 中,分类是 logger 的一部分,每一个 logger 都依附在 LoggerContext 上,它负责产生 logger,并且通过一个树状的层级结构来进行管理。Logback 的 logger 层级是继承的,logger 的层级是树状结构,从最顶层的 root logger 开始,然后是它的子 logger,子 logger 的子 logger,以此类推。

Appender

logback 允许日志以不同的方式进行输出。logback 输出目的地叫做 appender,appender 包括 console、file、remote socket server、MySQL、PostgreSQL、Oracle 或者其它的数据库、JMS、remote UNIX Syslog daemons 中,一个 logger 可以有多个 appender。

logger 通过 addAppender 方法来新增一个 appender。对于给定的 logger,每一个允许输出的日志都会被转发到该 logger 的所有 appender 中去,即 appender 从 logger 的层级结构中去继承叠加性。例如:如果 root logger 添加了一个 console appender,所有允许输出的日志至少会在控制台打印出来。定义一个 api 的 logger 添加了一个 file appender,那么 api 以及 api 的子级 logger 都可以在文件和控制台打印日志,可以通过设置 additivity = false 来改写默认的设置,这样 appender 将不再具有叠加性。

Layout

layout 的作用是将日志格式化,而 appender 的作用是将格式化后的日志输出到指定的目的地,PatternLayout 能够根据用户指定的格式来格式化日志。如:

1
2
<property name="LOG_FORMAT"
value="[%X{traceId}] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>

Pattern Layout参数:

  • c{length}/lo{length}/logger{length}: 输出 logger 的名字作为日志事件的来源。转换字符接收一个作为它的第一个也是为一个参数。
  • C{length}/class{length}: 输出发出日志请求的类的全限定名称。跟 %logger% 转换符一样,它也可以接收一个整型的可选参数去缩短类名。0 表示特殊含义,在打印类名时将不会输出包的前缀名。默认表示打印类的全限定名。生成调用者类的信息并不是特别快。
  • contextName/cn: 输出日志事件附加到的 logger 上下文的名字。
  • d{pattern}/date{pattern}/d{pattern, timezone}/date{pattern, timezone}: 用于输出日志事件的日期。
  • F / file: 输出日志事件的文件名。
  • L / line:输出日志事件的行号。生成行号不是特别快。因此,不建议使用,除非生成速度不是问题。
  • m / msg / message: 输出与日志事件相关联的,由应用程序提供的日志信息。
  • M / method:输出发出日志请求的方法名。生成方法名不是特别快,因此,应该避免使用,除非生成速度不是问题。
  • n / nop:输出一个空行。
  • p / le / level:输出日志事件的优先级。
  • r / relative:输出自应用启动后,以毫秒为单位的相对时间。
  • t / thread:输出发出日志事件的线程名。
  • T / caller:输出发出日志事件的类名、方法名、行号等信息。
  • u / user:输出发出日志事件的用户名。
  • x / xx:输出与当前日志事件相关的MDC(Mapped Diagnostic Context)数据。
  • X / XX:输出与当前日志事件相关的MDC(Mapped Diagnostic Context)数据。
  • ex / exception / throwable:输出异常信息。输出日志事件相关的堆栈信息,默认情况下会输出全部的堆栈信息。
  • xex / xException / xThrowable: 跟 %throwable 类似,只不过多了类的包信息。在每个堆栈信息的末尾,多了包含 jar 文件的字符串,后面再加上具体的实现版本。

logback 配置示例

  • maven 依赖
1
2
3
4
5
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.17</version>
</dependency>
  • logback xml 配置:classPath/resource
    • java 项目:logback.xml
    • spring-boot项目:logback-spring.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="10 seconds">


<property name="PROJECT_NAME" value="java-project"/>

<springProperty name="PROJECT_NAME" scop="context" source="spring.application.name"/>


<property name="LOG_FILE_MAX_SIZE" value="100MB"/>
<property name="LOG_FILE_MAX_HISTORY" value="30"/>


<property name="LOG_PATH" value="/var/logs/java/${PROJECT_NAME}"/>
<property name="DEBUG_LOG" value="${PROJECT_NAME}-debug"/>
<property name="API_LOG" value="${PROJECT_NAME}-api"/>
<property name="INFO_LOG" value="${PROJECT_NAME}-info"/>
<property name="ERROR_LOG" value="${PROJECT_NAME}-error"/>


<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${PROJECT_NAME} ${hostname} %X{traceId} %thread %-5level %logger{36}:%msg%n"/>


<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>


<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${DEBUG_LOG}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter" level="DEBUG"/>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${DEBUG_LOG}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
<maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
</rollingPolicy>
</appender>


<appender name="API" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${API_LOG}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter" level="INFO"/>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${API_LOG}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
<maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
</rollingPolicy>
</appender>


<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${INFO_LOG}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter" level="INFO"/>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${INFO_LOG}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
<maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
</rollingPolicy>
</appender>


<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${ERROR_LOG}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter" level="ERROR"/>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${ERROR_LOG}.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>${LOG_FILE_MAX_SIZE}</maxFileSize>
<maxHistory>${LOG_FILE_MAX_HISTORY}</maxHistory>
</rollingPolicy>
</appender>


<logger name="com.ares" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="INFO"/>
</logger>

<logger name="api" level="info" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="API"/>
</logger>


<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO"/>
<appender-ref ref="API"/>
</root>

<root level="ERROR">
<appender-ref ref="ERROR"/>
</root>
</configuration>

基于logback日志脱敏示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LogMaskingConverter extends MessageConverter {

private static final String PHONE_PATTERN = "(\"phone\"\\s*:\\s*\")(\\d{11})(\"*)";

@Override
public String convert(ILoggingEvent event) {
if (event == null || event.getFormattedMessage() == null) {
return "";
}
String message = event.getFormattedMessage();
message = message.replaceAll(PHONE_PATTERN, "$1****$3");
return message;
}
}

xml配置文件:

1
2
3
<conversionRule conversionWord="mask" converterClass="com.ares.helper.LogMaskingConverter" />
<property name="LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} ${PROJECT_NAME} ${hostname} %X{traceId} %thread %-5level %logger{36}: %mask%n"/>

注意: 在 LOG_PATTERN 引入 mask


java日志基础
http://example.com/2025/06/11/java-log基础与实践/
作者
ares
发布于
2025年6月11日
许可协议