Administrator
发布于 2026-09-02 / 1,274 阅读
0
0

鸿蒙埋点与监控体系:日志、Crash、性能的统一采集

系列文章《鸿蒙开发从入门到专家》第 38 篇 · 专家

代码能写好只是开始。线上才是真相:用户卡在哪个页面、为什么 ANR、为什么崩溃、上个月改的功能到底有没有人用。这些问题要靠可观测体系回答——日志、Crash 监控、性能采集、用户行为分析,都要规划到位。

这一篇把鸿蒙应用的可观测性分四个层次拆开讲:日志、Crash、性能、用户行为。每个层次有独立的工具和落地动作,但数据上报要走统一的协议。

一、什么是可观测性

可观测性(Observability)源自控制系统理论,指从系统外部输出推断系统内部状态的能力。映射到鸿蒙应用:

  • 日志(Logs):发生了什么,一条条离散事件。

  • 指标(Metrics):数值随时间变化,如 FPS、CPU、内存。

  • 链路(Traces):一次请求的完整路径。

鸿蒙生态的工具集:

维度

官方能力

第三方

日志

hilog

自建 / Sentry

Crash

hiAppEvent

Bugly、AppGallery Connect

性能

Profiler、hiTraceMeter

自建 APM

行为

自研 SDK

GrowingIO、神策

综合

-

DataBuddy、Firebase 兼容层

下面按层次逐个讲。

二、日志体系:把打印变成数据

hilog 是鸿蒙的官方日志 API,开发者随手就调,但日志要被当成"可观测性数据",不能只是 console.log。

1. 结构化日志

import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN = 0x0001;
const TAG = 'LoginFlow';

export function logLoginStart() {
  hilog.info(DOMAIN, TAG, 'login.start', JSON.stringify({ source: 'wechat' }));
}

export function logLoginError(err: Error) {
  hilog.error(DOMAIN, TAG, 'login.error', JSON.stringify({
    name: err.name,
    message: err.message,
    stack: err.stack,
  }));
}

要点:

  • 日志格式用 JSON,利于服务端解析。

  • DOMAIN 按业务线划分。

  • 必须有 event name,便于检索。

2. 上报到服务器

import { http } from '@kit.NetworkKit';

class LogUploader {
  private buffer: LogEntry[] = [];

  add(entry: LogEntry) {
    this.buffer.push(entry);
    if (this.buffer.length >= 50) this.flush();
  }

  async flush() {
    if (!this.buffer.length) return;
    const entries = this.buffer;
    this.buffer = [];
    await http.request('https://log.example.com/v1/ingest', {
      method: 'POST',
      body: JSON.stringify(entries),
    }).catch(() => {
      // 上传失败,先写本地,后续重试
      entries.forEach(e => LocalStorage.save(e));
    });
  }
}

要点:

  • 批量上报,节省网络。

  • 失败要落本地,下次启动重试。

  • 上报频率随网络场景调整。

3. 采样策略

不是所有日志都全量上报。debug 阶段可以全开,生产环境采样:

function shouldSample(): boolean {
  return Math.random() < 0.1; // 10% 采样
}

风险高的链路(如支付)采样 100%,长尾路径采样 10%。

三、Crash 监控:捕获用户侧的"硬错误"

Crash 是用户感知最强的。鸿蒙的 hiAppEvent 已经能捕获到崩溃事件,但要做得更多。

1. 订阅崩溃事件

import { hiAppEvent } from '@kit.PerformanceAnalysisKit';

hiAppEvent.addWatcher({
  watchId: 'crashWatcher',
  appEventFilters: [
    { domain: 'app_crash', eventTypes: [hiAppEvent.EventType.CRASH, hiAppEvent.EventType.ANR] },
  ],
  onReceive: (events) => {
    events.forEach((evt) => {
      uploadCrash({
        type: evt.name,
        stack: evt.params['stack'] as string,
        fingerprint: hash(evt.params['stack'] as string),
        device: deviceInfo(),
      });
    });
  },
});

注意:崩溃事件在 onReceive 回调里可能没有足够上下文(比如当前页面、业务参数)。可以加一层"前置缓存":

class CrashContext {
  static current = '';

  static set(action: string) { this.current = action; }
  static clear() { this.current = ''; }
}

// Wrap 在路由拦截器里
router.beforeEach((to) => CrashContext.set(`nav.to.${to.name}`));

// 崩溃时附加上
hiAppEvent.on('app_crash', () => upload({ ...event, scene: CrashContext.current }));

2. 堆栈还原

上线包混淆过,原始堆栈是不可读的,需要符号化:

  1. CI 构建产物时同步生成 mapping.txt。

  2. 上传 mapping 到崩溃后台。

  3. 客户端上传崩溃堆栈时附 version + buildId。

  4. 后台用 mapping 文件做符号化。

DevEco 的混淆工具会输出 .map 文件:

obfuscator/transformer --config build-profile.json5 --out map/

CI 里:

after_script:
  - ./tools/upload_mapping.sh build/r8-map/ $BUILD_ID

3. 多端崩溃聚合

后端要做 stack fingerprint 聚合:

def fingerprint(stack: str) -> str:
    # 取每帧的前 3 行
    lines = [l for l in stack.split('\n') if 'at ' in l][:3]
    return hash('\n'.join(lines))

聚合后展示 TOP 崩溃 + 影响用户数。

四、性能监控:FPS、卡顿、冷启动

性能监控比日志精细、要求更高。

1. 帧率(FPS)

@kit.ArkUI 提供 Profiler API,可以用 Frame 类监控:

import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';

@Entry
@Component
struct MonitorPage {
  private fpsSamples: number[] = [];

  aboutToAppear() {
    setInterval(() => {
      hiTraceMeter.startTrace('fps.measure', 1);
      // 计算当前帧率(具体实现可参考 Profiler Sample)
      hiTraceMeter.finishTrace('fps.measure', 1);
    }, 1000);
  }

  build() { Column() { Text('帧率监控').fontSize(18) } }
}

更简洁的做法是订阅 Profiler 的实时数据流(生产用第三方封装)。

2. 冷启动时间

冷启动测量有一个标准锚点:从系统创建 Ability 到第一帧渲染完。

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    const start = Date.now();
    this.startTimestamp = start;
  }

  onWindowStageCreate(windowStage: WindowStage) {
    windowStage.on('firstFrameDrawn', () => {
      const cost = Date.now() - this.startTimestamp;
      uploadMetric({ event: 'cold_start', duration: cost });
    });
  }
}

冷启动时间超过 2 秒就要优化,4 秒以上用户基本就要卸载了。

3. 卡顿(ANR/Freeze)

第 31 篇已经讲了 Profiler 工具,线上用 hiAppEvent 订阅 appfreeze:

hiAppEvent.addWatcher({
  watchId: 'freezeWatcher',
  appEventFilters: [{ domain: 'appfreeze', eventTypes: [hiAppEvent.EventType.CARD, hiAppEvent.EventType.TIMEOUT] }],
  onReceive: (events) => uploadMetrics(events),
});

阈值通常设 5s,超过就上报。

五、用户行为:从点击到漏斗

业务最关心的不是技术问题,是"用了没"“转化怎么”。这部分通常用商业 SDK,但埋点设计要规范。

1. 事件结构

type Event = {
  event: string;          // 'order.submit'
  properties: object;     // { price: 99, itemCount: 3 }
  user: string;           // unionId
  appVersion: string;
  osVersion: string;
  ts: number;
};

export function track(event: string, props?: object) {
  Bus.emit(event, { ...props, event, ts: Date.now() });
}

页面浏览:

aboutToAppear() {
  track('page.view', { page: 'product_detail', id: this.productId });
}

按钮点击:

Button('立即购买').onClick(() => {
  track('click.buy_now', { id: this.productId });
});

2. 漏斗与归因

服务端按 event + userId + ts 排序,构造漏斗:

register    1000 人
  ↓
open_app    900 人
  ↓
search      600 人
  ↓
product_view 200 人
  ↓
order_submit 50 人

每一段的转化率看一眼就知道问题在哪。

3. 隐私合规

埋点涉及隐私,用户协议和隐私弹窗一定要先做。业务部门要委托法务审。常见的合规要点:

  • 收集前给用户知情同意。

  • 不采集敏感数据(身份证、银行卡 CVV、生物特征原值)。

  • 提供"取消埋点"的开关。

  • 数据保留期限合规(一般 6 个月到 1 年)。

六、可观测性的整体架构

上面四块分开看,实际建设时要打通:

       客户端                          服务端
 ┌──────────────────────┐        ┌──────────────┐
 │  日志 / Crash / 性能  │ ─────► │  接入层       │
 │  行为 / 链路          │  HTTPS │  Kafka / Pulsar │
 │  统一 SDK 上报        │        │  实时分发      │
 └──────────────────────┘        └────┬─────────┘
                                      │
                             ┌────────┼────────┐
                             ▼        ▼        ▼
                          日志检索   监控告警   数据仓库
                           (ES)    (Grafana)  (ClickHouse)

数据双写:冷数据落仓、热数据进流处理系统。

七、案例:电商 App 可观测体系演进

V1:凑活用

只用 console.log + Bugly。前期勉强够用,一上量开始崩:Bugly 上崩溃率从 0.5% 涨到 2%,客服根本不知道发生什么。

V2:分层接入

  • hilog 结构化日志 + 自建上报系统。

  • hiAppEvent 订阅崩溃 + ANR。

  • 性能指标接入 DataBuddy。

  • 埋点 SDK 用自研,统一协议。

V3:一体化平台

所有指标进同一个 Grafana。开发、QA、产品都能拉数据看趋势。崩溃率异常、性能下降、漏斗变化都有报警。

收益:

  • 线上 bug 排查时间从"3 小时 + 多次提单"压缩到"30 分钟内"。

  • 漏斗诊断后,"搜索→商品详情"转化率从 38% 提升到 56%。

  • 灰度期间通过性能监控拦截掉一次内存泄漏。

小结

可观测性是"上线以后的事",但要在"上线之前"想清楚:

  • 日志:结构化、批量上报、失败本地重试。

  • Crash:订阅 + 堆栈还原 + 聚合。

  • 性能:FPS、ANR、冷启动三件套。

  • 行为:统一埋点协议 + 隐私合规。

  • 统一:所有数据走一份协议、同一接入层。

下一篇我们要从工程化角度收束——聊聊鸿蒙应用架构模式从 MVC 到 MVI 的演进,把过去几十篇讲的代码组织思路串成一条主线。

参考阅读

  • 鸿蒙开发者文档《hilog 使用指南》

  • 鸿蒙开发者文档《hiAppEvent 上报》

  • 《Observability Engineering》(Honeycomb 团队著作)

  • 《数据驱动:从方法到实践》(业务分析)


评论