日志优化:别让日志拖垮你的系统
日志本身是 IO 操作,大量日志输出会拖慢系统。
曾经遇到过一个案例:接口响应时间 200ms,但其中 180ms 都在写日志。优化日志后,响应时间降到了 30ms。
日志的性能问题
| 问题 | 影响 |
|---|---|
| 同步写入 | 线程阻塞等待 IO |
| 日志量过大 | 磁盘写满 |
| 日志对象频繁创建 | GC 压力 |
| 字符串拼接 | CPU 开销 |
异步日志
用异步 appender 解耦日志写入和主线程:
xml
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize> <!-- 队列大小 -->
<discardingThreshold>0</discardingThreshold> <!-- 0 = 队列满就丢弃 -->
<includeCallerData>true</includeCallerData> <!-- 保留调用栈信息 -->
<appender-ref ref="FILE"/>
</appender>队列满会发生什么?
discardingThreshold=0 意味着队列满了直接丢弃日志,不阻塞主线程。对于 INFO 级别的日志,丢弃是可以接受的;对于 ERROR 日志,建议同步写入。
xml
<!-- ERROR 日志同步,INFO/WARN 用异步 -->
<appender name="SYNC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<!-- ... -->
</appender>日志滚动策略
控制日志文件大小和保留时间:
xml
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize> <!-- 单个文件最大 100MB -->
<maxHistory>30</maxHistory> <!-- 保留 30 天 -->
<totalSizeCap>10GB</totalSizeCap> <!-- 最多占用 10GB 磁盘 -->
</rollingPolicy>压缩后的日志(.gz)体积是原始文件的 1/10 左右,可以大幅节省磁盘。
日志采样
高频日志(比如循环中的 debug)可以通过采样减少输出量:
java
public class SamplingDemo {
private static final Logger log = LoggerFactory.getLogger(SamplingDemo.class);
private static final int SAMPLE_RATE = 100;
private static final AtomicInteger counter = new AtomicInteger(0);
public void logSample(String message) {
if (counter.incrementAndGet() % SAMPLE_RATE == 0) {
log.info("采样日志: {}", message);
}
}
}1% 的采样率意味着每秒 1000 条日志只会输出 10 条,在排查问题时足够了。
日志级别动态调整
不需要改代码重启,通过配置调整日志级别:
xml
<!-- logback-spring.xml -->
<springProfile name="dev">
<root level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<root level="INFO"/>
</springProfile>总结
- 异步写入:减少 IO 对主线程的影响
- 日志压缩:
.gz格式节省磁盘空间 - 滚动策略:按时间和大小轮转,防止磁盘满
- 采样打印:高频日志用采样减少输出量
- 级别区分:ERROR 同步,DEBUG/INFO 异步
日志优化是性价比最高的性能优化之一,改动小,效果立竿见影。
