日志优化:日志分级、日志异步写入、日志压缩、日志上传策略
日志这东西,说大不大,说小不小。我见过不少团队,线上出了bug,翻半天日志发现关键信息被淹没了,或者日志文件把存储撑爆了。说白了,日志优化不是让你少打日志,而是让你打得聪明、存得高效、传得及时。
今天咱们就聊聊日志优化的四个核心维度:分级、异步写入、压缩、上传策略。嗯,每个点我都踩过坑,咱们一个一个说。
一、日志分级:别让所有日志都挤在一起
你想想看,如果Debug日志和Error日志混在一起,排查问题的时候就像在垃圾堆里找针。我个人的习惯是,严格区分日志级别,并且让不同级别的日志走不同的通道。
| 级别 | 含义 | 建议输出 | 存储策略 |
|---|---|---|---|
| VERBOSE | 最细粒度信息 | 仅开发阶段 | 不落盘 |
| DEBUG | 调试信息 | 开发/测试包 | 按需落盘,自动清理 |
| INFO | 关键流程节点 | 全量包 | 保留7天 |
| WARN | 潜在问题 | 全量包 | 保留30天 |
| ERROR | 明确异常 | 全量包 | 永久保留(压缩后) |
我的经验:我曾经在一个项目中,线上包开了VERBOSE级别的日志,结果一天产生了2GB的日志文件,直接把用户存储干爆了。后来我们强制在Release包中只保留WARN及以上级别,问题瞬间解决。
二、日志异步写入:别让日志拖慢主线程
很多新手喜欢直接在业务代码里写日志,比如:
// 错误示范:同步写入,阻塞主线程
Log.d("TAG", "用户点击了按钮");
FileUtils.writeToFile("/sdcard/log.txt", logContent);
这有什么问题?文件I/O操作是耗时的,尤其是在低端机上。你想想看,用户点了个按钮,结果界面卡了200ms,就因为你写了个日志文件。
正确的做法是异步写入。我一般用生产者-消费者模式:
// 异步日志写入器
public class AsyncLogWriter {
private BlockingQueue<String> logQueue = new LinkedBlockingQueue<>(1024);
private ExecutorService executor = Executors.newSingleThreadExecutor();
public void init() {
executor.submit(() -> {
while (true) {
try {
String log = logQueue.take(); // 阻塞获取
writeToFile(log); // 在后台线程写入
} catch (InterruptedException e) {
break;
}
}
});
}
public void log(String msg) {
if (!logQueue.offer(msg)) {
// 队列满了,丢弃最旧的日志
logQueue.poll();
logQueue.offer(msg);
}
}
}
注意:异步写入虽然不阻塞主线程,但要注意队列大小。我曾经遇到过队列无限增长,导致OOM的情况。建议设置一个上限,比如1024条,满了就丢弃最旧的。
三、日志压缩:存储空间是宝贵的
日志文件如果不压缩,几天就能吃掉几个GB。我建议在日志落盘后,对历史日志进行压缩。常用的压缩算法有GZIP和LZ4。
GZIP压缩率高,但速度慢;LZ4压缩率稍低,但速度快。我个人倾向于用LZ4做实时压缩,GZIP做归档压缩。
// LZ4压缩示例
public byte[] compressWithLZ4(byte[] data) {
LZ4Factory factory = LZ4Factory.fastestInstance();
int maxCompressedLength = factory.fastCompressor().maxCompressedLength(data.length);
byte[] compressed = new byte[maxCompressedLength];
int compressedLength = factory.fastCompressor().compress(data, 0, data.length, compressed, 0, maxCompressedLength);
return Arrays.copyOf(compressed, compressedLength);
}
压缩时机也很关键。我建议在日志文件轮转时进行压缩,而不是每写一条就压缩一次。比如每小时轮转一次日志文件,轮转后立即对旧文件进行压缩。
四、日志上传策略:什么时候传?传多少?
日志上传是个双刃剑。传得太频繁,浪费用户流量和电量;传得太少,问题排查不及时。我总结了一套策略:
- 按级别上传:ERROR日志立即上传,WARN日志批量上传,INFO日志仅在WiFi下上传
- 按条件触发:当应用发生崩溃时,上传崩溃前5分钟的日志
- 按时间间隔:每天凌晨上传一次前一天的日志(仅WiFi)
- 按存储阈值:当日志文件总大小超过50MB时,触发上传并清理
核心原则:用户无感知。上传操作必须在后台进行,不能影响用户正常使用。我建议使用WorkManager来调度上传任务,系统会自动选择合适的时机。
知识体系总览
下面这张图总结了日志优化的整体架构,你可以对照着看看自己的项目在哪个环节还有优化空间:
避坑指南
最后,分享几个我踩过的坑:
- 日志文件轮转:我曾经没有做轮转,一个日志文件写到几个GB,用户想删都删不掉。建议按大小(比如10MB)或按时间(比如每小时)轮转。
- 敏感信息过滤:日志里不要打印用户密码、Token等敏感信息。我见过有人把用户身份证号直接打出来的,这要是被第三方拿到,麻烦就大了。
- 上传失败重试:上传失败要有退避策略。我曾经设置每5秒重试一次,结果用户一晚上重试了上千次,电量直接崩了。
一个小技巧:在日志中加入唯一标识符(比如SessionId),这样上传到服务器后,可以方便地把同一用户的多条日志关联起来,排查问题效率翻倍。
好了,日志优化就聊到这儿。记住一句话:日志是给未来的自己看的,别让未来的自己骂现在的你。