HiLog 是鸿蒙日志体系里最不起眼的组件——写日志谁不会?但健康菜谱助手的一次线上排查让我意识到,乱写的日志比没有日志更糟:两万条 INFO 日志淹没了唯一的 ERROR,关键线索被刷掉,排查多花了四小时。这篇记录 HiLog 的分级体系、域与标签的设计、隐私合规的 %{public} 与 %{private}、以及日志规范如何在项目里落地。一、分级体系:五个级别各自的真实用途HiLog 五级:DEBUG、INFO、WARN、ERROR、FATAL。文档教的是定义,工程里要定的是什么内容用什么级。我的分级标准:DEBUG 是开发期排查细节(变量值、分支走向),发布构建默认关闭;INFO 是关键路径的里程碑(页面创建、请求发起、迁移开始),数量可控;WARN 是异常但已兜底(缓存失效走了降级、重试成功);ERROR 是操作失败且影响功能(请求失败且无缓存、写入失败);FATAL 是不可恢复即将崩溃。最容易被滥用的是 INFO——把调试细节打进 INFO,发布包里日志被淹没的根源就在这。级别内容标准发布构建DEBUG排查细节,变量快照关闭INFO里程碑事件,数量可控保留WARN已兜底的异常保留ERROR功能失败保留上报FATAL不可恢复保留崩溃关联二、domain 与 tag 的设计:日志的定位系统HiLog 的 domain(域,十六进制数)加 tag(标签)构成日志的定位系统。域按模块划分——0x0001 存储、0x0002 网络、0x0003 UI、0x0004 迁移——跨模块排查时按域过滤就能拉出单模块的完整日志。标签按类文件命名(RecipeStore、SyncClient),同域内按 tag 区分来源。踩过的坑:早期 tag 随手写,同一个类的日志出现三个 tag 变体,过滤时漏一半。规范落地:tag 常量集中定义在日志工具文件里,类初始化时取常量,手写字符串直接禁止。// 日志工具:统一 domain 与 tag 常量 const DOMAIN_STORAGE 0x0001 const TAG RecipeStore hilog.info(DOMAIN_STORAGE, TAG, load favorites: %{public}d items, count)三、隐私合规:%{public} 与 %{private} 的强制区分HiLog 的格式化参数必须显式声明 public 或 private——private 的值在发布环境会被脱敏成 ,public 才明文输出。这个机制是隐私合规的硬约束,但默认行为有个坑:不声明格式的参数(如直接拼字符串进日志)不会自动脱敏,反而绕过了保护。项目规范:日志一律用格式化参数,禁止字符串拼接;用户相关数据(菜谱名、备注)一律 %{private},技术数据(计数、耗时、状态码)用 %{public}。这条规范在 AppGallery 审核的隐私检查里顺带通过,没有返工。// 规范写法:公开技术数据,隐私数据脱敏 hilog.info(DOMAIN_STORAGE, TAG, load favs: %{public}d items, user %{private}s, count, userId) // 禁止:字符串拼接绕过脱敏 hilog.info(DOMAIN_STORAGE, TAG, user userId loaded count)四、日志工具封装:五分钟省下重复劳动裸写 hilog 五个参数太啰嗦,封装一个日志工具统一 domain、tag、级别判断与 debug 开关:class Log { static d(tag: string, msg: string, ...args: Object[]) { if (!DEBUG_BUILD) return hilog.debug(DOMAIN, tag, msg, ...args) } static i(tag: string, msg: string, ...args: Object[]) { hilog.info(DOMAIN, tag, msg, ...args) } static w(tag: string, msg: string, ...args: Object[]) { hilog.warn(DOMAIN, tag, msg, ...args) } static e(tag: string, msg: string, ...args: Object[]) { hilog.error(DOMAIN, tag, msg, ...args) } }封装的隐藏收益是 DEBUG 级别的全局开关:DEBUG_BUILD 由编译宏注入,发布构建里 debug 调用直接被摇树优化掉——日志的零成本切换靠封装实现,散落的裸调用做不到。五、日志的量控制:每屏一条的自律日志量的控制标准:滚动一屏的列表,产生的日志不超过一条。逐项打点的冲动要被聚合打点替代——列表滚动位置不逐帧打,只打停稳后的最终位置;批量操作不逐条打,打一条聚合结果(处理了 20 条,成功 18 失败 2)。那次 ERROR 被淹没的事故之后,全应用做了日志瘦身:删掉循环内打点 14 处、降级 INFO 到 DEBUG 37 处、合并相邻重复 9 处,发布包的日志量降到原来的四分之一,关键日志的可见性回来了。六、与 HiAppEvent 的配合:日志与事件的两端关联日志与打点的关联靠追踪 ID:每个关键链路(迁移、同步、支付)发起时生成一个 traceId,链路上的每条 HiLog 与每个 HiAppEvent 事件都携带它。排查时从事件入手拿到 traceId,再按 traceId 过滤日志还原全过程——双向可查。配合规范:fault 级事件必须携带 traceId;traceId 的生成用时间戳加随机四位的组合,保证单设备唯一且肉眼可读。const traceId Date.now().toString(36) Math.floor(Math.random() * 10000).toString(36) Log.e(TAG, migrate fail, trace %{public}s, traceId) hiAppEvent.write(migration_fail, hiAppEvent.EventType.FAULT, { traceId, step })七、真实排查:四小时与两分钟的差距两个同类问题在不同日志质量下的排查对比。问题一(日志质量差时期):收藏丢失,两万条 INFO 里翻 ERROR,翻到时发现关键报错在四小时前的日志段里被冲掉了,最终靠用户复现才定位。问题二(规范落地后):同步失败,WARN 日志一条锁定重试耗尽,ERROR 一条定位到失败原因,加上打点事件圈定影响范围,两分钟闭环。同一个应用,日志体系改造前后的排查效率差了百倍。日志是写给三周后的自己的——三周后的你不记得今天的代码为什么这么写,只有日志能告诉你当时发生了什么。八、发布与调试的分离:两份配置日志的发布与调试配置分离:调试构建全级别开启加 debug 面板显示;发布构建只保留 INFO 以上、DEBUG 调用摇树剔除、日志文件落盘走 HiLog 的持久化配置。两份配置的切换靠 DEBUG_BUILD 编译宏,切换点收敛在日志工具类内部,业务代码无感。发布包的日志量最终控制在日均 200 条以内——这是给用户设备留的合理 footprint,也是给自己留的排查余粮。九、日志落盘与用户反馈:让日志能被带出来HiLog 默认输出到 hilogd 守护进程的缓冲区,设备重启就没了,用户反馈里也带不出来。健康菜谱助手的做法:关键链路的 WARN 以上日志,同步落一份到应用沙箱的日志文件——按天分文件,单文件超过两兆滚动覆盖,只保留最近三天。落盘的格式化把时间、级别、tag、traceId 拼成一行,导出时用户直接把文件发过来就能看。用户反馈页做了附带日志开关,默认勾选并在勾选框旁写明仅包含技术日志,不含个人内容——private 脱敏在落盘前就已生效,这句话才立得住。真机导出用 hdc file recv 拉沙箱文件,排查期一天能拉十几次。// 按天落盘:WARN 以上同步写沙箱,三天滚动 function logToFile(level: string, tag: string, msg: string) { const day new Date().toISOString().slice(0, 10) const path filesDir /logs/app-${day}.log const line ${Date.now()} ${level} ${tag} ${msg}\n fileIo.writeSync(path, line, { append: true }) if (fileIo.statSync(path).size 2 * 1024 * 1024) rotateLog(path) }落盘的坑有一个:写文件本身失败不能递归打日志,会死循环。工具函数里对落盘异常静默吞掉,最多在内存计数器上加一,调试面板展示落盘失败 N 次。别小看这个细节,我见过把 fileIo 异常 catch 之后又调 Log.e 的写法,写盘失败触发日志、日志写盘再失败,几秒内把 CPU 拉满。十、TaskPool 与 Worker 里的日志:跨线程不断线日志体系在多线程下有个隐蔽问题:TaskPool 和 Worker 里跑的代码,拿不到主线程封装好的日志工具单例——每个 Worker 有自己的执行环境,静态类不共享。最初的方案是每个 Worker 文件里复制一份 Log 类,结果 domain 常量抄错了两个,后台解析日志时模块归属全乱。后来收敛成日志模块单独一个 ets 文件,主线程和 Worker 各自 import 同一份,domain 与 tag 常量只有一处定义。Worker 内的日志前缀再加一层 [W] 标记,和主线程日志区分线程归属。线程场景处理方式注意点主线程 UI常规 Log 工具回调里的日志带页面 tagTaskPool 任务同工具 import入参出参打点走 %{private}Worker 常驻同工具[W] 前缀循环任务禁止逐条 INFONative 侧HiLog C 接口与 ArkTS 共用 domain 编号Native 侧容易漏:C 代码用 hilog_native 接口时,domain 编号如果和 ArkTS 侧对不上,按域过滤时原生日志会整段缺失。项目里把 domain 常量生成到 napi 桥接头文件里,两边编译期引用同一个值,从源头杜绝错位。跨线程排查另有一个技巧:Worker 里的耗时任务在开始和结束各打一条 INFO,配对消耗时间戳,线程卡顿一眼可见。十一、崩溃现场:让 FATAL 自动留下最后一段日志FATAL 级别的价值在崩溃瞬间:crash 回调触发时,内存里还留着最近几十条日志,机会窗口只有几秒。项目在 ErrorManager 的 crash 回调里做了三件事:先把内存环形缓冲区(最近一百条日志,含 DEBUG 级)整体 dump 到 crash 目录;再把崩溃堆栈、异常类型、当时的页面路由追加进去;最后才走默认的崩溃上报流程。恢复启动后检测到 crash 文件存在,提示用户上次异常退出,是否提交日志——用户点确认,文件随反馈上传,拒绝也不影响使用。// 崩溃瞬间:dump 环形缓冲区到文件 const ringBuf: string[] [] function pushRing(line: string) { ringBuf.push(line); if (ringBuf.length 100) ringBuf.shift() } // crash 回调里: onCrash(err) { fileIo.writeSync(crashDir /crash- Date.now() .log, ringBuf.join(\n) \nSTACK\n err.stack) }这套机制上线后收到三次真实回报:一次数据库迁移崩溃、一次大文件解析内存溢出、一次 Worker 反序列化崩溃,三份 crash 日志都完整还原了崩溃前最后 100 条日志,定位时间都在十分钟以内。没有这套机制的时期,崩溃排查只能靠复现——有的问题复现率百分之一,靠运气等于排不了。十二、总结:日志规范的四条军规收束成四条军规:级别按内容标准选,DEBUG 禁止进发布包;domain 按模块、tag 走常量,定位系统统一;格式化参数显式 public/private,隐私脱敏不过夜;每屏一条的量控制,聚合替代逐条。四条军规配合打点体系(事件管结果、日志管过程、traceId 串联),构成应用完整的可观测性底座。日志的价值不在写的那一刻,在线上出问题的那一刻——那一分钟的差距,就是提前写好规范的全部意义。参考资料:华为开发者:HiLog 日志输出华为开发者:日志隐私脱敏 %public/%private华为开发者:HiLog 与 HiAppEvent 配合#鸿蒙 #HarmonyOS 7(API 26)
企业数字化 ERP 产品动态
相关推荐
大模型算法八股 一、深度学习与Pytorch(一)神经网络与训练过程输出:线性组合-加入偏置-激活函数对于第一次正向传播,权重的值将随机初始化。sigmoid:输出(0,1)之间,输入值过大或过小梯度消失&#x… · 2026/11/4 19:50:06
SSE 流式输出:数据块边界、语义边界与状态解析 一、SSE 的基本概念与运行机制SSE(Server-Sent Events,服务器发送事件)是一种基于 HTTP 的服务器向客户端持续推送事件的通信机制。它使用 text/event-stream 作为响应的媒体类型,允许服务端在一个持续打开的 HTTP 响应中依次发送… · 2026/11/4 19:48:04
INCA汽车标定入门:安装、A2L/S19导入与CAN测量实操 简介:《INCA标定软件使用教程》面向汽车标定初学者及需要熟悉INCA操作的汽车电子工程师,围绕软件安装、许可证配置、实验环境建立、标定工程设计与数据录制等环节展开,帮助读者理解A2L与s19文件导入、CAN设备连接、变量选取和示波器观察等基础… · 2026/11/4 21:09:12
短剧广告变现系统架构与优化实践 1. 短剧广告变现的行业现状与痛点 短剧内容近年来呈现爆发式增长,根据第三方监测数据显示,2023年短剧市场规模同比增长超过300%。在这个快速发展的赛道中,广告变现成为大多数制作方的核心收入来源。但实际操作中,内容团队往往会遇… · 2026/11/4 21:09:12
城市运管服平台架构设计与智能治理实践 1. 城市运管服平台的核心价值解析城市运管服平台作为现代城市治理的"数字中枢",其核心价值在于打通了传统城市管理中各业务系统的数据孤岛。我在参与某省会城市平台建设时深有体会——过去城管、交通、环保等部门的数据就像散落的拼图,而运管服… · 2026/11/4 21:09:12
x64dbg动态调试检测:从PEB到打分框架的工程化方案 1. 先想清楚:你到底要检测什么x64dbg 动态调试检测方案这个题目,很多人在搜索的时候脑子里想的其实是“怎么判断我的程序正在被 x64dbg 附加”,但真正落地之前,最该先回答的是另一个问题:你检测出来之后要干什么。这个… · 2026/11/4 21:09:12
SafeVLA框架:具身智能安全决策的突破性进展 1. 项目背景与核心突破在具身智能(Embodied AI)领域,让智能体在物理世界中安全执行视觉语言指令一直是个棘手难题。北大杨耀东团队最新提出的SafeVLA框架,在NeurIPS 2025上获得Spotlight论文收录,其核心创新在于通过多… · 2026/11/4 21:08:10
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38