java日志优化核心是“怎么打”和“打给谁看”:关无效日志、用占位符与supplier延迟计算、启用异步写入、精简堆栈与格式。

频繁打印日志会显著拖慢 Java 应用,核心问题不在“打了多少”,而在于“怎么打”和“打给谁看”。关键优化方向是:减少无效计算、避开同步 IO、控制输出节奏。
关掉不该打的日志
生产环境默认禁用 DEBUG,INFO 也只保留在关键路径(如订单创建、支付回调)。框架底层日志(Spring、MyBatis、Netty)一律设为 WARN 或 ERROR。MyBatis 的 SQL 日志尤其要关——它每条 SQL 都触发一次拼接+写入,万级并发下极易打满磁盘 IOPS。
- 按包精细控制:dao 层设为 ERROR,service 层仅入口/出口打 INFO,controller 层设为 WARN
- 用 Spring Boot Actuator 的 /actuator/loggers 端点临时开启某类 DEBUG,排查完立即恢复
- Logback 配置示例:
<logger name="com.example.dao" level="ERROR"></logger>
别让日志白算一遍
字符串拼接式写法("User " + user.getName() + " login")会在每次调用时强制执行所有表达式,哪怕日志最终被级别过滤掉。占位符("User {} login")由日志框架延迟绑定:只有当前级别启用时,才真正计算参数值。
- 高开销操作(如 JSON 序列化、远程调用结果)用 Supplier 包裹:
logger.debug("Order: {}", () -> toJson(order)) - 旧版 SLF4J 不支持 Supplier?先预检:
if (logger.isDebugEnabled()) { logger.debug("Order: {}", toJson(order)); } - 避免在 for 循环里打日志;必须记录时,改用计数器限频(如每千次打一次)或聚合后批量输出
让日志自己排队去写
同步写入会阻塞业务线程,每次 write() 都可能触发系统调用。必须启用异步机制:
- Log4j2:引入 disruptor 依赖,配置
AsyncLogger或AsyncAppender - Logback:用
AsyncAppender,队列大小建议设为 1024–4096 - 缓冲区不是越大越好:单条日志短、QPS 高 → 设为 8KB;单条长、允许少量延迟 → 设为 16KB
- ERROR 等关键日志仍需显式
flush(),防止故障时日志丢失
精简内容,少写点废话
一条异常日志附带 30 行堆栈,在高并发下等于 30 倍 I/O 放大。按需裁剪:
- 业务异常(参数校验失败)→ 只留最上层 3–5 帧
- 网络/DB 异常(超时、连接拒绝)→ 保留关键中间层,12–18 帧足矣
- 移除低价值字段:日志 pattern 中去掉毫秒级时间戳
%d{HH:mm:ss.SSS},改用秒级 - 避免在日志中反复调用
toString()或遍历大集合
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











