杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目简介与核心能力
    • 快速开始
    • 工程结构与依赖库
  • 核心功能

    • RCSP OTA 升级流程
    • BLE 升级通道
    • SPP 升级通道
    • 自动回连机制
  • 蓝牙通信架构

    • 蓝牙抽象层与基础组件
    • BLE 模块实现
    • SPP 模块实现
    • 蓝牙管理与 OTA 管理器
  • 示例应用

    • 应用入口与启动流程
    • 主界面与设备连接交互
    • 关于、日志与辅助页面
  • 调试与运维

    • 日志系统与调试技巧
    • 问题排查与技术支持
  • 开发者指南

    • SDK 版本历史
    • 集成与二次开发指南

日志系统与调试技巧

本页介绍 JL_OTA HarmonyOS 应用内置的日志系统设计与调试方法,涵盖 hilog 输出封装、Worker 线程异步落盘、日志文件滚动、故障日志(FaultLog)采集,以及配套的日志文件浏览页面。

Purpose and Scope

本页面向需要阅读、扩展或调试 JL_OTA 应用的开发者,完整说明 entry/src/main/ets/tool/log/ 目录下的日志子系统:

  • Log.ts:主线程日志门面,封装 hilog 输出并将日志投递给 Worker
  • LogWorker.ets:后台线程,负责消息分发、队列缓冲与文件写入
  • LogWriter.ets:日志文件写入器(滚动、限长)
  • model/LogMessage.ts:线程间消息模型与消息类型枚举
  • LogFileListPage.ets:日志文件列表浏览页面
  • FaultLogWriter.ets:故障日志(崩溃/卡死)落盘写入器

本页不覆盖 OTA 升级流程本身、蓝牙传输、设备通信协议等业务能力,这些内容请参见对应目录页。日志系统只是 OTA 调试过程中"记录证据"的基础设施。

Overview

OTA 升级是高风险操作:断连、校验失败、版本不匹配等问题往往发生在设备端,问题复现困难。为了在真机上定位问题,JL_OTA 应用实现了一套"双通道"日志机制:

  1. 实时通道(hilog):通过 HarmonyOS 的 @ohos.hilog 将日志输出到系统日志缓冲区,配合 hdc shell hilog 命令实时查看,适合开发期联调。
  2. 持久通道(文件):所有日志同时格式化为纯文本,通过 worker 线程投递到独立后台线程,以同步流的方式写入应用缓存目录下的 appLog/ 文件夹,适合测试人员复现问题后直接导出日志文件。

设计意图在于:日志写入绝不能阻塞主线程(文件 I/O 是慢操作),同时 主线程日志 API 必须足够轻量(每个 API 只是格式化字符串 + postMessage)。系统还利用 @kit.PerformanceAnalysisKit 的 FaultLogger 查询 JS_CRASH 等故障记录,将崩溃现场(pid/uid/reason/fullLog)一并落盘,形成完整的故障证据链。

flowchart TD
    subgraph sg_Main["主线程 (UI)"] 
        App["业务代码"] -->|"Log.d/i/w/e()"| LogFacade["Log 静态类<br/>domain=0xFFBB, prefix=zhjieli"]
        LogFacade -->|"hilog.debug/info/warn/error"| HiLog["@ohos.hilog 系统日志"]
        LogFacade -->|"outputLog() 格式化"| Post["postMessage(msg)"]
    end

    subgraph sg_Worker["Worker 线程 (LogWorker.ets)"]
        OnMsg["workerPort.onmessage"] --> Switch{"MessageType 分发"}
        Switch -->|"LOG"| Queue["Queue&lt;string&gt; 缓冲"]
        Queue --> Handle["handleLog() 逐条弹出"]
        Handle --> LW["LogWriter.append()"]
        Switch -->|"START"| Init["初始化 Queue + LogWriter"]
        Switch -->|"STOP"| Close["关闭写入器"]
        Switch -->|"FAULT"| Fault["saveFaultLog()<br/>FaultLogger.query(JS_CRASH)"]
    end

    subgraph sg_Storage["存储层"]
        LW --> File["cacheDir/appLog/jlota-YYYYMMDD-HHmmss.SSS.txt"]
        Fault --> FaultWriter["FaultLogWriter"]
        FaultWriter --> FFile["cacheDir/appLog/fault_*.txt"]
    end

    HiLog -->|"hdc shell hilog 实时查看"| Dev["开发者终端"]
    File -->|"LogFileListPage 浏览/导出"| UI["日志列表页面"]

    Post --> OnMsg

上图展示了日志系统的整体架构:主线程只负责格式化与投递,所有磁盘 I/O 均发生在独立 Worker 线程中,通过 MessageType 枚举实现消息语义分发,存储层统一落在 cacheDir/appLog/ 目录。

Architecture

分层职责

层文件职责
门面层Log.ts静态方法 d/i/w/e,hilog 输出 + 格式化 + 投递 Worker
消息层model/LogMessage.tsLogMessage<T> 载荷 + MessageType 消息语义
工作线程LogWorker.ets消息分发、Queue 缓冲、故障日志查询
写入层LogWriter.ets / FaultLogWriter.ets同步流写文件、8MB 滚动
展示层LogFileListPage.ets列出 appLog/ 目录下的日志文件供查看/删除

关键设计决策

  • Worker 隔离 I/O:文件写入使用 fs.createStreamSync + writeSync + flushSync 同步 API,若放在主线程会卡 UI;放到 Worker 线程后主线程日志调用变为非阻塞的 postMessage。
  • 消息驱动而非函数调用:主线程与 Worker 之间通过 postMessage 传递 LogMessage,Worker 端 onmessage 按 type 分发,天然支持 START/LOG/STOP/FAULT 四类语义。
  • 日志与故障分离:普通日志走 LogWriter,崩溃现场走 FaultLogWriter,两者都是 BaseWriter 的子类,复用同一套"打开/追加/关闭"生命周期。
  • 防死循环约定:代码多处标注 Fixme: 此函数不能使用封装的Log,否则会死循环 —— Worker 内部(如 LogWriter.append 出错时)若调用 Log.e 会再次向自身投递 LOG 消息,形成无限递归。因此 Worker 内部错误处理也直接调用 Log.e(仅走 hilog 且 tryToSave 检测到 workerInstance 存在时仍会投递……实际代码中 LogWriter 的错误分支调用 Log.e 存在理论风险,但 handleLog 的同步 Promise 循环保证了单条消息处理完成前不会处理下一条,规避了递归风暴,详见"失败模式"章节)。

核心实现

Log 静态门面(Log.ts)

Log 是一个纯静态类,定义了三个关键常量:

  • domain: number = 0xFFBB:hilog 域 ID(应用自定义域,用于 hilog 过滤)
  • prefix: string = "zhjieli":hilog 标签前缀,品牌标识
  • format: string = '%{public}s':隐私格式化占位符,声明参数为公开内容(不脱敏)

四个对外方法 d/i/w/e 遵循同一模式:先调用对应级别的 hilog API 输出到系统日志,再调用私有 outputLog 格式化后投递 Worker。这样一条日志同时拥有实时通道与持久通道:

static d(tag: string, msg:string) {
  hilog.debug(Log.domain, Log.prefix, Log.format, `${tag}: ${msg}`);
  Log.outputLog(LogLevel.D, tag, msg)
}

static i(tag: string, msg: string) {
  hilog.info(Log.domain, Log.prefix, Log.format, `${tag}: ${msg}`);
  Log.outputLog(LogLevel.I, tag, msg)
}

static w(tag: string, msg: string) {
  hilog.warn(Log.domain, Log.prefix, Log.format, `${tag}: ${msg}`);
  Log.outputLog(LogLevel.W, tag, msg)
}

static e(tag: string, msg: string) {
  hilog.error(Log.domain, Log.prefix, Log.format, `${tag}: ${msg}`);
  Log.outputLog(LogLevel.E, tag, msg)
}

Source: Log.ts

日志行格式化(outputLog)

持久通道的每一行日志由 outputLog 拼接,格式为:

YYYYMMDD HH:mm:ss.SSS <级别> [module:]tag: message

其中 module 是受保护静态字段(默认空),alignNumber 负责月份/日期/时分秒补零(如 3 → 03)。注意时间格式与 hilog 原生格式不同,这是为了在文本文件里便于阅读和排序:

private static outputLog(level: LogLevel, tag: string, arg: string) {
  let date = new Date()
  let m = Log.module ? `${Log.module}:` : ''
  const time = date.getFullYear() + alignNumber(date.getMonth() + 1) + alignNumber(date.getDate()) + ' '
    + alignNumber(date.getHours()) + ':' + alignNumber(date.getMinutes()) + ':' + alignNumber(date.getSeconds())
    + '.' + date.getMilliseconds();
  const log = time + " " + level + " " + m + (tag ? tag : Log.prefix) + ": " + (arg ? arg : "null") + "\n";

  Log.tryToSave(log)
}

Source: Log.ts

tryToSave 是主线程与 Worker 之间的唯一桥梁:仅在 workerInstance 已创建时投递 MessageType.LOG 消息,否则静默丢弃(避免日志系统自身未启动时抛异常):

private static tryToSave(log: string) {
  if (workerInstance) {
    let m = new LogMessage<string>(MessageType.LOG)
    m.value = log;
    workerInstance?.postMessage(m)
  }
}

Source: Log.ts

生命周期管理

Log.startSaving(context) 负责创建 Worker:使用 worker.ThreadWorker('entry/ets/tool/log/LogWorker.ets') 指定 Worker 入口文件(相对 module 根),创建后立即发送 START 消息并注册 onexit 回调;stopSaving() 先发送 STOP 再 terminate() 回收线程。getFaultLog(context) 发送 FAULT 消息触发崩溃日志落盘。

消息模型(model/LogMessage.ts)

Worker 通信的载荷是 LogMessage<T> —— 一个极简的泛型信封,type 声明语义、value 携带数据:

export class LogMessage<T> {
  public type: MessageType;
  public value?: T;

  constructor(type: MessageType) {
    this.type = type;
  }
}

export enum MessageType {
  START = 1,
  LOG = 2,
  STOP = 3,
  FAULT = 4, // Fault log
}

Source: LogMessage.ts

四种消息语义:

  • START:携带日志目录路径(context.cacheDir + '/appLog'),Worker 初始化 Queue 与 LogWriter
  • LOG:携带已格式化的日志行字符串,Worker 入队并触发写入
  • STOP:关闭写入器、清空队列(线程随后由主线程 terminate)
  • FAULT:携带日志目录路径,Worker 查询系统故障日志并落盘

Worker 消息分发(LogWorker.ets)

Worker 入口通过 worker.workerPort.onmessage 注册处理函数,按 m.data.type 做 switch 分发。logHandler 是核心调度器:

function logHandler(m: MessageEvents) {
  switch (m.data.type) {
    case MessageType.LOG:// Fixme: 从这开始的函数都不要使用封装的Log,否则会死循环
      queue?.add((m.data as LogMessage<string>).value)
      handleLog()
      break
    case MessageType.START:
      const logMessage = m.data as LogMessage<string>
      if (logWriter && logWriter?.isOpened()) {
        Log.e(TAG, 'Log writer has opened')
        return
      }
      queue = new Queue()
      let logDir = logMessage.value
      logWriter = new LogWriter()
      logWriter.open(logDir!)
      break
    case MessageType.STOP:
      logWriter?.close()
      logWriter = undefined
      queue = undefined
      break
    case MessageType.FAULT:
      saveFaultLog(m)
      break;
  }
}

Source: LogWorker.ets

值得注意的细节:

  1. 幂等保护:START 时若 logWriter 已打开则直接 return,防止重复初始化。
  2. 队列消费:handleLog 用 while ((queue?.length ?? 0) > 0) 在单个 Promise 内把队列清空,保证"一批消息一个写入批次",减少 postMessage 与文件 I/O 的切换开销:
async function handleLog(): Promise<boolean> {
  let promise = new Promise<boolean>((resolve) => {
    while ((queue?.length ?? 0) > 0) {
      let log = queue?.pop() ?? '';
      logWriter?.append(log)
    }
    resolve(true);
  })
  return promise
}

Source: LogWorker.ets

  1. 错误透传:onmessageerror 与 onerror 均被捕获并记录,避免 Worker 静默崩溃。

文件写入与滚动(LogWriter.ets)

LogWriter 继承 BaseWriter,实现 append/open/close/isOpened。最核心的约束是 单文件 8MB 上限(LOG_FILE_SIZE_LIMIT = 8 * 1024 * 1024),达到上限后关闭当前流、新建文件,实现日志滚动:

append(log: string) {
  if (this.logStream) {
    this.logStream.writeSync(log);
    this.logStream.flushSync();
    this.fileSize += log.length
    if (this.fileSize >= LOG_FILE_SIZE_LIMIT) {
      this.close() // close previous file
      this.createFile() // create a new log file
    }
  }
}

private createFile(): boolean {
  this.fileSize = 0 // reset
  try {
    if (!fs.accessSync(this.logDir)) {
      fs.mkdirSync(this.logDir);
    }
    this.logPath = `${this.logDir}/${createFilename()}`
    this.logStream = fs.createStreamSync(this.logPath, "w");
  } catch (err) {
    Log.e(TAG, "Create file error: " + err);
    return false;
  }
  return true;
}

Source: LogWriter.ets

文件名由 createFilename() 按时间戳生成:jlota-YYYYMMDD-HHmmss.SSS.txt(毫秒级精度,理论上避免同秒重名冲突)。目录不存在时自动 mkdirSync,目录创建与流创建均包裹在 try/catch 中,失败时返回 false 并记录错误。

故障日志采集(saveFaultLog)

saveFaultLog 使用 FaultLogger.query(FaultLogger.FaultType.JS_CRASH) 查询系统记录的 JS 崩溃,逐条提取 pid / uid / type / timestamp / reason / module / summary / fullLog 字段,通过 FaultLogWriter 写入独立文件。getLogTypeDesc 将枚举映射为可读字符串(CPP_CRASH / JS_CRASH / APP_FREEZE / NO_SPECIFIC)。整个查询与写入包裹在 try/catch 中,任何一步失败都不会影响主线程。

日志文件浏览页面(LogFileListPage.ets)

LogFileListPage.ets 是日志系统的用户界面入口,负责展示 appLog/ 目录下生成的日志文件,支持查看与删除(仓库中同时存在 icon_log_delete.png 删除按钮资源)。它让测试人员无需连接 IDE 即可在应用内翻阅升级过程中的日志证据,是"调试技巧"中面向非开发者的最后一环。

核心流程

一条日志从业务代码到磁盘文件的完整旅程如下。以 OTA 过程中最常见的 Log.i(TAG, "upgrade start") 为例:

sequenceDiagram
    participant Biz as 业务代码 (OTA流程)
    participant Log as Log 静态类
    participant HiLog as hilog 系统日志
    participant Worker as LogWorker 线程
    participant Queue as Queue&lt;string&gt;
    participant Writer as LogWriter
    participant FS as appLog/*.txt 文件

    Note over Biz,Log: startSaving() 已先行执行
    Biz->>Log: Log.i(TAG, "upgrade start")
    Log->>HiLog: hilog.info(0xFFBB, "zhjieli", "%{public}s", "TAG: upgrade start")
    Log->>Log: outputLog() 拼装 "20240920 10:00:00.123 i TAG: upgrade start"
    Log->>Worker: postMessage({type: LOG, value: 行文本})
    Worker->>Queue: queue.add(行文本)
    Worker->>Queue: handleLog() while 循环弹出
    Queue-->>Writer: logWriter.append(行文本)
    Writer->>FS: writeSync + flushSync
    Writer->>FS: fileSize >= 8MB ? 关旧流建新文件
    FS-->>Biz: 下一行日志继续

启动时序

sequenceDiagram
    participant Main as 主线程
    participant W as Worker 线程
    participant WL as LogWriter
    participant FL as FaultLogWriter

    Main->>W: new ThreadWorker('entry/ets/tool/log/LogWorker.ets')
    Main->>W: postMessage({type: START, value: cacheDir/appLog})
    W->>W: queue = new Queue()
    W->>WL: new LogWriter().open(dir) 创建流
    Note over Main,W: 升级过程中持续 LOG 消息
    Main->>W: postMessage({type: FAULT, value: dir})
    W->>FL: saveFaultLog() 查询 JS_CRASH 并写文件
    Main->>W: postMessage({type: STOP})
    W->>WL: close() 关闭流
    Main->>W: terminate() 回收线程

关键点:START 必须先于任何 LOG 到达(tryToSave 在 worker 未创建时静默丢弃);STOP 后主线程立即 terminate,因此 Worker 无需自行 close()。

使用示例

基本用法:记录调试/信息/告警/错误

业务代码只需一行调用,四个级别覆盖全部场景。日志会自动同时出现在 hilog 与文件中:

Log.d(TAG, 'OTA upgrade start')   // 调试级别
Log.i(TAG, 'Ble connected')       // 信息级别
Log.w(TAG, 'Timeout, retry...')   // 告警级别
Log.e(TAG, 'Check crc error: ' + err)  // 错误级别

Source: Log.ts

启动/停止日志保存

通常在应用入口(如 aboutToAppear)调用 startSaving,退出时调用 stopSaving。startSaving 是幂等的:worker 已存在时直接返回,避免重复创建线程:

static startSaving(context: Context) {
  if (workerInstance) {
    return
  }
  workerInstance = new worker.ThreadWorker('entry/ets/tool/log/LogWorker.ets');
  let m = new LogMessage<string>(MessageType.START)
  m.value= context.cacheDir + LOG_DIR_NAME
  workerInstance.postMessage(m)

  workerInstance.onexit = (code: number) => {
    Log.w(TAG, 'Log thread worker>> exit: ' + code)
  }
}

static stopSaving() {
  let m = new LogMessage<string>(MessageType.STOP)
  workerInstance?.postMessage(m)
  workerInstance?.terminate()
  workerInstance = undefined
}

Source: Log.ts

采集崩溃故障日志

升级失败伴随 JS 崩溃时,调用 getFaultLog 将系统 FaultLogger 中的崩溃现场(含 fullLog)写入 appLog/ 目录:

static getFaultLog(context: Context) {
  let m = new LogMessage<string>(MessageType.FAULT)
  m.value = context.cacheDir + LOG_DIR_NAME
  workerInstance?.postMessage(m)
}

Source: Log.ts

自定义消息扩展

需要向 Worker 增加新语义时(例如"导出日志"),先在 MessageType 中追加枚举值,再在 logHandler 的 switch 中增加分支:

export enum MessageType {
  START = 1,
  LOG = 2,
  STOP = 3,
  FAULT = 4, // Fault log
}

Source: LogMessage.ts

配置选项

日志系统的可调参数集中在两个文件中,均为编译期常量(无运行时配置项):

常量位置类型默认值说明
LOG_DIR_NAMELog.tsstring'/appLog'日志目录名,拼接在 context.cacheDir 之后
Log.domainLog.tsnumber0xFFBBhilog 域 ID,用于 hdc shell hilog -D 过滤
Log.prefixLog.tsstring"zhjieli"hilog 标签前缀
Log.formatLog.tsstring'%{public}s'hilog 隐私格式化占位符(public 不脱敏)
Log.moduleLog.tsstring''受保护静态字段,可被子类覆盖,拼入文件行格式
LOG_FILE_SIZE_LIMITLogWriter.etsnumber8 * 1024 * 1024单个日志文件大小上限(字节),触发滚动
Worker 入口路径Log.tsstring'entry/ets/tool/log/LogWorker.ets'ThreadWorker 构造参数,相对 module 根

Source: Log.ts / LogWriter.ets

API 参考

Log 静态类

方法签名说明
static d(tag: string, msg: string): void调试日志:hilog.debug + 落盘(级别 d)
static i(tag: string, msg: string): void信息日志:hilog.info + 落盘(级别 i)
static w(tag: string, msg: string): void告警日志:hilog.warn + 落盘(级别 w)
static e(tag: string, msg: string): void错误日志:hilog.error + 落盘(级别 e)
static startSaving(context: Context): void创建 Worker 并发送 START;幂等,已存在则直接返回
static stopSaving(): void发送 STOP 后 terminate Worker,置空实例
static getFaultLog(context: Context): void发送 FAULT 消息,触发崩溃日志落盘

参数约定:

  • tag:模块标签,写入 hilog 与文件行中(如 LogWriter、OTA)
  • msg:日志正文;outputLog 对空值显示为 null
  • context:用于获取 cacheDir 拼出日志目录

返回: 全部为 void,日志调用永不向调用方抛异常(Worker 未启动时静默丢弃)。

LogWriter 类(继承 BaseWriter)

方法签名说明
open(logDir: string): boolean校验目录参数、创建文件流;失败返回 false
append(log: string): void同步写入 + flush;超过 8MB 自动关闭旧流、新建文件
close(): boolean关闭流,置空引用;异常时返回 false
isOpened(): boolean流是否已创建

LogWorker 内部函数

函数说明
logHandler(m: MessageEvents)onmessage 入口,按 MessageType 分发
handleLog(): Promise<boolean>单 Promise 内清空队列并逐条写入
saveFaultLog(m: MessageEvents)查询 JS_CRASH 并写入 FaultLogWriter
getLogTypeDesc(type: FaultLogger.FaultType): string故障类型枚举转可读字符串

失败模式、边界情况与并发

死循环风险(自引用)

源码中两处 Fixme 注释(LogWriter 头部与 logHandler 的 LOG 分支)明确警告:Worker 内部不得调用封装的 Log。原因是 Log.e → outputLog → tryToSave → 若 workerInstance 存在则向 Worker 投递新 LOG 消息 → 再次进入写入逻辑 → 若再出错再投递……形成无限递归。实际防护机制:

  • 文件写入失败路径(createFile 的 catch)调用 Log.e,此时 Worker 正在处理消息,新 LOG 会入队,但 handleLog 的同步 while 循环与单线程模型确保当前批次先执行完毕,不会在消息处理中嵌套重入——风险被降级为"失败日志可能反复入队",而非栈溢出。
  • 扩展时遵循同一约定:Worker 线程内只允许 console.* 或直接 hilog,禁止调用封装 Log。

Worker 未启动时的日志丢失

tryToSave 在 workerInstance 为 undefined 时静默丢弃日志。若业务代码在 startSaving 之前打日志,这些行只出现在 hilog、不会落盘。设计取舍:日志系统自身失败不应影响业务主流程,属于有意的 fail-open。

文件系统异常

mkdirSync / createStreamSync 失败(磁盘满、权限不足)被 try/catch 捕获并返回 false;append 因 writeSync 抛错未捕获时,该批次写入中断,但 Worker 不崩溃(后续消息继续尝试)。close 同样捕获异常返回 false。注意:目录不存在时不会自动递归创建父目录,mkdirSync(this.logDir) 假定 cacheDir 已存在(系统保证)。

并发与一致性

  • 单消费者模型:Worker 是 ArkTS 单线程,Queue 无锁安全;主线程的 postMessage 是有序投递,日志行顺序与调用顺序一致。
  • 批量写入:handleLog 在一个 Promise 内清空队列,多条日志共享一次调度窗口,减少上下文切换;代价是高峰期日志可能短暂积压。
  • 滚动竞态:8MB 触发的关流/建流在同一同步代码块内完成,Worker 单线程下无并发窗口,不会出现"双流写同一文件"。

重复初始化保护

startSaving 检查 workerInstance 幂等返回;Worker 端 START 分支检查 logWriter.isOpened() 防止重复 open。stopSaving 后 workerInstance = undefined,可安全再次 startSaving。

性能与运维注意事项

  • 同步写盘:writeSync + flushSync 是阻塞 I/O,这是刻意隔离在 Worker 线程的原因;主线程只承担字符串格式化与 postMessage(约微秒级),UI 流畅度不受日志量影响。
  • 文件大小:单文件 8MB 上限防止日志无限膨胀;滚动文件名含毫秒时间戳,同秒滚动也几乎不会重名。运维上建议定期清理 cacheDir/appLog/(页面提供了删除入口)。
  • 日志目录位置:日志写在 cacheDir(应用缓存目录),系统可能在空间紧张时清理缓存目录,重要日志应尽早通过 LogFileListPage 导出备份。
  • hilog 实时过滤:开发期可用 hdc shell hilog | grep zhjieli 过滤本应用日志;domain=0xFFBB 也可用于 hilog -D 0xFFBB 级别的域过滤。

调试技巧

  1. 级别分层:开发期用 d,联调用 i,可恢复异常用 w,不可恢复错误用 e。hilog 本身有级别过滤,落盘文件也保留级别便于事后检索。
  2. tag 约定:每个模块以自己的类名作 tag(如 TAG = 'LogWriter'),配合文件行中的 [module:]tag 前缀,grep 时可按模块快速切片。
  3. 崩溃现场闭环:FaultLogWriter 输出含 reason、summary、fullLog 的完整崩溃栈;配合普通日志的时间线,可还原"崩溃前发生了什么"。
  4. 文件查看入口:LogFileListPage 直接在应用内列出并删除 appLog/ 下的日志文件,无需数据线即可取证,适合测试与售后场景。
  5. 隐私占位符:format 使用 %{public}s 声明公开,若日志含用户敏感信息,应改为 %{private}s 由 hilog 自动脱敏。

扩展点

  • BaseWriter 抽象基类:LogWriter 与 FaultLogWriter 共用"open/append/close/isOpened"契约,新增输出目标(如上报服务器、加密存储)只需实现 BaseWriter 并挂到 logHandler。
  • MessageType 枚举:新增消息语义(如导出、清空、采样开关)按"枚举 + switch 分支"两步扩展,主线程通过 LogMessage<T> 泛型携带任意载荷。
  • Log.module 受保护字段:可派生 Log 子类覆盖 module,为不同业务域打上独立前缀,实现日志分区。

Related Links

  • Log.ts 源码
  • LogWorker.ets 源码
  • LogWriter.ets 源码
  • LogMessage.ts 源码
  • FaultLogWriter.ets 源码
  • LogFileListPage.ets 源码
  • 升级流程相关页面:OTA 业务流程文档(见目录导航)
Next
问题排查与技术支持