日志、埋点、错误收集的统一接入
Flutter 实战系列第 6 篇。
目标:建立“可观测性”基础设施,让问题可发现、可定位、可复盘。
1. 问题背景:业务场景 + 现象
中大型业务最怕“线上出问题但看不见”:
- 用户说卡、说崩,但没有上下文
- 同一个问题在不同模块重复出现
- 日志分散在 print、debugPrint、平台日志里
- 埋点口径不一致,数据无法对齐业务结论
- 崩溃上报缺关键字段,无法定位到具体链路
2. 原因分析:核心原理 + 排查过程
2.1 核心原理
可观测性至少包含三层:
- 日志(Log):还原过程
- 事件埋点(Event):量化行为
- 错误上报(Error/Crash):定位故障
如果这三层没有统一标准,就会出现“有数据但无结论”。
2.2 常见反模式
- 直接到处
print - 事件名随意命名,字段口径混乱
- 错误上报不带用户/路由/环境信息
- 网络错误和业务错误没有统一编码
- 线上日志级别未分级,噪音太多
3. 解决方案:方案对比 + 最终选择
3.1 方案对比
-
各模块自己打日志/埋点
快,但无法治理 -
统一 SDK 封装层(推荐)
需要初期建设,但长期稳定
3.2 最终选择
建立统一可观测层(Observe):
Observe.log(...):结构化日志Observe.event(...):统一事件埋点Observe.error(...):异常与崩溃上报- 全部自动注入公共上下文:
env、uid、route、traceId、appVersion
4. 关键代码:最小必要代码片段
4.1 统一入口接口
class Observe {
static late String env;
static late String appVersion;
static void init({required String envName, required String version}) {
env = envName;
appVersion = version;
FlutterError.onError = (details) {
error(
type: 'flutter_error',
message: details.exceptionAsString(),
stack: details.stack,
);
};
}
static void log(String tag, String message, {Map<String, dynamic>? extra}) {
final payload = {
'type': 'log',
'tag': tag,
'message': message,
'env': env,
'appVersion': appVersion,
...?extra,
};
// 转发到控制台 / 文件 / 远端日志平台
debugPrint(payload.toString());
}
static void event(String name, {Map<String, dynamic>? params}) {
final payload = {
'type': 'event',
'name': name,
'env': env,
'appVersion': appVersion,
...?params,
};
// 转发埋点 SDK
debugPrint(payload.toString());
}
static void error({
required String type,
required String message,
StackTrace? stack,
Map<String, dynamic>? extra,
}) {
final payload = {
'type': type,
'message': message,
'stack': stack?.toString(),
'env': env,
'appVersion': appVersion,
...?extra,
};
// 转发崩溃平台(如 Sentry/Firebase Crashlytics/自建)
debugPrint(payload.toString());
}
}