HarmonyOS开发:线上监控
HarmonyOS开发:线上监控
📌 核心要点:线上监控是应用的"心电图"——崩溃率、ANR率、启动时间、关键转化率,这些指标实时跳动,异常时立刻告警,让你在用户投诉之前就发现问题。
背景与动机
你发了一个新版本,心里忐忑但自我安慰"应该没问题"。三天后,客服反馈说用户投诉量暴增。你打开后台一看,崩溃率从0.1%飙升到3%,已经炸了三天了。
三天!你居然三天之后才知道出了问题。
没有监控的应用就像蒙着眼开车——你不知道前面是坦途还是悬崖,直到撞上去。
线上监控要解决的核心问题:
- 崩溃监控:应用崩溃了多少次?什么原因?哪些设备上?
- ANR监控:应用卡死了多少次?卡在哪个函数?
- 性能监控:启动时间多少?帧率多少?内存占用多少?
- 业务监控:关键转化率是否正常?支付成功率有没有下降?
- 告警通知:指标异常时第一时间通知到人,而不是三天后才知道
鸿蒙应用的线上监控有其特殊性——HiLog日志体系、HiAppEvent事件打点、HiSysEvent系统事件,这些都是鸿蒙特有的监控基础设施。
核心原理
线上监控体系的架构:数据采集 → 数据上报 → 数据分析 → 告警通知。
鸿蒙监控基础设施:
| 工具 | 用途 | 特点 |
|---|---|---|
| HiLog | 日志输出 | 分级日志(DEBUG/INFO/WARN/ERROR/FATAL) |
| HiAppEvent | 应用事件打点 | 结构化事件,支持自定义参数 |
| HiSysEvent | 系统事件监听 | 监听系统级事件(崩溃、ANR等) |
| HiTraceMeter | 性能追踪 | 函数级耗时追踪 |
| HiCollie | 看门狗 | 检测应用卡死(ANR) |
代码实战
基础用法:崩溃监控
崩溃是最严重的线上问题,必须第一时间捕获和上报。
// entry/src/main/ets/monitor/CrashMonitor.ets
// 崩溃监控器
import { hiAppEvent } from '@kit.AnalysisHiAppEvent';
import { hilog } from '@kit.PerformanceAnalysisKit';
export class CrashMonitor {
private static DOMAIN = 0x0001; // 应用事件域
private static TAG = 'CrashMonitor';
/**
* 初始化崩溃监控
* 在Ability的onCreate中调用
*/
static init(): void {
// 1. 配置HiAppEvent
hiAppEvent.configure({
disable: false, // 启用事件打点
maxStorage: '100M', // 最大存储空间
});
// 2. 注册崩溃回调
// HarmonyOS会在应用崩溃时写入崩溃日志
// 我们在下次启动时读取并上报
hilog.info(0x0001, this.TAG, '崩溃监控已初始化');
}
/**
* 检查上次是否有崩溃
* 在应用启动时调用
*/
static async checkLastCrash(): Promise<void> {
try {
// 检查崩溃标记文件
const crashFlag = await this.readCrashFlag();
if (crashFlag && crashFlag.hasCrash) {
hilog.warn(0x0001, this.TAG, `检测到上次崩溃: ${crashFlag.crashTime}`);
// 读取崩溃堆栈
const crashStack = await this.readCrashStack();
// 上报崩溃信息
await this.reportCrash({
crashTime: crashFlag.crashTime,
crashType: crashFlag.crashType || 'unknown',
crashStack: crashStack,
appVersion: crashFlag.appVersion,
deviceInfo: await this.getDeviceInfo(),
});
// 清除崩溃标记
await this.clearCrashFlag();
}
// 设置当前运行标记(用于检测下次是否崩溃)
await this.setRunningFlag();
} catch (error) {
hilog.error(0x0001, this.TAG, `崩溃检查失败: ${error}`);
}
}
/**
* 手动上报异常
* 用于捕获try-catch中的异常
*/
static async reportError(error: Error, context?: string): Promise<void> {
const errorInfo = {
errorType: error.name,
errorMessage: error.message,
errorStack: error.stack || '',
context: context || '',
timestamp: Date.now(),
appVersion: await this.getAppVersion(),
deviceInfo: await this.getDeviceInfo(),
};
// 使用HiAppEvent打点
hiAppEvent.write({
domain: this.DOMAIN,
name: 'APP_ERROR',
eventType: hiAppEvent.EventType.FAULT,
params: errorInfo,
});
// 同时上报到自建监控平台
await this.uploadToMonitorServer(errorInfo);
hilog.error(0x0001, this.TAG, `异常已上报: ${error.name} - ${error.message}`);
}
/**
* 上报崩溃信息到监控服务端
*/
private static async reportCrash(crashInfo: CrashInfo): Promise<void> {
// 使用HiAppEvent打点(华为分析平台可查看)
hiAppEvent.write({
domain: this.DOMAIN,
name: 'APP_CRASH',
eventType: hiAppEvent.EventType.FAULT,
params: crashInfo,
});
// 上报到自建监控平台
await this.uploadToMonitorServer(crashInfo);
}
/**
* 上报到自建监控服务端
*/
private static async uploadToMonitorServer(data: Record<string, Object>): Promise<void> {
try {
// 实际实现使用http模块上报
// 这里简化处理
hilog.info(0x0001, this.TAG, '监控数据已上报');
} catch (error) {
hilog.error(0x0001, this.TAG, `监控数据上报失败: ${error}`);
}
}
// 辅助方法(简化实现)
private static async readCrashFlag(): Promise<CrashFlag | null> { return null; }
private static async readCrashStack(): Promise<string> { return ''; }
private static async clearCrashFlag(): Promise<void> {}
private static async setRunningFlag(): Promise<void> {}
private static async getAppVersion(): Promise<string> { return '3.2.0'; }
private static async getDeviceInfo(): Promise<DeviceInfo> {
return { model: 'unknown', osVersion: 'unknown', memory: 0 };
}
}
interface CrashInfo {
crashTime: number;
crashType: string;
crashStack: string;
appVersion: string;
deviceInfo: DeviceInfo;
}
interface CrashFlag {
hasCrash: boolean;
crashTime: number;
crashType?: string;
appVersion?: string;
}
interface DeviceInfo {
model: string;
osVersion: string;
memory: number;
}
进阶用法:ANR监控与性能监控
ANR(Application Not Responding)是用户最反感的体验之一——应用卡住了,啥也干不了。
// entry/src/main/ets/monitor/PerformanceMonitor.ets
// 性能监控器
import { hiAppEvent } from '@kit.AnalysisHiAppEvent';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
export class PerformanceMonitor {
private static DOMAIN = 0x0001;
private static TAG = 'PerfMonitor';
// 性能指标阈值
private static readonly THRESHOLDS = {
coldStartMs: 3000, // 冷启动阈值:3秒
hotStartMs: 1000, // 热启动阈值:1秒
pageRenderMs: 500, // 页面渲染阈值:500ms
frameDropRate: 5, // 掉帧率阈值:5%
memoryWarningMb: 400, // 内存告警阈值:400MB
};
/**
* 监控冷启动耗时
*/
static monitorColdStart(): void {
const startTime = Date.now();
// 在EntryAbility的onWindowStageCreate中结束计时
// 实际使用时通过回调或全局变量传递startTime
AppStorage.setOrCreate('cold_start_time', startTime);
}
/**
* 记录冷启动完成
*/
static reportColdStartComplete(): void {
const startTime = AppStorage.get<number>('cold_start_time', 0);
if (startTime === 0) return;
const duration = Date.now() - startTime;
const isSlow = duration > this.THRESHOLDS.coldStartMs;
hiAppEvent.write({
domain: this.DOMAIN,
name: 'COLD_START',
eventType: hiAppEvent.EventType.STATISTIC,
params: {
duration: duration,
isSlow: isSlow,
threshold: this.THRESHOLDS.coldStartMs,
}
});
if (isSlow) {
hilog.warn(0x0001, this.TAG, `冷启动耗时过长: ${duration}ms (阈值: ${this.THRESHOLDS.coldStartMs}ms)`);
} else {
hilog.info(0x0001, this.TAG, `冷启动耗时: ${duration}ms`);
}
}
/**
* 监控页面渲染耗时
*/
static startPageTrace(pageName: string): string {
const traceId = `page_${pageName}_${Date.now()}`;
hiTraceMeter.startTrace(traceId, 1);
return traceId;
}
static finishPageTrace(traceId: string, pageName: string): void {
hiTraceMeter.finishTrace(traceId, 1);
// 记录页面渲染事件
hiAppEvent.write({
domain: this.DOMAIN,
name: 'PAGE_RENDER',
eventType: hiAppEvent.EventType.STATISTIC,
params: {
pageName: pageName,
timestamp: Date.now(),
}
});
}
/**
* 监控函数执行耗时
*/
static async measureFunction<T>(
funcName: string,
fn: () => Promise<T>,
thresholdMs: number = 1000
): Promise<T> {
const startTime = Date.now();
try {
const result = await fn();
const duration = Date.now() - startTime;
if (duration > thresholdMs) {
hilog.warn(0x0001, this.TAG, `函数 ${funcName} 耗时过长: ${duration}ms`);
hiAppEvent.write({
domain: this.DOMAIN,
name: 'SLOW_FUNCTION',
eventType: hiAppEvent.EventType.STATISTIC,
params: {
functionName: funcName,
duration: duration,
threshold: thresholdMs,
}
});
}
return result;
} catch (error) {
const duration = Date.now() - startTime;
// 函数执行异常也要记录
hiAppEvent.write({
domain: this.DOMAIN,
name: 'FUNCTION_ERROR',
eventType: hiAppEvent.EventType.FAULT,
params: {
functionName: funcName,
duration: duration,
error: String(error),
}
});
throw error;
}
}
/**
* 监控内存使用
*/
static checkMemoryWarning(): void {
// 获取当前内存使用
// 实际实现通过性能API获取
const currentMemoryMb = 0; // 占位
if (currentMemoryMb > this.THRESHOLDS.memoryWarningMb) {
hilog.warn(0x0001, this.TAG, `内存使用过高: ${currentMemoryMb}MB`);
hiAppEvent.write({
domain: this.DOMAIN,
name: 'MEMORY_WARNING',
eventType: hiAppEvent.EventType.STATISTIC,
params: {
memoryMb: currentMemoryMb,
threshold: this.THRESHOLDS.memoryWarningMb,
}
});
}
}
}
完整示例:告警规则与通知
监控数据采集了,但没人看等于白采。告警系统是监控的"嘴巴"——指标异常时主动通知到人。
# alert_service.py - 告警服务
import time
import json
import requests
from datetime import datetime, timedelta
from typing import List, Dict, Optional
class AlertRule:
"""告警规则"""
def __init__(self, name: str, metric: str, operator: str,
threshold: float, duration_minutes: int = 5,
severity: str = 'warning', channels: List[str] = None):
self.name = name
self.metric = metric
self.operator = operator # gt, lt, eq, gte, lte
self.threshold = threshold
self.duration_minutes = duration_minutes
self.severity = severity # info, warning, critical
self.channels = channels or ['dingtalk']
def evaluate(self, value: float) -> bool:
"""评估告警条件"""
ops = {
'gt': lambda v, t: v > t,
'lt': lambda v, t: v < t,
'eq': lambda v, t: v == t,
'gte': lambda v, t: v >= t,
'lte': lambda v, t: v <= t,
}
return ops.get(self.operator, lambda v, t: False)(value, self.threshold)
class AlertService:
"""告警服务"""
# 预定义告警规则
DEFAULT_RULES = [
AlertRule(
name='崩溃率过高',
metric='crash_rate',
operator='gt',
threshold=1.0, # 崩溃率 > 1%
duration_minutes=5,
severity='critical',
channels=['dingtalk', 'sms']
),
AlertRule(
name='ANR率过高',
metric='anr_rate',
operator='gt',
threshold=0.5, # ANR率 > 0.5%
duration_minutes=10,
severity='warning',
channels=['dingtalk']
),
AlertRule(
name='冷启动耗时过长',
metric='cold_start_p99',
operator='gt',
threshold=5000, # P99 > 5秒
duration_minutes=30,
severity='warning',
channels=['dingtalk']
),
AlertRule(
name='关键转化率下降',
metric='key_conversion_rate',
operator='lt',
threshold=80, # 转化率 < 80%
duration_minutes=60,
severity='warning',
channels=['dingtalk']
),
AlertRule(
name='API错误率过高',
metric='api_error_rate',
operator='gt',
threshold=5.0, # 错误率 > 5%
duration_minutes=5,
severity='critical',
channels=['dingtalk', 'sms']
),
]
def __init__(self, monitor_api_url: str):
self.monitor_api_url = monitor_api_url
self.rules = self.DEFAULT_RULES.copy()
self.alert_history: List[dict] = []
self.last_alert_time: Dict[str, datetime] = {}
def add_rule(self, rule: AlertRule):
"""添加自定义告警规则"""
self.rules.append(rule)
def check_alerts(self, metrics: Dict[str, float]) -> List[dict]:
"""检查所有告警规则"""
triggered_alerts = []
for rule in self.rules:
value = metrics.get(rule.metric)
if value is None:
continue
if rule.evaluate(value):
# 检查是否在静默期内(避免频繁告警)
if self._is_in_silence_period(rule.name):
continue
alert = {
'rule_name': rule.name,
'metric': rule.metric,
'current_value': value,
'threshold': rule.threshold,
'operator': rule.operator,
'severity': rule.severity,
'channels': rule.channels,
'timestamp': datetime.now().isoformat(),
}
triggered_alerts.append(alert)
self.last_alert_time[rule.name] = datetime.now()
self.alert_history.append(alert)
return triggered_alerts
def send_alerts(self, alerts: List[dict]):
"""发送告警通知"""
for alert in alerts:
for channel in alert['channels']:
if channel == 'dingtalk':
self._send_dingtalk(alert)
elif channel == 'sms':
self._send_sms(alert)
elif channel == 'feishu':
self._send_feishu(alert)
def _is_in_silence_period(self, rule_name: str) -> bool:
"""检查是否在静默期内(同一规则30分钟内不重复告警)"""
last_time = self.last_alert_time.get(rule_name)
if last_time and (datetime.now() - last_time) < timedelta(minutes=30):
return True
return False
def _send_dingtalk(self, alert: dict):
"""发送钉钉告警"""
severity_emoji = {
'info': 'ℹ️',
'warning': '⚠️',
'critical': '🚨',
}
message = {
"msgtype": "markdown",
"markdown": {
"title": f"{severity_emoji.get(alert['severity'], '⚠️')} {alert['rule_name']}",
"text": f"""
## {severity_emoji.get(alert['severity'], '⚠️')} {alert['rule_name']}
**严重程度**: {alert['severity'].upper()}
**指标**: {alert['metric']}
**当前值**: {alert['current_value']}
**阈值**: {alert['threshold']}
**时间**: {alert['timestamp']}
> 请尽快处理!
"""
}
}
# 发送到钉钉Webhook
webhook_url = "https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
try:
requests.post(webhook_url, json=message, timeout=5)
except Exception as e:
print(f"钉钉告警发送失败: {e}")
def _send_sms(self, alert: dict):
"""发送短信告警(仅critical级别)"""
if alert['severity'] != 'critical':
return
# 调用短信API
print(f"📱 短信告警: {alert['rule_name']} - 当前值 {alert['current_value']}")
def _send_feishu(self, alert: dict):
"""发送飞书告警"""
print(f"📨 飞书告警: {alert['rule_name']}")
# 告警服务运行循环
def run_alert_service():
"""定时检查告警"""
alert_service = AlertService(monitor_api_url="https://your-monitor-api.com")
while True:
try:
# 1. 获取最新监控指标
metrics = fetch_latest_metrics()
# 2. 检查告警
alerts = alert_service.check_alerts(metrics)
# 3. 发送告警
if alerts:
alert_service.send_alerts(alerts)
for alert in alerts:
print(f"🚨 告警触发: {alert['rule_name']} (当前值: {alert['current_value']})")
except Exception as e:
print(f"告警检查异常: {e}")
# 每分钟检查一次
time.sleep(60)
def fetch_latest_metrics() -> Dict[str, float]:
"""从监控平台获取最新指标"""
# 实际实现从监控API获取
return {
'crash_rate': 0.3,
'anr_rate': 0.1,
'cold_start_p99': 2800,
'key_conversion_rate': 92,
'api_error_rate': 1.2,
}
if __name__ == '__main__':
run_alert_service()
踩坑与注意事项
坑1:监控数据量太大
每个操作都打点,每天产生几亿条事件,存储成本暴增,查询变慢。
解决方案:分级采集,关键事件全量采集,普通事件采样采集。
// 采样采集策略
class SamplingStrategy {
// 关键事件:100%采集
static readonly CRITICAL_EVENTS = ['APP_CRASH', 'APP_ANR', 'PAYMENT_FAILED'];
// 普通事件:10%采样
static shouldSample(eventName: string): boolean {
if (this.CRITICAL_EVENTS.includes(eventName)) {
return true; // 关键事件始终采集
}
// 基于用户ID哈希采样
const userId = this.getCurrentUserId();
const hash = this.simpleHash(userId);
return (hash % 100) < 10; // 10%采样率
}
private static simpleHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash & hash;
}
return Math.abs(hash);
}
private static getCurrentUserId(): string {
return 'user_id_placeholder';
}
}
坑2:告警风暴
一个指标异常触发几十条告警,手机被轰炸,反而忽略了真正重要的信息。
解决方案:告警聚合+静默期+分级通知。
# 告警聚合:同一规则30分钟内只告警一次
# critical级别:钉钉+短信
# warning级别:仅钉钉
# info级别:仅记录,不通知
# 静默期配置
SILENCE_PERIODS = {
'critical': timedelta(minutes=15), # critical级别15分钟静默
'warning': timedelta(minutes=30), # warning级别30分钟静默
'info': timedelta(hours=1), # info级别1小时静默
}
坑3:监控影响应用性能
打点操作太频繁,本身就成了性能瓶颈。
解决方案:批量上报、异步写入、限制打点频率。
// 批量上报管理器
class BatchReporter {
private static eventQueue: Array<Record<string, Object>> = [];
private static readonly MAX_BATCH_SIZE = 50; // 最大批量大小
private static readonly FLUSH_INTERVAL = 30000; // 30秒上报一次
private static flushTimer: number | null = null;
/**
* 添加事件到队列
*/
static addEvent(event: Record<string, Object>): void {
this.eventQueue.push(event);
// 队列满了立即上报
if (this.eventQueue.length >= this.MAX_BATCH_SIZE) {
this.flush();
}
// 启动定时上报
if (this.flushTimer === null) {
this.flushTimer = setTimeout(() => {
this.flush();
}, this.FLUSH_INTERVAL);
}
}
/**
* 批量上报
*/
static async flush(): Promise<void> {
if (this.eventQueue.length === 0) return;
const batch = [...this.eventQueue];
this.eventQueue = [];
// 清除定时器
if (this.flushTimer !== null) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
// 异步上报
try {
await this.uploadBatch(batch);
} catch (error) {
// 上报失败,重新放回队列(限制重试次数)
if (this.eventQueue.length < this.MAX_BATCH_SIZE * 2) {
this.eventQueue.unshift(...batch);
}
}
}
private static async uploadBatch(events: Array<Record<string, Object>>): Promise<void> {
// 实际上报逻辑
}
}
坑4:崩溃堆栈被混淆
Release构建的代码是混淆过的,崩溃堆栈里全是a.b.c,根本看不懂。
解决方案:保留sourceMap文件,用反混淆工具还原。
# 构建时保留sourceMap
# build-profile.json5中配置
{
"arkOptions": {
"compilerOptions": {
"sourceMap": true # 生成sourceMap
}
}
}
# sourceMap文件位置
# entry/build/default/outputs/default/sourceMaps/
# 崩溃堆栈反混淆
# 使用DevEco Studio的反混淆工具
# Tools → Reverse Code Obfuscation
坑5:ANR检测不准确
HiCollie看门狗的检测阈值是固定的,有些场景(比如首次加载大量数据)会误报ANR。
解决方案:对已知耗时场景设置白名单,或者调整检测阈值。
// ANR白名单
const ANR_WHITELIST = [
'FirstLaunchActivity', // 首次启动允许较长时间
'DataMigrationPage', // 数据迁移允许较长时间
];
function shouldReportANR(activityName: string, durationMs: number): boolean {
if (ANR_WHITELIST.includes(activityName)) {
return durationMs > 10000; // 白名单页面10秒才算ANR
}
return durationMs > 5000; // 普通页面5秒算ANR
}
HarmonyOS 6适配说明
HarmonyOS 6对线上监控的改进:
-
HiAppEvent 2.0:新版事件打点支持更丰富的参数类型和更灵活的事件定义。
-
实时崩溃上报:HarmonyOS 6支持崩溃实时上报,不需要等到下次启动。
-
ANR检测增强:HiCollie 2.0支持自定义检测阈值和主线程卡顿检测。
-
性能基线:HarmonyOS 6提供了各设备型号的性能基线数据,可以对比判断应用性能是否正常。
-
隐私合规:HiAppEvent 2.0内置隐私合规检查,确保采集的数据不包含用户敏感信息。
总结
线上监控是应用运营的"眼睛"——没有监控,你就是在黑暗中摸索,用户出了问题你比谁都晚知道。
| 维度 | 评价 |
|---|---|
| 学习难度 | ⭐⭐⭐⭐ 监控体系搭建需要理解HiAppEvent、告警规则设计、数据采集策略 |
| 使用频率 | ⭐⭐⭐⭐⭐ 监控是持续运行的,7x24小时不间断 |
| 重要程度 | ⭐⭐⭐⭐⭐ 没有监控的应用就是"裸奔",线上问题只能靠用户反馈 |
几个关键提醒:
- 崩溃监控是底线,应用上线第一天就要有
- 告警要精准,告警太多等于没有告警,关键指标异常才告警
- 监控数据要分级,关键事件全量采集,普通事件采样采集
- 批量上报,别每次打点都发网络请求,攒一批一起发
- sourceMap必须保留,混淆后的崩溃堆栈没有sourceMap就是废纸
监控搭好了,出了问题怎么定位?下一篇文章讲故障排查——从日志分析到堆栈解读,从远程调试到故障复盘。
更多推荐


所有评论(0)