可观测性组件
| 维度 | 工具 | 目标 |
|---|---|---|
| 日志 | Timber、Logger、Logcat | 记录调试与业务轨迹 |
| 崩溃 | Firebase Crashlytics、Sentry | 捕获崩溃与非致命异常 |
| 性能 | Firebase Performance、Macrobenchmark | 监控启动、网络、帧率 |
| 业务指标 | 自建埋点、Analytics SDK | 分析用户行为 |
日志体系
Kotlin 封装
object AppLogger {
fun init() {
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
Timber.plant(ReleaseTree())
}
}
fun d(message: String, vararg args: Any?) = Timber.d(message, *args)
fun e(throwable: Throwable, message: String, vararg args: Any?) = Timber.e(throwable, message, *args)
}
class ReleaseTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (priority >= Log.ERROR) {
Crashlytics.log(priority, tag, message)
t?.let { Crashlytics.recordException(it) }
}
}
}
日志脱敏
fun String.maskMiddle(): String {
return if (length > 7) replaceRange(3, length - 4, "***") else this
}
AppLogger.d("phone=%s", phone.maskMiddle())
崩溃监控
- Crashlytics 初始化
class App : Application() {
override fun onCreate() {
super.onCreate()
FirebaseApp.initializeApp(this)
AppLogger.init()
}
}
- 捕获协程异常:
val crashHandler = CoroutineExceptionHandler { _, throwable ->
Crashlytics.recordException(throwable)
}
viewModelScope.launch(crashHandler) { ... }
性能指标
- Firebase Performance:监控网络请求与启动。
- Macrobenchmark:离线性能回归。
- 自建指标:OKHttp Interceptor 记录耗时。
class MetricInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val start = SystemClock.elapsedRealtime()
return try {
chain.proceed(chain.request())
} finally {
val cost = SystemClock.elapsedRealtime() - start
Metrics.logNetwork(chain.request().url.toString(), cost)
}
}
}
事件追踪与关联
- 使用
traceId把日志、崩溃、接口串起来。
val traceId = UUID.randomUUID().toString()
AppLogger.d("traceId=%s start login", traceId)
Crashlytics.setCustomKey("traceId", traceId)
Monitoring 仪表板
- ELK/EFK:收集 Logcat -> Logstash -> Elasticsearch -> Kibana。
- Grafana + Prometheus:展示性能与业务指标。
- 飞书/钉钉机器人:发送告警。
{
"alerts": [
{
"metric": "app_startup_time",
"condition": "> 3000",
"notify": ["lark-webhook-url"]
}
]
}
本地调试技巧
adb logcat -s AppLogger过滤。adb shell dumpsys dropbox查看崩溃。- 使用
Stetho/Flipper观察网络与数据库。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 日志过多 | 调试日志在 Release 未关闭 | 根据 BuildConfig 控制日志级别 |
| 崩溃无 Trace | 混淆未保留符号表 | 上传符号文件,保留 Crashlytics 映射 |
| 指标不准确 | 采集点不统一 | 统一埋点规范与时钟,避免重复上报 |
实战案例:日志的”三级火箭”
我在货运项目(某物流冷链项目)里用了 logger: ^2.6.0(pubspec 可查),Android 侧对等的是 Timber。但”打日志”只是第一级,完整的可观测性是三级火箭:
- 本地日志(Timber / logger):开发期看,
Timber.plant(DebugTree()),release 包用ReleaseTree只上报不打印。千万别把用户隐私打进日志——身份证号、token 打进 logcat,等于明文广播。 - 崩溃上报(Firebase Crashlytics / Sentry):
flutter_brightness这种桌面应用我没接(用户量小),但货运这种 toB 应用必须接。Crashlytics 的recordException+ 自定义 key(用户 id、页面),是线上救火的核心依据。 - 业务埋点(Firebase Analytics / 自研):PV/UV、关键转化漏斗。注意国内合规:埋点 SDK 的隐私政策必须在《隐私政策》里声明,否则应用市场审核会被打回。
还有个血泪教训:日志级别要能远程开关。线上出诡异 bug,最需要的是 verbose 日志,但发版时全量开又影响性能。Firebase Remote Config 配个 log_level 开关,救过我不止一次。
参考:Firebase 官方文档 Crashlytics;Android 官方文档《Log》
总结
可观测性是保障应用质量的关键。通过构建标准化的日志、崩溃与性能监控体系,结合自动化报警与仪表板,可快速发现问题、定位根因,支撑稳定迭代。Kotlin 提供的封装能力可让这些工具更易于使用与维护。
