4、日志级别详解:DEBUG、INFO、WARN、ERROR、FATAL 的含义与用途

日志这东西,看起来简单,用好了能救你一命。

我记得刚接触 Compose 那会儿,拼命加日志,打印出来全是 Log.d,整个控制台刷屏。结果呢?真出问题时,鼠标滚轮都滚冒烟了,也找不到关键信息。

今天我们就彻底聊透五种日志级别。

日志级别的核心思想:信息的轻重缓急

说白了,日志级别就是一个 「消息分类器」。
你想想看,一段日志可以回答两个问题:

  • 重要吗?——这直接影响系统是否保持运行。
  • 给谁看?——是给我开发时调试用,还是给运维看?

我个人习惯把日志看作「交通信号灯」,每种颜色代表不同的紧急程度:

  • DEBUG:绿灯,正常行驶,但可以观察细节。
  • INFO:蓝灯,正常行驶,记录关键节点。
  • WARN:黄灯,可能有坑,请注意。
  • ERROR:红灯,立即停车排查。
  • FATAL:黑灯,系统可能要挂了。

核心原则:生产环境中,我只保留 WARN 及以上级别,DEBUG 和 INFO 只在开发阶段全开。这样能降低 I/O 开销,也能避免重要信息被淹没。

日志级别决策与过滤流程 代码产生日志 日志门面 (统一收集) 等级过滤器 (Level) 环境开关 (BuildConfig) 内容过滤 (Tag/TAG) 输出到 Logcat / 远程日志 重要:DEBUG/INFO 在线上关闭, WARN/ERROR/FATAL 全部保留

五个级别,逐个拆解

级别 数值 (Android) 含义 典型用途
DEBUG 3 开发调试信息,生产环境通常关闭 变量值、重组次数、排版参数
INFO 4 关键节点信息,记录正常流程 页面加载完成、网络请求成功
WARN 5 潜在问题,但不影响当前功能 资源泄露、API 降级、重组次数过多
ERROR 6 错误事件,可能需要人工介入 网络异常、数据解析失败、Compose 崩溃
FATAL 7 致命错误,应用很可能崩溃 ANR、OOM、kernel 异常

1. DEBUG 级别:开发者的放大镜

DEBUG 是写给自己的。它只存在于 BuildConfig.DEBUG = true 的版本里。

在 Compose 调试中,我最常用 DEBUG 来记录 重组次数 和 参数变化:

Log.d("ComposeRecompose", "TextContent 重组了,当前值:$text")

为什么要关注这个?因为 Compose 的声明式渲染依赖重组。如果你看到某段代码重组了几百次,那多半是不可控重组——也就是性能隐患。

技巧:在 Android Studio 的 Logcat 中,可以过滤 package:mine 来只显示自己应用的日志。我习惯再配合 Log.d(TAG, ...) 中的 TAG 做二次过滤。

2. INFO 级别:系统的脉搏

INFO 记录的是「应该发生」的事情。比如用户登录成功、屏幕旋转、布局加载完成。

对我而言,DEBUG 是微观调试,INFO 是宏观监控。

Log.i("Lifecycle", "MyScreen 已进入 Composition")

在回归测试中,我会在所有关键 activity/fragment 的 onCreate 或 LaunchedEffect 中添加 INFO 日志。这样就能快速定位:到底是哪一步没有走到?

3. WARN 级别:黄牌警告

WARN 代表「虽然有危险,但系统还能继续运行」。我在项目中遇到过,重组次数过多时,WARN 日志会提示潜在的性能问题:

Log.w("Performance", "Recompose count exceeded threshold: 50 times in 1s")

这个级别经常会被人忽略。但你想想看,线上大量 WARN 堆积,可能就是在预告某个模块即将崩溃。

4. ERROR 级别:需要立即响应

ERROR 代表功能已不可用。网络连接失败、数据格式错误、权限被拒……这些都是 ERROR。

我曾经在一个 Compose 项目里,用 ERROR 日志标记所有不可恢复的错误:

Log.e("NetworkError", "Fetch user list failed: ${error.message}")

注意:ERROR 日志一定要附带堆栈信息或错误详情,不然光看一条「Something went wrong」等于没打。

5. FATAL 级别:最后的遗言

FATAL 是最高的。它意味着应用即将或已经崩溃。在 Android 原生日志中,Log.wtf() 就属于 FATAL(What a Terrible Failure)。

我不建议滥用 FATAL,它应该只留给无法恢复、无法捕获的致命错误,比如:

  • 空指针但无法捕获(Compose 内部崩溃)
  • 线程死锁
  • 抛出 PendingAtomicThrow 后无法继续
Log.wtf("Fatal", "Unhandled composition exception: ${exception}")

你知道吗?我一般把 FATAL 当作最后一道防线。如果它出现了,那说明我的前置防御(null 检查、try-catch)全都漏了。

如何选择级别?一个小决策树

刚开始的同学容易纠结「这个该用 ERROR 还是 WARN?」。我个人习惯做如下判断:

  1. 用户能感知到吗? —— 能,且功能不可用 -> ERROR;能但暂时不影响 -> WARN。
  2. 需要人工处理吗? —— 需要 -> WARN/ERROR;只是记录 -> INFO/DEBUG。
  3. 这是必然事件还是偶然事件? —— 必然(如网络超时)-> ERROR;偶然(如临时降级)-> WARN。

我的一条铁律:DEBUG 打在开发环境,INFO 打在测试环境,WARN/ERROR 打在线上。线上线下分开打,用 BuildConfig 控制开关。

避坑指南:日志不是越多越好

我曾经犯过一个低级错误,把 DEBUG 日志留在发布版本里。结果线上日志量爆炸,磁盘写满,导致 ANR。

从那之后,我给自己定了几条规则:

  • DEBUG 和 INFO 必须包在 if (BuildConfig.DEBUG) 里
  • WARN 和 ERROR 可以留在线上,但必须限制频率(比如用 RateLimiter)
  • FATAL 必须配合崩溃采集 SDK 上报,不要只打日志

嗯,说白了日志级别就是你写给未来自己的一封信。
写得清楚,未来少踩坑;写得潦草,排起查来眼泪汪汪。