云计算百科
云计算领域专业知识百科平台

HarmonyOS应用实战-启示散页-58-诊断导出别变成日志泄露:用脱敏报告替代原始 hilog

HarmonyOS 应用实战 58:诊断导出别变成日志泄露,用脱敏报告替代原始 hilog

用户反馈“答案抽错了”“导入后题库少了两条”时,开发最容易说一句:把日志导出来看看。这个动作在团队内部很顺,但放到面向用户的应用里就不一样了。hilog 里可能出现题库名称、问题正文、答案片段、文件路径、异常堆栈和调试开关,一旦原样打包给用户复制,诊断入口就会从排障工具变成隐私出口。

更稳的做法不是禁止诊断,而是把诊断导出做成一份业务白名单报告:只告诉开发者故障发生在哪个阶段、触发了哪个事件码、候选数量是多少、关联的是哪个安全标识;不导出正文、不导出原始日志、不导出 Preferences 全量内容。

在这里插入图片描述

本文解决四个问题:

  • 区分开发日志、运行账本和用户可发送报告。
  • 设计只包含安全字段的诊断事件模型。
  • 把脱敏和报告组装收在导出边界,而不是散在页面里。
  • 用敏感样本反向验证报告里没有用户正文。
  • 在这里插入图片描述
    在这里插入图片描述

    先把故障链拆开:诊断不等于日志打包

    在“答案之书”这类本地应用里,用户数据通常都在设备侧:题库、历史、收藏、备份文件都可能含有私人内容。故障排查需要线索,但线索不等于原文。真正要定位的问题通常只有这几个:是不是当前题库为空、抽取候选是否被过滤没了、导入是否被 schema 拦住、写入 Preferences 是否失败。

    危险链路:
    页面打印 questionText
    -> 开发为了省事导出 hilog
    -> 用户反馈附件含私人问题和答案
    -> 故障排清楚了,但隐私边界已经失守

    安全链路:
    业务层记录 safeEvent
    -> 导出服务按白名单组装报告
    -> 用户预览后复制
    -> 开发只拿 stage、code、count、safeId 排查

    这一步先确定责任边界。hilog 适合开发阶段定位现场,运行账本适合沉淀可解释事件,用户导出的报告只能读安全账本。三者混在一起,后面再靠正则删除敏感字段,很难保证不漏。

    材料面向谁可以包含不应该包含
    hilog 开发调试 模块、事件码、公开计数、异常类型 用户正文、完整路径、原始备份内容
    运行账本 应用内部排障 阶段、结果、业务安全 id、数量 问题文本、答案文本、题库名
    用户报告 用户复制给客服或开发 版本、阶段、事件码、计数、截断 id 原始日志、Preferences 全量 JSON

    事件模型只允许安全字段进入账本

    诊断模型应该从类型层就限制输入。不要先定义一个大对象,再提醒调用方“不要传敏感字段”。更可靠的做法是让 SafeDiagnosticEvent 根本没有 questionText、answerText、deckName 这类字段。

    type DiagnosticStage = 'startup' | 'draw' | 'favorite' | 'import' | 'backup' | 'diagnostics';
    type DiagnosticCode =
    | 'OK'
    | 'EMPTY_DECK'
    | 'INVALID_ROUTE'
    | 'SCHEMA_MISMATCH'
    | 'STORE_WRITE_FAILED'
    | 'REPORT_REDACTED';

    interface SafeDiagnosticEvent {
    eventId: string;
    occurredAt: number;
    stage: DiagnosticStage;
    code: DiagnosticCode;
    deckRef?: string;
    itemCount?: number;
    appVersion: string;
    note?: string;
    }

    这段代码的关键不是字段多少,而是字段性质。deckRef 不是题库名,它应该来自系统生成的稳定 id 片段;itemCount 只表达数量;note 只写固定短语,不能拼接用户输入。这样模型本身就能挡住大部分误用。

    写 hilog 时也要按公开字段组织

    HarmonyOS 的 hilog 支持用格式占位标记公开或隐私参数。即使日志系统可以隐藏隐私参数,业务侧仍然不要把正文传进去后再指望日志显示层兜底。比较稳的策略是:日志只打事件码和计数,正文完全不进入日志调用。

    import hilog from '@ohos.hilog';

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

    class AnswerBookLog {
    static drawFinished(code: DiagnosticCode, candidateCount: number): void {
    hilog.info(
    DOMAIN,
    TAG,
    'drawFinished code=%{public}s count=%{public}d',
    code,
    candidateCount
    );
    }

    static drawRejected(code: DiagnosticCode): void {
    hilog.warn(DOMAIN, TAG, 'drawRejected code=%{public}s', code);
    }
    }

    这段日志能帮助判断抽取链路是否执行、候选数量是否异常,但它不关心“用户问了什么”。如果某次问题必须复现,也应该由用户手动描述,而不是应用自动把正文带出设备。

    脱敏器不要处理正文,要处理系统生成的标识

    很多团队会说“那我把问题正文 hash 一下再导出”。这听起来安全,实际不够稳:短文本、常见问题、固定答案都有被字典猜测的可能。诊断报告需要的是关联同一条业务对象,不需要还原内容,所以更推荐只处理系统生成的 id。

    class SafeReference {
    static fromStableId(id: string | undefined): string {
    if (!id || id.length < 8) {
    return 'unknown';
    }
    return `${id.substring(0, 4)}${id.substring(id.length 4)}${id.length}`;
    }

    static rejectUserText(label: string, value: string | undefined): void {
    if (value && value.trim().length > 0) {
    throw new Error(`${label} must not enter diagnostics report`);
    }
    }
    }

    这里的设计意图是反直觉的:不要“安全地导出正文”,而是从流程上不接收正文。fromStableId 只接受系统 id,rejectUserText 用在测试或调试构造里,帮助团队尽早发现有人把正文传到了诊断边界。

    运行账本由 Service 写入,页面只触发业务动作

    页面层最容易拿到完整展示数据,因此也最容易误把展示字段写进诊断报告。更清晰的 owner 是 DiagnosticsLedgerService:它提供几个窄入口,每个入口只接收必要参数。

    class DiagnosticsLedgerService {
    private readonly repository: DiagnosticsRepository;

    constructor(repository: DiagnosticsRepository) {
    this.repository = repository;
    }

    async recordDrawResult(deckId: string, candidateCount: number, ok: boolean): Promise<void> {
    const event: SafeDiagnosticEvent = {
    eventId: `draw-${Date.now()}`,
    occurredAt: Date.now(),
    stage: 'draw',
    code: ok ? 'OK' : 'EMPTY_DECK',
    deckRef: SafeReference.fromStableId(deckId),
    itemCount: candidateCount,
    appVersion: AppBuildInfo.versionName
    };
    await this.repository.append(event);
    }

    async recordImportBlocked(deckId: string, importedCount: number): Promise<void> {
    await this.repository.append({
    eventId: `import-${Date.now()}`,
    occurredAt: Date.now(),
    stage: 'import',
    code: 'SCHEMA_MISMATCH',
    deckRef: SafeReference.fromStableId(deckId),
    itemCount: importedCount,
    appVersion: AppBuildInfo.versionName
    });
    }
    }

    这段代码把“什么时候记一笔账”和“报告长什么样”分开了。抽取服务、导入服务只写安全事件;导出服务只读安全事件;页面没有机会把题目正文拼进报告字符串。

    导出服务重新组装报告,而不是复制系统日志

    用户报告应该是一份可读文本或 JSON,而不是 hilog 文件。报告头写清版本和时间,事件列表只保留白名单字段。为了便于客服沟通,可以给每个事件加一句固定解释,但解释也必须来自 code 映射表,不能拼接用户输入。

    const CODE_MESSAGE: Record<DiagnosticCode, string> = {
    OK: '流程完成',
    EMPTY_DECK: '当前题库没有可抽取条目',
    INVALID_ROUTE: '入口参数无效',
    SCHEMA_MISMATCH: '导入文件版本不兼容',
    STORE_WRITE_FAILED: '本地写入失败',
    REPORT_REDACTED: '报告已按白名单脱敏'
    };

    class DiagnosticsReportService {
    constructor(private readonly repository: DiagnosticsRepository) {}

    async buildReport(): Promise<string> {
    const events = await this.repository.loadRecent(30);
    const lines: string[] = [
    `app=${AppBuildInfo.versionName}`,
    `createdAt=${Date.now()}`,
    'privacy=only safe diagnostic fields are exported',
    ''
    ];

    events.forEach((event: SafeDiagnosticEvent, index: number) => {
    lines.push([
    `#${index + 1}`,
    `stage=${event.stage}`,
    `code=${event.code}`,
    `message=${CODE_MESSAGE[event.code]}`,
    `deck=${event.deckRef ?? '-'}`,
    `count=${event.itemCount ?? 0}`,
    `at=${event.occurredAt}`
    ].join(' '));
    });

    return lines.join('\\n');
    }
    }

    报告看起来没有原始日志“丰富”,但它更适合用户发送。开发者能看到阶段、错误码、计数和版本,足够决定下一步是查导入 schema、题库加载、路由参数还是本地写入。

    设置页要让用户先预览,再复制

    诊断导出不适合做成一个静默复制按钮。用户应该先看到将要发送的内容,确认里面没有私人问题和答案,再点复制。页面只负责展示报告和触发复制动作,真正的报告内容仍然由 Service 生成。

    @Component
    struct DiagnosticsExportPanel {
    @State private reportText: string = '';
    @State private loadError: string = '';
    private reportService: DiagnosticsReportService = new DiagnosticsReportService(new DiagnosticsRepository());

    aboutToAppear(): void {
    this.reportService.buildReport()
    .then((text: string) => {
    this.reportText = text;
    })
    .catch(() => {
    this.loadError = '诊断报告生成失败,请稍后重试';
    });
    }

    build() {
    Column({ space: 12 }) {
    Text('诊断报告')
    .fontSize(18)
    .fontWeight(FontWeight.Medium)
    Text(this.loadError.length > 0 ? this.loadError : this.reportText)
    .fontSize(13)
    .maxLines(12)
    .textOverflow({ overflow: TextOverflow.Ellipsis })
    Button('复制报告')
    .enabled(this.reportText.length > 0)
    .onClick(() => {
    DiagnosticsCopyService.copy(this.reportText);
    })
    }
    }
    }

    这段 ArkUI 示例故意没有在页面里读取题库、历史或答案。设置页越克制,导出边界越清楚;后续即使换复制 API 或分享入口,也不会影响脱敏规则。

    用敏感样本做反向验收

    诊断能力最怕只在正常样本下点一次通过。真正要验证的是“敏感内容不会出现”。可以准备一套本地测试题库,题目里故意包含姓名、手机号、地址、长文本和私密答案,然后触发抽取失败、导入失败、收藏失败,再导出报告。

    敏感样本:
    deckName=家庭决策
    questionText=测试姓名-A phone_138****8000 周五是否要转账
    answerText=不要把 bank_secret_keyword 告诉任何人
    expected=报告里不出现 测试姓名-A、phone_138****8000、bank_secret_keyword、家庭决策

    静态检查也可以很直接,不必复杂。导出样例落到 release/diagnostics/safe_report.txt 后,用关键词反查一次。

    rg n "测试姓名-A|phone_138\\*\\*\\*\\*8000|bank_secret_keyword|questionText|answerText|deckName" release/diagnostics

    如果这条命令有命中,就不要解释“只是测试数据”。对用户可发送报告来说,测试数据泄露和真实数据泄露在工程性质上是同一个问题,都说明边界没有收住。

    常见问题先按入口排查

    诊断报告不是越详细越好,而是要让开发者能按入口继续查。下面这张表可以直接放进团队检查记录里。

    现象先查哪里修复方向
    报告里出现题目或答案 DiagnosticsLedgerService 的入参 删除正文入参,只传系统 id 与计数
    报告为空但用户确实出错 业务服务是否写入安全事件 在抽取、导入、备份失败处补 recordXxx
    开发看不懂事件 DiagnosticCode 是否过少 补固定错误码和 code message,不补正文
    报告能定位但日志仍有正文 页面或 Service 的 hilog 调用 只打印公开事件码,正文不进入日志
    发布前无法证明安全 是否保存了导出样例 留一份脱敏报告样例和关键词扫描结果

    落地时给诊断能力留一条交付记录

    如果把这套方案改进到真实项目里,交付记录至少写四件事:本次新增了哪些安全事件码,哪些业务入口会写账本,导出报告保存在哪里,敏感样本扫描结果是什么。没有这些证据,只能说明代码片段看起来合理,不能说明诊断能力已经可交付。

    交付项应留下的证据说明
    事件模型 SafeDiagnosticEvent 和 DiagnosticCode 定义 证明报告字段受控
    写入入口 抽取、导入、备份等 Service 调用点 证明页面没有私自拼报告
    导出样例 safe_report.txt 或截图 证明用户看到的内容可检查
    反向扫描 敏感关键词扫描输出 证明没有正文泄露

    本文提供的是一种诊断导出设计方式,不等于已经在某个真实 HAP 里完成真机验证。落地到项目时,还要结合实际包名、页面入口、复制/分享方式和发布流程补齐验证截图。

    小结

    诊断导出要服务排障,但不能复制原始 hilog。把安全事件写进业务账本,把脱敏和报告组装收进 DiagnosticsReportService,再用敏感样本反向扫描,就能同时保留排查线索和隐私边界。对用户来说,能预览、能理解、能安全发送;对开发者来说,能定位阶段、错误码和版本,不需要拿到用户正文。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » HarmonyOS应用实战-启示散页-58-诊断导出别变成日志泄露:用脱敏报告替代原始 hilog
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!