Appearance
监控上报链路搭建
从 0 到 1 打通 Unity → Android 原生 → 后台监控平台 → 飞书告警的完整链路:端上四层架构保证主线程不被监控拖慢,10 段链路每一段都有日志签名可定位断点,配额限流防止告警风暴。
业务 Monitor14 个
上报链路10 段
配额限流10 次/天 + 100KB/天
告警时延crash 后 5 分钟
背景
小鹏一张图的线上质量数据(crash、卡顿、性能、信号异常)需要从车机采集 → 上报 → 后台聚合 → 飞书通知 → 开发可查阅。6.0.0 版本之前,这条链路不存在——线上问题要等用户反馈才知道,SR 黑屏卡死这类问题在用户嘴里是「导航死了」,没有任何可分析的数据。
问题
- 车机不是服务器:监控逻辑跑在渲染主线程边上,采集一重就把帧率拖垮,监控自己成了被监控对象。
- 上报量必须可控:线上几万台车,一个误判的异常就能把后台和告警群刷爆。
- 链路长、断点多:从 Unity C# 到 Android Java 到 MQTT 到后台索引,任何一段静默失败,表现都是「监控网站查无此条」。
方案
端上四层架构
| 层级 | 模块 | 职责 |
|---|---|---|
| L1 业务层 | 各业务 Service | 在关键点调用采集接口 |
| L2 监控层 | 各 XXXMonitor | 轻量采集 + 定时分析 + 组装报告 |
| L3 框架层 | ExceptionMonitor | 后台线程调度 + 配额管理 + 本地日志 |
| L4 传输层 | MonitorManager | tag→type 映射 + JSON 包装 + 发 Android |
核心纪律(和异步日志同源):主线程只做轻量采集(计数/滑窗/固定队列/快照,禁止字符串拼接和堆栈采样),后台线程按 sample_time 周期做分析,命中才组装报告,统一走 SaveException 出口——本地日志必写,落盘可选,上报 Android 受配额和开关控制。
全链路 10 段
mermaid
flowchart LR
A["Monitor 异常触发<br>OnException"] --> B["SaveException<br>本地日志 + 配额判断"]
B --> C["MonitorManager<br>tag→type 映射 + JSON"]
C --> D["Unity→Android 桥<br>消息 ID 1050"]
D --> E["MonitorService<br>按 type 限流"]
E --> F["DataLogDelegate<br>Handler 切线程"]
F --> G["构造 MoleEvent<br>+ 公共参数注入"]
G --> H["MQTT 通道<br>xmart:cdu:data:VIN"]
H --> I["主动监控平台<br>索引延迟 5-30min"]
I --> J["Web 查询 +<br>飞书异常告警"]每一段都有日志签名:Unity 侧看 SendExceptionReportToAndroid: type=...,桥接看 SendUnityMsg : 1050,Android 侧看 report accepted,MQTT 看 internalSend,200。排查时按链路从前到后 grep,最后一个有日志的节点的下一跳就是断点——「上有下无」一定对应一个具体根因。
端上全通之后还有后台一关:事件要在主动监控平台注册白名单,带灰度/debug 后缀的包版本会被过滤,desc 含测试关键字会被黑词拦截,同一用户同一事件高频上报会触发反作弊,索引本身还有 5-30 分钟延迟。联调时「端上全 OK 但网站查无」基本就是这五种情况之一。
配额与闸门
- 配额:按 tag 维度 10 次/天 + 100KB/天,超限静默丢弃(只在 DebugLog 里可见),跨天自动重置。设计建议是每类异常一天只报一次,带足上下文。
- 七道闸门:Monitor 的 Active 开关、Report 开关(启动约 2 分钟内默认不上报)、本地落盘开关、配额、Android 转发总开关(ADB 可翻转)、ROM 类型(USERDEBUG 不上报)、tag→type 注册表。任何一道关着,数据就到不了 Android。
- 兜底:本地
XLog日志不受配额影响,超限了至少本地还有。
关键实现
两处真实缺陷修复
缺陷 1:上报内容变成对象哈希(620 版本监控失效)。线上发现后台收到的 desc 字段全是 XDataPropertyValue@1a2b3c 这样的对象哈希而不是 JSON——Android 侧解包信号时漏取 .value,整包被 toString() 了。后果是 620 版本监控上报实质失效:异常类型解析全部落入 UNKNOWN 桶,限流计数也不按类型分桶。修复解包逻辑后,620 版本缺陷闭环,合入 6.2.0/6.2.2。这个缺陷的教训直接写进了排查手册:端上每一段都「成功」,不代表数据是对的,必须抽查上报内容本身。
缺陷 2:限流策略与联调冲突。生产限流同 type 每天 10 次,压测时 10 次打满后链路「看起来断了」,排查半天是期望行为。后来给测试包单独放宽到 100 次(cherry-pick 专用 commit,不合入主线),并在手册里把「reach max daily report count」标注为正常日志而非故障。
6.0.0 落地的 14 个 Monitor
帧率、内存、行车障碍物、LD 行车障碍物、泊车障碍物、泊车位数据、AP 占据格、可走面、LD 引导线/可走面、车道线、行车消息耗时、消息全路径耗时、占据格消息耗时、状态监控。判定标准对齐产品体感:画面冻结、帧率、tile 空白、可走面/可走线缺失、内存异常、脚本异常——监控项是从「产品接受不了什么」倒推出来的,不是从技术能采什么正推的。
指标
| 指标 | 采集方式 | 告警阈值 |
|---|---|---|
| Crash 率 | Unity Exception Handler | > 0.1%/日 |
| 帧率 | 每 5 分钟采样 | < 30fps |
| GPU 占用 | Android GPU Inspector | > 80% |
| 内存 | Unity Profiler API | > 500MB |
| 卡顿 | 主线程超时检测 | > 200ms |
- 620 版本监控上报失效缺陷闭环(合入 6.2.0/6.2.2)。
- 线上问题从「用户反馈才知道」变成「crash 后 5 分钟飞书通知」,告警带车型、版本、异常概要,可一键认领/忽略。
- 14 个业务 Monitor 覆盖数据、性能、稳定性三类异常,接入有 SOP:新增 Monitor 必须注册 tag→type 映射,否则上报会被打到默认桶归错类。
复盘
- 监控系统的第一需求是「别影响业务」。主线程轻量采集、后台分析、统一出口这三条纪律,比任何单个 Monitor 的判定逻辑都重要。
- 静默丢弃一定要有旁路可见性。配额超限不打 warn 是防刷屏的正确设计,但没有 DebugLog 旁路的话,联调会被自己的限流骗到。
- 长链路靠日志签名排查,不靠猜。10 段链路每段一个 grep 关键字,断点定位从半天缩到十分钟。
- 先修链路,再信数据。620 版本「监控没报异常」一度被解读为「质量变好」,实际是上报失效——监控自己的健康度也要被监控。