HarmonyOS 应用实战 99:发布前日志别靠人工删,用 ReleaseLogAudit 检查白名单
本地工具类应用也会处理用户内容:提问、答案、收藏、导入文本、题库名称。调试阶段为了排查方便,很容易顺手把对象、错误、参数整段打进hilog。等到发布前再靠人工搜索删除,风险很高,因为日志分散在 entry、HSP 页面、服务和 HAR 仓储里。
“答案之书”当前工程的日志多数已经比较克制,比如首页只记录deckId和问题长度,抽取服务只记录题库 id、索引和数量。但发布前仍需要一张明确白名单:哪些字段能打,哪些字段不能打,扫描命令怎么跑,发现异常后改哪里。
当前日志分布不是一个文件能看完
先用命令看全项目日志:
rg-n"hilog\\.(info|warn|error|debug)"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\entry\src\main\ets"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHSP\src\main\ets"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHAR\src\main\ets"当前能看到的典型位置包括:
| 层级 | 文件 | 日志类型 |
|---|---|---|
| entry | EntryAbility.ets | 生命周期、启动失败 |
| entry | Index.ets | 提交入口、问题长度 |
| entry | SeedLoader.ets | 默认题库播种、恢复原因 |
| HSP 服务 | AnswerPickService.ets | 抽取题库、索引、总数 |
| HSP 服务 | DeckService.ets | 保存、重命名、删除题库 id |
| HAR 仓储 | PreferencesStore.ets | 偏好读写失败 |
这说明发布前不能只检查一个服务文件。真正要做的是把日志调用当成一类交付项统一审计。
当前做得对的地方:记录长度和计数
首页提交问题时,没有打印问题正文:
consttrimmed:string=this.question.trim();constparams:DrawingParams={deckId:this.currentDeckId,question:trimmed||undefined};hilog.info(DOMAIN,TAG,'submit deck=%{public}s qLen=%{public}d',params.deckId,trimmed.length);抽取服务也没有打印答案正文:
constidx:number=pickIndexExcluding(deck.answers.length,excludeIndex);constans:Answer=deck.answers[idx];hilog.info(DOMAIN,TAG,'pick deck=%{public}s idx=%{public}d/%{public}d',deckId,idx,deck.answers.length);returnans;这两处是很好的边界:可以记录deckId、长度、索引、数量;不能记录question原文和answer.text。文章要把这种边界写成规则,而不是依赖开发者每次都自觉。
风险点:错误对象和 JSON.stringify
日志里仍有需要关注的模式,比如:
hilog.error(DOMAIN,'testTag','Failed to set colorMode. Cause: %{public}s',JSON.stringify(err));JSON.stringify(err)不一定包含用户内容,但它是一个风险形态:一旦错误对象里携带业务参数,就会把字段整体打出去。发布前审计时,应该把JSON.stringify、raw、text、question、answers、import这类关键词单独扫出来。
白名单先定义清楚
建议把日志字段分成三类:
| 类别 | 可以记录 | 不要记录 |
|---|---|---|
| 标识 | deckId、favoriteId、错误分类 | 完整题库对象 |
| 计数 | qLen、answerCount、idx/total | 答案正文 |
| 状态 | seedVersion、schemaVersion、路径阶段 | 导入原文 |
| 错误 | 受控错误码、简短原因 | 整个 payload 或 JSON 文本 |
这张白名单不只是安全要求,也能提升排障效率。日志里出现“题库保存失败,count=1”比出现一整段答案文本更容易判断问题,且不会泄露用户内容。
用 SafeLog 封住业务日志
可以先从业务层开始封装,不必一次替换所有系统生命周期日志:
typeSafeLogValue=string|number|boolean;classSafeLog{staticinfo(tag:string,event:string,fields:Record<string,SafeLogValue>):void{constmessage:string=Object.keys(fields).sort().map((key:string):string=>`${key}=${fields[key]}`).join(' ');hilog.info(DOMAIN,tag,'%{public}s %{public}s',event,message);}}业务代码调用时只传白名单字段:
SafeLog.info(TAG,'answer.pick',{deckId,index:idx,total:deck.answers.length});不要提供any或对象直传接口。日志封装越方便传大对象,越容易在调试时把正文塞进去。
发布前脚本扫敏感词
不需要等到审查时才人工翻代码,可以先准备一组rg命令:
rg-n"hilog\\.(info|warn|error|debug)"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\entry\src\main\ets"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHSP\src\main\ets"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHAR\src\main\ets"rg-n"hilog\\..*(text|question|raw|answers|JSON\\.stringify|payload|import)"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\entry\src\main\ets"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHSP\src\main\ets"`"D:\ProgramData\huawei\lesson\The_Book_of_Answers\libraryHAR\src\main\ets"第一条命令列出所有日志点,第二条命令只抓高风险字段。命中不一定就是错误,但必须人工确认。
把当前日志归类
当前工程可以先按下面方式判断:
| 日志 | 判断 | 说明 |
|---|---|---|
submit deck + qLen | 保留 | 只记录题库 id 和问题长度 |
pick deck + idx/total | 保留 | 没有答案正文 |
save deck id + count | 保留 | 没有题库名和答案文本 |
add favorite total | 保留 | 只记录数量 |
JSON.stringify(err) | 复核 | 错误对象来源要确认 |
onRestore bundleVersion | 复核 | 可以记录版本,但不要扩展成备份体 |
这个归类可以放进发布前清单。每次新增日志,先问它属于哪一类,再决定能否合入。
调试日志要有构建开关
有些问题确实需要在本地看正文,比如导入解析失败时看原始片段。但这类日志只能放在调试构建下:
constRELEASE_LOG:boolean=true;functiondebugOnlyUserText(tag:string,text:string):void{if(RELEASE_LOG){return;}hilog.debug(DOMAIN,tag,'debug text=%{public}s',text);}真实工程里RELEASE_LOG应该从构建模式或编译常量读取,而不是手写常量。这里的重点是原则:正式包日志路径不能接收用户正文。
验证路径
发布前至少跑三类验证:
| 验证 | 命令或操作 | 期望 |
|---|---|---|
| 静态扫描 | rg "hilog\\." | 日志点全列出 |
| 敏感字段扫描 | `rg "text | question |
| 交互抽样 | 提问、导入、收藏、恢复后抓日志 | 不出现问题正文、答案正文、导入全文 |
如果这篇只是文章改写,不要写成“发布包已完成审计”。真正的审计要在工程代码和设备日志上完成。
常见问题
日志排查要避免两个极端:一个是把用户正文全打出来,另一个是删到只剩“失败”。可维护的做法是在日志里保留阶段、owner、id、计数和错误分类,同时把问题正文、答案正文、导入原文挡在发布包之外。
| 现象 | 原因 | 修复 |
|---|---|---|
| 正式日志出现用户问题 | 直接打印question | 改成qLen |
| 日志出现完整答案 | 打印Answer对象 | 只记answerId或索引 |
| 导入失败日志很长 | 打印 raw import | 只记长度和错误分类 |
| 错误日志无法判断 | 删除过度,只剩“失败” | 保留 owner、阶段、计数 |
收口
第 99 篇的核心是把日志当成交付边界,而不是发布前手工删几行。当前工程已经有一些正确做法:记录长度、计数、id,不记录正文。后续要补的是 SafeLog 封装、敏感字段扫描和发布前抽样,让日志既能排障,又不带出用户内容。
最小闭环可以先从两件事开始:所有新增业务日志必须写清楚白名单字段,发布前固定跑一次rg敏感字段扫描。等这两项稳定后,再把常见事件收进SafeLog。这样日志不是被动清理,而是在编码阶段就被限制在可发布的字段范围内。