HarmonyOS日志系统实战:从HiLog到云日志分析
AppGallery Connect(AGC)提供了开箱即用的性能管理服务(APMS),无需集成SDK即可自动采集应用的崩溃、ANR、页面性能等数据。核心能力异常管理:自动采集崩溃、应用无响应等性能管理:启动耗时、页面切换耗时、网络请求分析单点查询:查看特定用户的性能数据日志回捞:按需拉取指定设备的日志// 记录自定义日志,供回捞使用// 使用hilog正常打印// 同时记录到APMS自定义日志缓
HarmonyOS日志系统实战:从HiLog到云日志分析
一、HiLog日志系统基础
1.1 HiLog是什么?
HiLog是HarmonyOS原生提供的日志系统,专为分布式应用场景设计。与传统的System.out.println或Android的Log.d不同,HiLog在设计之初就考虑了多设备协同、性能开销和安全隐私等维度。
HiLog的核心设计目标:
- 分级日志:支持DEBUG、INFO、WARN、ERROR、FATAL五级日志
- 标签化分类:通过domain和tag双维度标识日志来源
- 性能友好:异步写入机制,避免阻塞主线程
- 安全打印:内置隐私标识符,防止敏感信息泄露
- 跨设备聚合:结合分布式软总线,支持多设备日志关联
1.2 快速上手:ArkTS环境下的HiLog使用
1.2.1 导入模块
import { hilog } from '@kit.PerformanceAnalysisKit';
1.2.2 基础API示例
// 定义日志标签
const DOMAIN: number = 0xF811; // 业务领域标识,范围0x0000~0xFFFF
const TAG: string = 'LoginModule';
@Entry
@Component
struct Index {
build() {
Column() {
Button('用户登录')
.onClick(() => {
// DEBUG级别:开发调试用
hilog.debug(DOMAIN, TAG, '用户点击登录按钮,时间戳:%{public}d', Date.now());
// 模拟登录逻辑
this.login('test_user_123');
})
}
}
login(userId: string) {
// INFO级别:记录关键业务流程
hilog.info(DOMAIN, TAG, '开始处理登录请求,userId:%{private}s', userId);
try {
// 模拟网络请求
let result = this.callLoginApi(userId);
if (result.success) {
hilog.info(DOMAIN, TAG, '登录成功,userId:%{private}s', userId);
} else {
// WARN级别:潜在问题但业务可继续
hilog.warn(DOMAIN, TAG, '登录返回异常状态,错误码:%{public}d', result.code);
}
} catch (error) {
// ERROR级别:记录异常信息
hilog.error(DOMAIN, TAG, '登录过程异常:%{public}s', error.message);
}
}
callLoginApi(userId: string) {
// 模拟实现
return { success: true, code: 200 };
}
}
1.3 C++环境下的HiLog使用(Native层)
对于性能敏感或涉及Native代码的场景,HiLog同样提供了C/C++接口。
1.3.1 CMake配置
# CMakeLists.txt
target_link_libraries(entry PUBLIC libhilog_ndk.z.so)
1.3.2 代码实现
#include "hilog/log.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 业务领域
#define LOG_TAG "NativeEngine"
void ProcessVideoFrame(const char* frameData, int length) {
// 检查日志级别是否可用
if (OH_LOG_IsLoggable(LOG_DOMAIN, LOG_TAG, LOG_INFO)) {
OH_LOG_INFO(LOG_APP, "开始处理视频帧,长度:%{public}d", length);
}
// 业务处理逻辑
int result = DecodeFrame(frameData, length);
if (result < 0) {
// 使用隐私标识:%{private}s 会输出 <private>
OH_LOG_ERROR(LOG_APP, "视频解码失败,帧数据:%{private}s,错误码:%{public}d",
frameData, result);
} else {
OH_LOG_DEBUG(LOG_APP, "视频解码成功,耗时:%{public}dms", result);
}
}
运行结果示例(DevEco Studio控制台):
12-11 12:21:47.579 2695 2695 I A03200/NativeEngine: 开始处理视频帧,长度:1024
12-11 12:21:47.592 2695 2695 E A03200/NativeEngine: 视频解码失败,帧数据:<private>,错误码:-1
1.4 核心参数详解
| 参数 | 说明 | 使用建议 |
|---|---|---|
| domain | 业务领域标识,取值范围0x0000~0xFFFF | 按模块分配固定区间,如登录模块0xF801~0xF820 |
| tag | 日志标签,字符串类型,最长31字节 | 采用“模块_子功能”命名,如Login_Network |
| level | 日志级别:DEBUG/INFO/WARN/ERROR/FATAL | 按场景选择,生产环境关闭DEBUG |
| 隐私标识 | %{public}s明文显示,%{private}s显示为<private> |
敏感信息用private,非敏感用public |
二、分级日志规范与性能开销
2.1 五级日志的合理使用场景
很多团队日志写得乱,根本原因是没有明确各级日志的使用场景。以下是基于项目实践总结的使用规范:
2.1.1 DEBUG级别
使用场景:开发阶段临时调试、详细的方法入参、循环内的中间状态。
规范要求:
- 仅在开发环境开启,Release版本必须关闭
- 避免打印循环体内高频日志
- 用完及时清理或降级
// 不推荐:循环内打DEBUG日志
for (let i = 0; i < 1000; i++) {
hilog.debug(DOMAIN, TAG, '当前循环次数:%{public}d', i); // 1000条日志,性能灾难
}
// 推荐:只在关键节点打印
hilog.debug(DOMAIN, TAG, '开始批量处理,总数量:%{public}d', items.length);
let successCount = 0;
for (let item of items) {
// 处理逻辑
successCount++;
}
hilog.debug(DOMAIN, TAG, '批量处理完成,成功数:%{public}d', successCount);
2.1.2 INFO级别
使用场景:关键业务流程节点、状态变更、服务启动/停止、用户可感知的操作。
规范要求:
- 生产环境保留
- 每条日志有明确的业务含义
- 配合关键参数打印
// 推荐的INFO日志
hilog.info(DOMAIN, TAG, '应用冷启动完成,耗时:%{public}dms', costTime);
hilog.info(DOMAIN, TAG, '用户【%{private}s】登出系统', userId);
hilog.info(DOMAIN, TAG, '支付订单【%{private}s】状态变更为:%{public}s', orderId, status);
2.1.3 WARN级别
使用场景:预期内的异常情况、降级处理、重试机制触发、配置加载失败但使用默认值。
规范要求:
- 业务仍可继续,不需要立即人工介入
- 通常伴随重试或降级策略
// 推荐的WARN日志
if (network.isWeak()) {
hilog.warn(DOMAIN, TAG, '网络信号弱,启用图片降级加载模式');
}
try {
config = await loadConfig();
} catch (error) {
hilog.warn(DOMAIN, TAG, '配置加载失败,使用默认配置,错误:%{public}s', error.message);
config = DEFAULT_CONFIG;
}
2.1.4 ERROR级别
使用场景:业务无法正常进行、关键功能不可用、需要立即定位的异常、捕获的Exception。
规范要求:
- 必须包含可定位的上下文信息(请求ID、关键参数)
- 需要触发告警
- 记录堆栈信息
try {
let result = await api.request(orderData);
} catch (error) {
// 必须包含traceId或requestId
hilog.error(DOMAIN, TAG, '下单接口调用失败,订单号:%{private}s,错误:%{public}s,traceId:%{public}s',
orderId, error.message, traceId);
// 触发上报
reportErrorToServer(error);
}
2.1.5 FATAL级别
使用场景:应用即将崩溃、不可恢复的系统级错误、内存溢出、关键资源无法获取。
规范要求:
- 伴随应用重启或退出
- 通常由框架层抛出
- 必须全量上报
2.2 性能开销分析与优化
日志系统如果设计不当,会成为性能瓶颈。以下是HiLog的性能特点及优化建议:
2.2.1 日志打印的性能成本
| 操作 | 耗时(μs) | 说明 |
|---|---|---|
| 普通函数调用 | 0.01~0.05 | 基准参考 |
| DEBUG日志(关闭状态) | 0.1~0.5 | 级别检查开销 |
| INFO日志打印 | 10~50 | 格式化+写入缓冲区 |
| ERROR日志打印 | 20~80 | 包含额外堆栈信息 |
| 字符串拼接 | 随长度变化 | 避免在参数中拼接 |
2.2.2 常见性能陷阱
陷阱1:日志参数预拼接
// 不推荐:无论日志是否打印,都要执行字符串拼接
hilog.debug(DOMAIN, TAG, '用户信息:' + JSON.stringify(userInfo) + ',时间:' + Date.now());
// 推荐:使用格式化参数,只在真正打印时处理
hilog.debug(DOMAIN, TAG, '用户信息:%{public}s,时间:%{public}d',
JSON.stringify(userInfo), Date.now());
陷阱2:高频循环打印
// 不推荐:1000次循环打印1000条日志
items.forEach(item => {
hilog.info(DOMAIN, TAG, '处理项:%{public}s', item.id);
process(item);
});
// 推荐:聚合打印
hilog.info(DOMAIN, TAG, '开始批量处理,数量:%{public}d', items.length);
let start = Date.now();
items.forEach(item => process(item));
hilog.info(DOMAIN, TAG, '批量处理完成,总耗时:%{public}dms', Date.now() - start);
陷阱3:对象深度序列化
// 不推荐:大对象递归序列化
hilog.error(DOMAIN, TAG, '错误状态:%{public}s', JSON.stringify(complexObject));
// 推荐:只打印关键字段
hilog.error(DOMAIN, TAG, '错误码:%{public}d,错误类型:%{public}s',
complexObject.code, complexObject.type);
2.2.3 动态日志级别开关
通过系统配置实现运行时动态调整日志级别,既保证开发期可调试,又保障生产环境性能。
// 日志级别管理器
export class LogManager {
private static instance: LogManager;
private logLevelMap: Map<string, number> = new Map();
private defaultLevel: number = 3; // 默认INFO级别
static getInstance(): LogManager {
if (!LogManager.instance) {
LogManager.instance = new LogManager();
}
return LogManager.instance;
}
// 设置模块日志级别
setModuleLevel(module: string, level: number) {
this.logLevelMap.set(module, level);
}
// 检查是否可打印
isLoggable(module: string, level: number): boolean {
// 调试模式全开
if (this.isDebugMode()) return true;
let moduleLevel = this.logLevelMap.get(module) ?? this.defaultLevel;
return level >= moduleLevel;
}
// 从云端拉取配置
async syncConfigFromCloud() {
try {
let config = await requestLogConfig();
Object.entries(config).forEach(([module, level]) => {
this.setModuleLevel(module, level as number);
});
} catch (error) {
// 使用默认配置
}
}
private isDebugMode(): boolean {
// 判断是否为Debug包
return __DEV__ === true;
}
}
// 封装日志工具
export class Logger {
static debug(module: string, tag: string, msg: string, ...args: any[]) {
if (LogManager.getInstance().isLoggable(module, 0)) {
hilog.debug(0xF811, `[${module}]${tag}`, msg, ...args);
}
}
static info(module: string, tag: string, msg: string, ...args: any[]) {
if (LogManager.getInstance().isLoggable(module, 1)) {
hilog.info(0xF811, `[${module}]${tag}`, msg, ...args);
}
}
static error(module: string, tag: string, msg: string, ...args: any[]) {
if (LogManager.getInstance().isLoggable(module, 4)) {
hilog.error(0xF811, `[${module}]${tag}`, msg, ...args);
}
}
}
// 使用示例
Logger.debug('Login', 'Network', '请求参数:%{public}s', JSON.stringify(params));
三、日志采集与上传策略
3.1 日志采集架构设计
一套完整的日志采集系统需要考虑端侧缓存、网络适配、压缩加密等多个维度。
[应用层] → [日志缓冲区] → [压缩加密] → [网络判断] → [上报服务]
↓
[本地文件] ← [失败重试]
3.2 本地日志收集实现
3.2.1 使用AVLoggerConnection采集日志
HarmonyOS提供了AVLoggerConnection框架用于日志采集。
// 日志收集管理器
import { AVLoggerConnection } from '@ohos.multimedia.avlogger';
export class LogCollector {
private loggerConnection: AVLoggerConnection;
private logBuffer: string[] = [];
private readonly MAX_BUFFER_SIZE = 100;
private readonly MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
constructor(context: Context) {
this.initLoggerConnection(context);
this.initFileSystem(context);
}
private initLoggerConnection(context: Context) {
this.loggerConnection = new AVLoggerConnection(context, {
onLoggerConnected: () => {
console.log('日志服务连接成功');
this.startCollecting();
},
onLoggerError: (error) => {
console.error('日志服务异常:', error);
}
});
}
private startCollecting() {
// 开始采集系统日志
this.loggerConnection.on('log', (logData: string) => {
this.appendToBuffer(logData);
});
}
private appendToBuffer(log: string) {
this.logBuffer.push(log);
// 达到阈值触发写入
if (this.logBuffer.length >= this.MAX_BUFFER_SIZE) {
this.flushToFile();
}
}
private async flushToFile() {
if (this.logBuffer.length === 0) return;
try {
let logs = this.logBuffer.join('\n');
await this.writeToFile(logs);
this.logBuffer = [];
} catch (error) {
console.error('写入日志文件失败:', error);
}
}
private async writeToFile(content: string) {
// 实现文件写入逻辑
// 检查文件大小,超过阈值则滚动
}
// 获取日志文件列表
async getLogFiles(): Promise<string[]> {
// 返回日志文件路径列表
return [];
}
}
3.2.2 日志文件管理策略
export class LogFileManager {
private logDir: string;
private currentFile: string;
private maxFiles: number = 5; // 最多保留5个文件
private maxSize: number = 10 * 1024 * 1024; // 单个文件10MB
constructor(context: Context) {
this.logDir = context.filesDir + '/logs/';
this.initLogDirectory();
}
private async initLogDirectory() {
try {
let file = await fileIo.access(this.logDir);
if (!file) {
await fileIo.mkdir(this.logDir);
}
this.cleanExpiredFiles();
} catch (error) {
console.error('初始化日志目录失败:', error);
}
}
private async cleanExpiredFiles() {
try {
let files = await fileIo.listFile(this.logDir);
let logFiles = files.filter(f => f.endsWith('.log'))
.map(f => ({
name: f,
time: this.extractFileTime(f)
}))
.sort((a, b) => b.time - a.time);
// 删除超过数量限制的旧文件
if (logFiles.length >= this.maxFiles) {
for (let i = this.maxFiles - 1; i < logFiles.length; i++) {
await fileIo.unlink(this.logDir + logFiles[i].name);
}
}
} catch (error) {
console.error('清理日志文件失败:', error);
}
}
// 获取当前可用的日志文件
async getWritableFile(): Promise<string> {
if (!this.currentFile) {
this.currentFile = this.logDir + `app_${Date.now()}.log`;
}
// 检查当前文件大小
let stat = await fileIo.stat(this.currentFile);
if (stat.size >= this.maxSize) {
// 滚动新文件
this.currentFile = this.logDir + `app_${Date.now()}.log`;
}
return this.currentFile;
}
}
3.3 日志上传策略
合理的上传策略需要在实时性和成本之间取得平衡。
3.3.1 网络状态感知上传
import { connection } from '@ohos.net.connection';
export class LogUploadStrategy {
private isWifiConnected: boolean = false;
private isCharging: boolean = false;
private uploadQueue: string[] = [];
private isUploading: boolean = false;
constructor() {
this.monitorNetworkState();
this.monitorPowerState();
}
private monitorNetworkState() {
connection.on('netAvailable', (data) => {
let netHandle = data.netHandle;
// 判断网络类型
if (netHandle.netId === 100) { // WIFI
this.isWifiConnected = true;
this.tryUpload();
} else {
this.isWifiConnected = false;
}
});
}
private monitorPowerState() {
// 监听充电状态
// 实现略
}
// 添加日志到上传队列
addToUploadQueue(logFile: string) {
this.uploadQueue.push(logFile);
this.tryUpload();
}
private tryUpload() {
// 上传条件判断
if (this.isUploading) return;
if (this.uploadQueue.length === 0) return;
// WIFI环境下立即上传
if (this.isWifiConnected) {
this.startUpload();
}
// 移动网络下延迟上传并限制大小
else if (this.isCharging) {
// 充电时可以使用移动网络上传
this.startUploadWithLimit();
}
// 其他情况等待合适时机
}
private async startUpload() {
this.isUploading = true;
while (this.uploadQueue.length > 0) {
let logFile = this.uploadQueue.shift();
try {
await this.uploadLogFile(logFile);
// 上传成功后删除本地文件
await fileIo.unlink(logFile);
} catch (error) {
console.error('上传失败:', error);
// 失败重新入队
this.uploadQueue.unshift(logFile);
break;
}
}
this.isUploading = false;
}
private async uploadLogFile(filePath: string): Promise<boolean> {
// 读取文件
let file = await fileIo.open(filePath);
let stat = await fileIo.stat(filePath);
let buffer = new ArrayBuffer(stat.size);
await fileIo.read(file.fd, buffer);
await fileIo.close(file.fd);
// 压缩
let compressed = await this.compressData(buffer);
// 上传
let response = await request.upload({
url: 'https://log.agc.example.com/upload',
files: [{ filename: 'log.gz', uri: `data:application/octet-stream;base64,${compressed}` }],
data: [{ name: 'appId', value: getAppId() }]
});
return response.code === 200;
}
private async compressData(data: ArrayBuffer): Promise<string> {
// 使用gzip压缩
// 实际实现需引入zlib或系统压缩库
return data as any;
}
}
3.3.2 采样上报策略
对于高频日志,采用采样上报可以有效降低开销。
export class SampleStrategy {
private sampleRate: number = 0.1; // 10%采样率
private errorSampleRate: number = 1.0; // 错误日志100%采样
private slowSampleRate: number = 0.01; // 慢操作1%采样
// 是否上报
shouldUpload(level: number, eventType: string): boolean {
// 错误日志全量
if (level >= 3) { // ERROR及以上
return true;
}
// 慢操作低采样
if (eventType === 'slow_operation') {
return Math.random() < this.slowSampleRate;
}
// 普通日志按采样率
return Math.random() < this.sampleRate;
}
// 动态调整采样率
async adjustSampleRate() {
// 根据当前设备状态调整
let batteryLevel = await this.getBatteryLevel();
let memoryLevel = await this.getMemoryLevel();
if (batteryLevel < 20) {
// 低电量降低采样
this.sampleRate = 0.01;
} else if (memoryLevel > 0.8) {
// 高内存降低采样
this.sampleRate = 0.05;
} else {
// 正常状态
this.sampleRate = 0.1;
}
}
}
3.4 使用LTS Harmony SDK
华为云日志服务(LTS)提供了专门的Harmony SDK,简化了日志上报流程。
3.4.1 集成SDK
# 通过ohpm安装
ohpm install lts_harmony_sdk
// oh-package.json5
{
"dependencies": {
"lts_harmony_sdk": "0.0.1"
}
}
3.4.2 初始化与上报
import { LTSSDK, ConfigParam } from 'lts_harmony_sdk';
import { BusinessError } from '@kit.BasicServicesKit';
export class LTSManager {
private ltsSDK: LTSSDK | null = null;
private context: Context;
constructor(context: Context) {
this.context = context;
}
init() {
const config: ConfigParam = {
// 必填参数
region: 'cn-north-4', // 上报region
projectId: 'your_project_id', // 华为云项目ID
groupId: 'your_log_group_id', // LTS日志组ID
streamId: 'your_log_stream_id', // LTS日志流ID
// 选填参数
cacheThreshold: 100, // 上报条数阈值
timeInterval: 30, // 上报时间阈值(秒)
isReportBackground: true // 开启后台上报
};
try {
let applicationContext = this.context.getApplicationContext();
this.ltsSDK = new LTSSDK(applicationContext, config);
} catch (error) {
console.error('LTS SDK初始化失败:', (error as BusinessError).message);
}
}
// 上报单条日志
reportLog(event: string, data: object) {
if (!this.ltsSDK) return;
let content = {
event: event,
timestamp: Date.now(),
...data
};
let labels = {
appVersion: getAppVersion(),
deviceType: getDeviceType(),
userId: getCurrentUserId()
};
this.ltsSDK.report(content, labels);
}
// 立即上报
reportImmediately(event: string, data: object) {
if (!this.ltsSDK) return;
this.ltsSDK.reportImmediately({
event: event,
timestamp: Date.now(),
...data
});
}
}
四、AppGallery Connect日志分析
4.1 AGC性能管理服务概述
AppGallery Connect(AGC)提供了开箱即用的性能管理服务(APMS),无需集成SDK即可自动采集应用的崩溃、ANR、页面性能等数据。
核心能力:
- 异常管理:自动采集崩溃、应用无响应等
- 性能管理:启动耗时、页面切换耗时、网络请求分析
- 单点查询:查看特定用户的性能数据
- 日志回捞:按需拉取指定设备的日志
4.2 异常管理:零代码接入
从HarmonyOS 5.0.0 Release开始,应用无需主动集成SDK即可使用异常管理功能。
// 无需额外代码,系统自动采集
// 崩溃发生后,数据会自动上报到AGC
验证上报是否成功:
// 使用hilog过滤崩溃上报日志
// 搜索关键字:C04707.*report\s(error|success)
hilog.info(0, 'APMS', 'report success'); // 看到此日志表示上报成功
4.3 单点查询:定位特定用户问题
当遇到特定用户反馈问题时,可以通过单点查询功能分析该用户的性能数据。
4.3.1 设置用户标识
import { apms } from '@kit. PerformanceAnalysisKit';
// 在用户登录后设置
export function setUserIdentifier(userId: string) {
try {
apms.setUserIdentifier(userId);
hilog.info(0xF811, 'APMS', '设置用户标识:%{private}s', userId);
} catch (error) {
hilog.error(0xF811, 'APMS', '设置用户标识失败:%{public}s', error.message);
}
}
// 用户登出时清除
export function clearUserIdentifier() {
apms.setUserIdentifier('');
}
4.3.2 在AGC控制台查询
登录AppGallery Connect,进入“性能管理” > “单点查询”,输入用户标识即可查看:
- ANR分析:应用无响应事件的堆栈和上下文
- 页面分析:各页面的加载耗时、呈现耗时
- 慢启动追踪:冷启动/热启动的耗时分布
- 网络分析:接口请求的耗时、成功率
4.4 日志回捞:按需获取详细日志
日志回捞功能允许开发者在需要时,主动拉取指定用户终端上的日志,解决偶现问题定位难的问题。
4.4.1 启用自定义日志
import { apms } from '@kit. PerformanceAnalysisKit';
export class CustomLogger {
// 记录自定义日志,供回捞使用
static log(tag: string, message: string) {
// 使用hilog正常打印
hilog.info(0xF811, tag, message);
// 同时记录到APMS自定义日志缓冲区
try {
apms.recordLog(tag, message);
} catch (error) {
// APMS日志缓冲区可能满,忽略错误
}
}
// 记录关键事件
static recordEvent(eventName: string, properties: object) {
try {
apms.recordEvent(eventName, JSON.stringify(properties));
} catch (error) {
// 忽略
}
}
}
4.4.2 创建回捞任务
在AGC控制台操作步骤:
- 进入“性能管理” > “日志回捞”
- 点击“创建任务”
- 填写任务信息:
- 任务名称:如“定位登录失败问题-用户张三”
- 拉取时间范围:问题发生前后30分钟
- 用户标识:输入
setUserIdentifier设置的值
- 任务完成后,可在详情页查看或下载日志
4.4.3 回捞日志示例
2026-03-09 10:23:15.123 [INFO] [LoginModule] 用户点击登录按钮
2026-03-09 10:23:15.245 [DEBUG] [LoginModule] 请求参数:{"userId":"user_123","timestamp":1741501395245}
2026-03-09 10:23:16.012 [ERROR] [NetworkModule] 登录接口超时,错误码:-1001
2026-03-09 10:23:16.013 [WARN] [LoginModule] 触发重试机制,重试次数:1
2026-03-09 10:23:17.856 [INFO] [LoginModule] 重试成功,登录完成
4.5 崩溃分析实战
4.5.1 崩溃数据采集
系统自动采集的崩溃数据包含以下信息:
{
"crash_type": "Java Crash",
"thread_name": "main",
"exception_type": "NullPointerException",
"message": "Attempt to invoke virtual method on a null object reference",
"stacktrace": [
"at com.example.app.LoginActivity.onClick(LoginActivity.java:125)",
"at android.view.View.performClick(View.java:7448)"
],
"device_info": {
"model": "HUAWEI P50",
"os_version": "HarmonyOS 5.0.0",
"memory": 6144
},
"app_info": {
"version": "1.2.3",
"build_id": "20260201"
}
}
4.5.2 使用hilog辅助分析
当AGC未显示崩溃数据时,可以通过hilog排查:
// 过滤崩溃上报日志
// 命令:hilog | grep C04707
// 成功上报
// 12-11 12:21:47.579 2695 2695 I C04707: report success, reqId: 123456
// 上报失败(网络问题)
// 12-11 12:21:47.579 2695 2695 E C04707: report error, statusCode: 12, errMsg: Rcp error:1007900006
五、实战案例:构建完整的日志系统
5.1 系统架构
结合以上知识点,构建一个完整的日志系统架构:
┌─────────────────────────────────────────────────────────────┐
│ 应用端 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ HiLog API │ │ 自定义Logger│ │ 性能监控 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼─────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 日志处理管道 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌──────────────┐ │ │
│ │ │级别过滤 │ │采样过滤 │ │隐私脱敏 │ │ │
│ │ └────┬────┘ └────┬────┘ └──────┬───────┘ │ │
│ └───────┼────────────┼──────────────┼─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │本地文件存储 │ │实时控制台 │ │LTS/AGC上报 │ │
│ └─────────────┘ └─────────────┘ └──────┬──────┘ │
└──────────────────────────────────────────┼──────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 云端 │
│ ┌─────────────────────────────────────────────┐ │
│ │ AppGallery Connect │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 异常管理 │ │ 性能管理 │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 日志回捞 │ │ 单点查询 │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 华为云LTS(可选) │ │
│ │ 实时日志分析/告警/可视化 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
5.2 完整实现代码
5.2.1 日志配置管理
// log-config.ts
export interface LogConfig {
domain: number;
globalTag: string;
logDir: string;
maxFileSize: number; // 字节
maxFileCount: number;
uploadInterval: number; // 秒
uploadThreshold: number; // 条数
sampleRate: number; // 0-1
}
export class LogConfigManager {
private static instance: LogConfigManager;
private config: LogConfig;
private constructor() {
// 默认配置
this.config = {
domain: 0xF811,
globalTag: 'MyApp',
logDir: '/logs/',
maxFileSize: 10 * 1024 * 1024,
maxFileCount: 5,
uploadInterval: 30,
uploadThreshold: 100,
sampleRate: 0.1
};
}
static getInstance(): LogConfigManager {
if (!LogConfigManager.instance) {
LogConfigManager.instance = new LogConfigManager();
}
return LogConfigManager.instance;
}
async loadConfig() {
try {
// 尝试从本地加载配置
let localConfig = await this.loadLocalConfig();
if (localConfig) {
this.config = { ...this.config, ...localConfig };
}
// 从云端加载远程配置(可选)
let remoteConfig = await this.fetchRemoteConfig();
if (remoteConfig) {
this.config = { ...this.config, ...remoteConfig };
}
} catch (error) {
console.error('加载日志配置失败,使用默认配置', error);
}
}
getConfig(): LogConfig {
return { ...this.config };
}
private async loadLocalConfig(): Promise<Partial<LogConfig> | null> {
// 从首选项或文件读取
return null;
}
private async fetchRemoteConfig(): Promise<Partial<LogConfig> | null> {
// 从AGC远程配置拉取
return null;
}
}
5.2.2 统一的日志管理器
// log-manager.ts
import { hilog } from '@kit.PerformanceAnalysisKit';
import { LogConfig, LogConfigManager } from './log-config';
import { LogFileManager } from './log-file-manager';
import { LogUploadManager } from './log-upload';
import { BusinessError } from '@kit.BasicServicesKit';
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
FATAL = 4
}
export class LogManager {
private static instance: LogManager;
private config: LogConfig;
private fileManager: LogFileManager;
private uploadManager: LogUploadManager;
private moduleLevels: Map<string, LogLevel> = new Map();
private context: Context;
private constructor(context: Context) {
this.context = context;
this.config = LogConfigManager.getInstance().getConfig();
this.fileManager = new LogFileManager(context);
this.uploadManager = new LogUploadManager(context);
this.init();
}
static getInstance(context?: Context): LogManager {
if (!LogManager.instance && context) {
LogManager.instance = new LogManager(context);
}
return LogManager.instance;
}
private async init() {
await LogConfigManager.getInstance().loadConfig();
this.config = LogConfigManager.getInstance().getConfig();
// 初始化上传定时器
setInterval(() => {
this.uploadManager.tryUpload();
}, this.config.uploadInterval * 1000);
}
// 设置模块日志级别
setModuleLevel(module: string, level: LogLevel) {
this.moduleLevels.set(module, level);
}
// 检查是否可打印
isLoggable(module: string, level: LogLevel): boolean {
// Debug包全开
if (this.isDebugMode()) return true;
let moduleLevel = this.moduleLevels.get(module) ?? LogLevel.INFO;
return level >= moduleLevel;
}
private isDebugMode(): boolean {
// 判断是否为Debug包
return __DEV__ === true;
}
// 核心日志方法
log(level: LogLevel, module: string, tag: string, message: string, ...args: any[]) {
if (!this.isLoggable(module, level)) return;
// 处理隐私标识
let formattedMsg = this.formatMessage(message, args);
// 构建完整日志
let logEntry = {
timestamp: Date.now(),
level: LogLevel[level],
module: module,
tag: tag,
pid: ProcessManager.getPid(),
tid: ThreadManager.getTid(),
message: formattedMsg,
traceId: this.getTraceId()
};
// 输出到hilog
this.outputToHilog(level, module, tag, message, args);
// 写入文件
this.fileManager.appendLog(JSON.stringify(logEntry) + '\n');
// 错误日志立即触发上传条件
if (level >= LogLevel.ERROR) {
this.uploadManager.markHasError();
}
}
private outputToHilog(level: LogLevel, module: string, tag: string, msg: string, args: any[]) {
let fullTag = `[${module}]${tag}`;
switch (level) {
case LogLevel.DEBUG:
hilog.debug(this.config.domain, fullTag, msg, ...args);
break;
case LogLevel.INFO:
hilog.info(this.config.domain, fullTag, msg, ...args);
break;
case LogLevel.WARN:
hilog.warn(this.config.domain, fullTag, msg, ...args);
break;
case LogLevel.ERROR:
hilog.error(this.config.domain, fullTag, msg, ...args);
break;
case LogLevel.FATAL:
hilog.fatal(this.config.domain, fullTag, msg, ...args);
break;
}
}
private formatMessage(msg: string, args: any[]): string {
// 简单实现,实际应替换占位符
return msg;
}
private getTraceId(): string {
// 从上下文获取或生成traceId
return TraceContext.getCurrentId() || 'no-trace';
}
// 快捷方法
debug(module: string, tag: string, msg: string, ...args: any[]) {
this.log(LogLevel.DEBUG, module, tag, msg, ...args);
}
info(module: string, tag: string, msg: string, ...args: any[]) {
this.log(LogLevel.INFO, module, tag, msg, ...args);
}
warn(module: string, tag: string, msg: string, ...args: any[]) {
this.log(LogLevel.WARN, module, tag, msg, ...args);
}
error(module: string, tag: string, msg: string, ...args: any[]) {
this.log(LogLevel.ERROR, module, tag, msg, ...args);
}
fatal(module: string, tag: string, msg: string, ...args: any[]) {
this.log(LogLevel.FATAL, module, tag, msg, ...args);
}
}
// 导出便捷函数
export const log = LogManager.getInstance();
// 使用示例
// log.debug('Login', 'UI', '按钮点击 %{public}s', '登录');
5.2.3 应用入口初始化
// EntryAbility.ets
import { LogManager } from './utils/log-manager';
import { apms } from '@kit.PerformanceAnalysisKit';
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
// 初始化日志系统
const logManager = LogManager.getInstance(this.context);
// 设置模块日志级别
if (launchParam.launchReason === AbilityConstant.LaunchReason.NORMAL) {
logManager.setModuleLevel('Network', LogLevel.INFO);
logManager.setModuleLevel('Database', LogLevel.WARN);
}
// 设置APMS用户标识(如果已登录)
let userId = this.getStoredUserId();
if (userId) {
apms.setUserIdentifier(userId);
}
logManager.info('System', 'Startup', '应用启动,原因:%{public}s', launchParam.launchReason);
}
onDestroy() {
LogManager.getInstance().info('System', 'Lifecycle', '应用销毁');
}
}
5.3 最佳实践总结
5.3.1 Domain和Tag命名规范
根据社区实践,推荐以下命名规范:
// domain按业务线分配
export enum DomainConstant {
SYSTEM = 0xF001, // 系统基础
LOGIN = 0xF101, // 登录模块
PAY = 0xF201, // 支付模块
MESSAGE = 0xF301, // 消息模块
PROFILE = 0xF401, // 个人中心
}
// tag采用"模块_子功能"格式
// Login_UI, Login_Network, Login_Storage
// Pay_Order, Pay_Result, Pay_WebView
5.3.2 敏感信息处理规则
| 数据类型 | 处理方式 | 示例 |
|---|---|---|
| 用户ID | 脱敏显示前3后4 | user_***_1234 |
| 手机号 | 完全脱敏 | <phone> |
| Token | 完全脱敏 | <token> |
| 订单号 | 显示后6位 | ******123456 |
| IP地址 | 显示前3段 | 192.168.1.*** |
| 请求参数 | 结构化脱敏 | {"password":"<private>"} |
5.3.3 性能优化清单
- ✅ DEBUG日志只在开发环境开启
- ✅ 循环内避免打印日志
- ✅ 使用参数占位符代替字符串拼接
- ✅ 大对象只打印关键字段
- ✅ 设置合理的缓冲区大小
- ✅ 异步写入文件
- ✅ 采样上报高频日志
- ✅ 压缩后再上传
更多推荐


所有评论(0)