| Files | Last commit | Last update |
|---|---|---|
| 30 days ago |
Java 日志学习笔记
定位:Java 日志体系:日志级别、JUL / Log4j 2 / Logback / SLF4J 框架对比与最佳实践。
一、日志级别
Java 日志体系通常包含以下六个级别,按严重程度从低到高排列:
| 级别 | 说明 | 使用场景 |
|---|---|---|
TRACE |
最详细的追踪信息 | 细粒度调试,如循环内变量值、方法入参出参 |
DEBUG |
调试信息 | 开发阶段排查问题,记录程序执行路径 |
INFO |
一般运行信息 | 系统启动、关键业务操作完成、定时任务执行 |
WARN |
警告信息 | 可恢复的异常、配置缺失、降级策略触发 |
ERROR |
错误信息 | 不可恢复的异常、核心业务失败、外部调用异常 |
FATAL |
致命错误 | 系统无法继续运行,需要立即停机处理 |
// 各级别日志调用示例
logger.trace("Entering method processOrder with orderId={}", orderId);
logger.debug("Order {} status changed from {} to {}", orderId, oldStatus, newStatus);
logger.info("Application started successfully on port {}", port);
logger.warn("Connection pool size {} exceeds threshold {}", currentSize, threshold);
logger.error("Failed to process payment for order {}", orderId, exception);
logger.fatal("Database connection lost, shutting down service");
注意: 生产环境通常将日志级别设置为
INFO或WARN,以避免大量调试信息占用磁盘空间和 I/O 带宽。
二、java.util.logging(JUL)简介
java.util.logging(简称 JUL)是 Java 1.4 引入的官方日志框架,位于 java.util.logging 包中。
核心组件
| 组件 | 说明 |
|---|---|
Logger |
日志记录器,应用程序通过它发出日志调用 |
Handler |
日志处理器,决定日志输出到哪里(控制台、文件等) |
Formatter |
日志格式化器,决定日志的输出格式 |
Level |
日志级别枚举 |
LogManager |
日志管理器,负责配置和管理 Logger 实例 |
使用示例
import java.util.logging.*;
public class JulExample {
// 获取 Logger 实例
private static final Logger logger = Logger.getLogger(JulExample.class.getName());
public static void main(String[] args) {
// 基本使用
logger.info("这是一条 INFO 级别日志");
logger.warning("这是一条 WARN 级别日志");
logger.severe("这是一条 ERROR 级别日志");
// 配置 ConsoleHandler
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
handler.setFormatter(new SimpleFormatter());
logger.setLevel(Level.ALL);
logger.addHandler(handler);
// 带异常的日志
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.log(Level.SEVERE, "计算异常", e);
}
}
}
JUL 的局限性
- 性能较差,不支持异步日志
- 功能单一,缺少 Log4j/Logback 的高级特性
- 社区活跃度低,极少用于生产项目
- 日志级别命名与其他框架不一致(如
SEVERE而非ERROR)
三、Log4j 2 简介
Apache Log4j 2 是 Log4j 1.x 的升级版,在性能和功能上都有显著提升。
核心特性
- 异步日志:基于 LMAX Disruptor 无锁队列,吞吐量是 Log4j 1.x 和 Logback 的 10 倍以上
- 零 GC 模式:在 Java 8+ 上运行时可做到完全无垃圾回收压力
- 插件式架构:支持自定义 Appender、Layout、Filter 等
- 自动重载配置:支持配置文件热更新
- 多 API 支持:同时支持 Log4j 2 API、SLF4J、java.util.logging 等
依赖引入
<!-- Log4j 2 核心依赖 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.24.1</version>
</dependency>
<!-- SLF4J 桥接 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.24.1</version>
</dependency>
配置示例(log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<Property name="LOG_DIR">logs</Property>
<Property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
</Properties>
<Appenders>
<!-- 控制台输出 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${PATTERN}"/>
</Console>
<!-- 异步文件输出 -->
<RollingFile name="RollingFile"
fileName="${LOG_DIR}/app.log"
filePattern="${LOG_DIR}/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.example" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
零 GC 模式原理
Log4j 2 的零 GC(Garbage Collector Free)模式通过以下方式减少对象分配:
- 使用
ThreadLocal复用StringBuilder和缓冲区 - 避免创建
String对象,直接使用字符数组 - 日志消息延迟到真正写入时才格式化(Lambda 支持)
- 使用内存映射文件(MemoryMappedFile)减少 I/O 对象创建
// 使用 Lambda 延迟字符串拼接,减少不必要的对象创建
logger.debug("User {} performed action {} at time {}",
() -> userId,
() -> actionName,
() -> timestamp);
四、Logback 简介
Logback 由 Log4j 的创始人 Ceki Gulcu 创建,是 Log4j 1.x 的精神继承者,也是 Spring Boot 的默认日志框架。
核心模块
| 模块 | 说明 |
|---|---|
logback-core |
核心模块,提供基础功能 |
logback-classic |
经典模块,包含 SLF4J 集成 |
logback-access |
HTTP 访问日志模块(与 Jetty/Tomcat 集成) |
核心特性
- 原生支持 SLF4J:无需桥接层,性能更好
- 自动重载配置:
scan="true"实现配置热更新 - 条件配置:支持
<if>/<then>/<else>条件判断 - 丰富的过滤器:支持基于级别、Marker、MDC 的过滤
- 自动压缩:滚动日志自动 gzip 压缩
依赖引入(Spring Boot 项目默认包含)
<!-- Spring Boot Starter 已自动引入 Logback -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
使用示例
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogbackExample {
private static final Logger log = LoggerFactory.getLogger(LogbackExample.class);
public void processOrder(String orderId) {
log.info("开始处理订单: {}", orderId);
try {
// 业务逻辑
log.debug("订单 {} 处理完成", orderId);
} catch (Exception e) {
log.error("订单 {} 处理失败", orderId, e);
}
}
}
五、SLF4J 门面(日志抽象层)
SLF4J(Simple Logging Facade for Java)是一个日志门面,它为各种日志框架提供统一的 API。
架构设计
应用程序
|
v
SLF4J API (slf4j-api.jar)
|
v
SLF4J Binding (适配器层)
|
+---> logback-classic (直接实现)
+---> log4j-slf4j-impl (Log4j 2 适配)
+---> jul-to-slf4j (JUL 桥接)
+---> jcl-over-slf4j (Commons Logging 桥接)
|
v
具体日志框架实现
核心优势
| 优势 | 说明 |
|---|---|
| 解耦 | 业务代码不依赖具体日志实现,可自由切换框架 |
| 占位符 | {} 占位符避免不必要的字符串拼接 |
| 统一 API | 一个 API 适配所有主流日志框架 |
| 桥接能力 | 可将 JUL、JCL 等日志调用重定向到 SLF4J |
常用 API
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
public class Slf4jApiExample {
private static final Logger log = LoggerFactory.getLogger(Slf4jApiExample.class);
// 1. 占位符日志(推荐)
public void method1() {
log.info("用户 {} 登录成功,IP: {}", username, ipAddress);
}
// 2. 条件日志(耗时计算场景)
public void method2() {
if (log.isDebugEnabled()) {
log.debug("复杂计算结果: {}", expensiveComputation());
}
}
// 3. MDC(Mapped Diagnostic Context)用于链路追踪
public void method3(String traceId) {
MDC.put("traceId", traceId);
try {
log.info("处理业务请求");
} finally {
MDC.remove("traceId");
}
}
// 4. Marker 标记日志
public void method4() {
Marker securityMarker = MarkerFactory.getMarker("SECURITY");
log.warn(securityMarker, "检测到异常登录尝试");
}
}
桥接与排除冲突
当项目中混用多个日志框架时,需要通过桥接和排除来解决冲突:
<!-- 排除 Commons Logging,桥接到 SLF4J -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- JUL 桥接到 SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
六、各日志框架对比
| 对比维度 | JUL | Log4j 1.x | Log4j 2 | Logback |
|---|---|---|---|---|
| 发布年份 | 2002 (Java 1.4) | 2001 | 2014 | 2006 |
| 维护状态 | JDK 内置维护 | 已停止维护 | 活跃维护 | 活跃维护 |
| 异步日志 | 不支持 | 不支持 | 支持(Disruptor) | 支持(AsyncAppender) |
| 零 GC | 不支持 | 不支持 | 支持 | 不支持 |
| 自动重载 | 不支持 | 不支持 | 支持 | 支持 |
| 配置格式 | properties | XML/properties | XML/JSON/YAML | XML/Groovy |
| 过滤器 | 简单 | 有限 | 丰富 | 丰富 |
| 性能排名 | 最差 | 较差 | 最优 | 中等偏上 |
| Spring Boot 默认 | 否 | 否 | 否 | 是 |
| API 设计 | 原生 API | 原生 API | 独立 API + SLF4J | SLF4J 原生 |
| 滚动策略 | 有限 | 有限 | 非常灵活 | 灵活 |
| 社区活跃度 | 低 | 已停止 | 高 | 高 |
性能对比(吞吐量,越高越好)
下图为量级示意(各框架社区 benchmark 的常见数量级排序,非统一硬件实测数据;绝对吞吐随硬件 / 日志级别 / 消息大小浮动可达数倍):
Log4j 2 (Async) ████████████████████████████████████████ 数百万条/秒量级(异步框架中最快)
Log4j 2 (Sync) ███████████████████~ 百万条/秒量级
Logback (Async) █████████████~ 百万条/秒量级(略低于 Log4j 2 Async)
Logback (Sync) █████████~ 数十万~百万条/秒量级
JUL ███~ 十万条/秒量级(最慢)
七、SLF4J + Logback 配置示例
完整 logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<!-- Spring Profile 支持 -->
<springProfile name="dev">
<property name="LOG_LEVEL" value="DEBUG"/>
</springProfile>
<springProfile name="prod">
<property name="LOG_LEVEL" value="INFO"/>
</springProfile>
<!-- 日志格式定义 -->
<property name="CONSOLE_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{36}) - %msg%n"/>
<property name="FILE_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台 Appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件 Appender(按天滚动) -->
<appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app-info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>${FILE_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件 Appender(错误日志单独输出) -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app-error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>${FILE_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 异步 Appender(包裹文件 Appender) -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
<appender-ref ref="FILE_INFO"/>
</appender>
<!-- Logger 配置 -->
<logger name="com.example.service" level="DEBUG" additivity="false">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</logger>
<logger name="org.springframework" level="WARN"/>
<logger name="org.hibernate" level="WARN"/>
<!-- Root Logger -->
<root level="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE_INFO"/>
<appender-ref ref="FILE_ERROR"/>
</root>
</configuration>
application.yml 简化配置
logging:
level:
root: INFO
com.example: DEBUG
org.springframework: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
max-size: 100MB
max-history: 30
八、日志格式(JSON vs 文本)
文本格式
传统的文本格式适合人类阅读,使用 PatternLayout 定义输出模板:
2025-06-04 10:30:45.123 [main] INFO c.e.service.OrderService - 订单 12345 创建成功, 用户: user_001
<!-- 文本格式配置 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
| 优点 | 缺点 |
|---|---|
| 人类可读性好 | 机器解析困难 |
| 简单的 grep/awk 即可查询 | 结构化查询能力差 |
| 日志文件体积小 | 多行异常堆栈解析复杂 |
JSON 格式
JSON 格式适合机器处理,是日志聚合系统(ELK、Splunk)的首选:
{
"timestamp": "2025-06-04T10:30:45.123+08:00",
"level": "INFO",
"thread": "main",
"logger": "com.example.service.OrderService",
"message": "订单 12345 创建成功",
"traceId": "abc-123-def",
"userId": "user_001",
"orderId": "12345",
"serviceName": "order-service",
"environment": "production"
}
Logback JSON 配置(使用 logstash-logback-encoder)
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.json.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.json.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"serviceName":"order-service","environment":"production"}</customFields>
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
</encoder>
</appender>
Log4j 2 JSON 配置
<JsonLayout compact="true" eventEol="true" stacktraceAsString="true"
includeTimeMillis="true" locationInfo="true">
<KeyValuePair key="serviceName" value="order-service"/>
<KeyValuePair key="environment" value="${env:ENVIRONMENT}"/>
</JsonLayout>
格式选择建议
| 场景 | 推荐格式 | 原因 |
|---|---|---|
| 本地开发调试 | 文本 | 直观、易读 |
| 生产环境 + ELK | JSON | 结构化、便于索引和查询 |
| 生产环境 + 日志文件审计 | 文本 + JSON 双写 | 兼顾人工审计和机器分析 |
| 容器化部署(stdout) | JSON | 容器日志采集系统友好 |
九、异步日志原理
为什么需要异步日志
同步日志的瓶颈在于 I/O 操作:每次写日志都需要等待磁盘 I/O 完成,阻塞业务线程。异步日志将写日志操作交给后台线程处理,业务线程只需将日志事件放入队列即可继续执行。
架构模型
业务线程 后台 I/O 线程
| |
| 1. 创建 LogEvent |
| 2. 放入队列 (RingBuffer) |
| 3. 立即返回,继续业务 ----|
| |---- 4. 从队列取 LogEvent
| |---- 5. 格式化日志消息
| |---- 6. 写入磁盘/网络
| |---- 7. 处理下一个
实现方式对比
| 实现方式 | 队列类型 | 特点 |
|---|---|---|
| Log4j 2 Async | LMAX Disruptor RingBuffer | 无锁、高吞吐、低延迟 |
| Logback AsyncAppender | ArrayBlockingQueue | 有锁、实现简单 |
| 手动异步 | CompletableFuture | 灵活但需注意异常处理 |
Log4j 2 AsyncAppender 配置
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %-5level %logger - %msg%n"/>
</Console>
<RollingFile name="File" fileName="logs/async.log"
filePattern="logs/async.%d{yyyy-MM-dd}.log">
<PatternLayout pattern="%d %-5level %logger - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
</RollingFile>
<!-- 异步 Appender,使用 Disruptor 无锁队列 -->
<Async name="Async" bufferSize="262144" includeLocation="false">
<AppenderRef ref="File"/>
<AppenderRef ref="Console"/>
</Async>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
关键参数说明
| 参数 | 说明 | 推荐值 |
|---|---|---|
bufferSize |
RingBuffer 大小(必须为 2 的幂) | 262144 (256K) |
includeLocation |
是否包含代码位置信息(影响性能) | false(生产环境) |
blocking |
队列满时是否阻塞 | true(不丢日志) |
queueSize |
Logback AsyncAppender 队列大小 | 512-2048 |
discardingThreshold |
Logback 丢弃阈值 | 0(不丢弃) |
RingBuffer 核心原理
+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | <-- 预分配的数组
+---+---+---+---+---+---+---+---+
^ ^
consumer producer
1. RingBuffer 是一个固定大小的循环数组
2. 生产者(业务线程)通过 CAS 操作获取下一个序列号
3. 将日志事件写入对应槽位
4. 消费者(I/O 线程)按顺序读取并处理
5. 无锁设计:生产者和消费者各自维护独立的指针
6. 缓存行填充(Cache Line Padding)避免伪共享
性能影响
同步写日志: 业务线程 --[写日志 2ms]--> 继续执行
总耗时: 业务逻辑 + 2ms
异步写日志: 业务线程 --[入队 0.001ms]--> 继续执行
|
后台线程 --[写日志 2ms]--> 完成
总耗时: 业务逻辑 + 0.001ms
注意:
includeLocation="true"会捕获线程栈信息以获取类名、方法名、行号,这会显著影响性能。生产环境建议关闭。
十、最佳实践
1. 日志级别选择原则
| 场景 | 级别 | 示例 |
|---|---|---|
| 方法入口/出口 | DEBUG |
log.debug("entering processOrder, param={}", param) |
| 关键业务节点 | INFO |
log.info("订单 {} 支付成功", orderId) |
| 外部接口调用 | INFO(成功)/ ERROR(失败) |
log.info("调用支付接口成功, cost={}ms", cost) |
| 可自动恢复的异常 | WARN |
log.warn("数据库连接超时,尝试重连") |
| 需要人工介入的错误 | ERROR |
log.error("订单创建失败,数据库异常", e) |
| 系统级致命错误 | FATAL / ERROR |
log.error("无法启动服务,端口被占用", e) |
2. 禁止使用字符串拼接
// 错误:无论级别如何,字符串都会拼接
log.debug("Order: " + orderId + ", Status: " + status);
// 正确:使用占位符,仅在实际输出时拼接
log.debug("Order: {}, Status: {}", orderId, status);
// 更优:使用 Lambda 延迟计算(Log4j 2 / SLF4J 2.0)
log.debug("Order: {}, Status: {}",
() -> getOrderId(),
() -> getStatus());
3. 敏感信息脱敏
import org.apache.commons.lang3.StringUtils;
public class SensitiveDataMasker {
// 手机号脱敏: 138****1234
public static String maskPhone(String phone) {
if (StringUtils.isBlank(phone) || phone.length() < 7) {
return phone;
}
return phone.substring(0, 3) + "****" + phone.substring(phone.length() - 4);
}
// 身份证号脱敏: 110***********1234
public static String maskIdCard(String idCard) {
if (StringUtils.isBlank(idCard) || idCard.length() < 8) {
return idCard;
}
return idCard.substring(0, 3) + "***********" + idCard.substring(idCard.length() - 4);
}
// 银行卡号脱敏: **** **** **** 1234
public static String maskBankCard(String bankCard) {
if (StringUtils.isBlank(bankCard) || bankCard.length() < 4) {
return bankCard;
}
return "**** **** **** " + bankCard.substring(bankCard.length() - 4);
}
// 邮箱脱敏: u***@example.com
public static String maskEmail(String email) {
if (StringUtils.isBlank(email) || !email.contains("@")) {
return email;
}
String[] parts = email.split("@");
String name = parts[0];
if (name.length() <= 1) {
return name + "***@" + parts[1];
}
return name.charAt(0) + "***@" + parts[1];
}
}
// 使用示例
log.info("用户下单,手机号: {}, 邮箱: {}",
SensitiveDataMasker.maskPhone(phone),
SensitiveDataMasker.maskEmail(email));
4. 异常日志正确写法
// 错误:异常信息丢失(堆栈不会输出)
log.error("操作失败: " + e.getMessage());
// 错误:打印堆栈但不包含有意义的消息
log.error("", e);
// 正确:消息 + 异常对象
log.error("订单 {} 支付失败,原因: {}", orderId, e.getMessage(), e);
// 推荐:使用占位符,异常作为最后一个参数
log.error("处理请求失败, userId={}, requestId={}", userId, requestId, e);
5. 日志量控制
// 避免在循环中打印日志
// 错误
for (Order order : orders) {
log.debug("处理订单: {}", order.getId()); // 可能产生海量日志
}
// 正确:使用采样或汇总
if (log.isDebugEnabled() && orders.size() < 100) {
for (Order order : orders) {
log.debug("处理订单: {}", order.getId());
}
} else {
log.info("批量处理 {} 个订单", orders.size());
}
// 使用限流日志
private static final RateLimiter LOG_RATE_LIMITER = RateLimiter.create(10); // 每秒10条
public void logWithRateLimit(String message) {
if (LOG_RATE_LIMITER.tryAcquire()) {
log.warn(message);
}
}
6. 结构化日志 + MDC 链路追踪
import org.slf4j.MDC;
import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;
public class TraceIdFilter implements Filter {
private static final String TRACE_ID_KEY = "traceId";
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
String traceId = UUID.randomUUID().toString().replace("-", "");
try {
MDC.put(TRACE_ID_KEY, traceId);
chain.doFilter(request, response);
} finally {
MDC.remove(TRACE_ID_KEY);
}
}
}
配合日志格式,每条日志自动带上 traceId:
2025-06-04 10:30:45.123 [http-8080-1] INFO OrderService [traceId=a1b2c3d4] - 订单创建成功
7. 生产环境检查清单
| 检查项 | 说明 |
|---|---|
| 日志级别 | 生产环境至少 INFO,避免 DEBUG |
| 敏感信息 | 手机号、身份证、密码等必须脱敏 |
| 异常堆栈 | ERROR 级别必须包含异常堆栈 |
| 异步日志 | 高并发场景必须使用异步日志 |
| 日志滚动 | 必须配置按天滚动和大小限制 |
| 磁盘空间 | 监控日志目录大小,设置总容量上限 |
| includeLocation | 生产环境关闭代码位置信息 |
| 统一格式 | 全公司统一日志格式(推荐 JSON) |
| 日志集中化 | 接入 ELK/日志平台,不要只存在本地磁盘 |
📊 本节统计
| 统计维度 | 数值 | 口径 |
|---|---|---|
| 分类主题数 | 1 | 日志体系(JUL / Log4j 2 / Logback / SLF4J 4 个框架综合) |
| 子 README 数 | 0 | 无子 README |
| 含 frontmatter 的 README | 1 / 1 | 100% 覆盖(2026-07-01) |
统计时间戳:2026-07-01