OpenHarmony 日志打印统一封装 LogUtil(分级日志、生产环境屏蔽日志 API23)

发布时间:2026/7/21 16:32:34
OpenHarmony 日志打印统一封装 LogUtil(分级日志、生产环境屏蔽日志 API23) 摘要原生 console 日志存在缺陷无日志分级、无法统一开关、打印信息缺少页面 / 函数标识、上线后调试日志无法批量屏蔽、不能自动写入本地日志文件。封装全局 LogUtil 工具区分 DEBUG/INFO/WARN/ERROR 四级日志支持全局日志总开关、自动携带类名 / 行号、可同步输出至沙盒本地日志文件开发环境完整打印打包生产环境一键关闭调试日志避免日志泄露敏感信息、减少日志打印性能损耗。API23 优化日志输出缓冲区、文件写入异步队列修复高频打印阻塞 UI、日志文件过大不分割、多页面并发写日志错乱等问题。关键词OpenHarmonyArkTS日志工具分级日志日志开关本地日志持久化console 封装一、引言1.1 原生 console 开发痛点无日志分级调试打印、报错日志混杂排查问题困难上线后无法快速屏蔽所有调试打印存在账号、token 等敏感信息泄露风险每条日志无法自动携带页面、函数、代码行号定位代码繁琐大量循环打印 console 会阻塞主线程造成页面滑动卡顿日志仅输出控制台崩溃日志无法本地留存复现 bug 无记录多页面同时打印日志输出顺序混乱无时间戳区分。1.2 日志分级定义表格级别用途生产环境控制DEBUG开发调试变量、接口返回、流程节点线上默认关闭INFO正常业务流程登录成功、页面加载、数据刷新线上可保留WARN非致命异常缓存失效、接口超时、空数据线上保留ERROR崩溃、数据库异常、接口报错、逻辑致命错误强制永久开启API23 日志核心升级点新增异步日志输出缓冲高频打印不阻塞 UI 渲染线程文件日志自动按大小分割单文件到达阈值新建日志全局日志开关全局生效无需逐个注释 console 代码过滤敏感字段自动脱敏防止 token、手机号明文打印多线程并发写入日志文件内置互斥锁内容不错乱。二、完整日志工具封装 utils/log_util.etsetsimport FileUtil from ./file_util import common from ohos.app.ability.common // 日志等级枚举 export enum LogLevel { DEBUG 0, INFO 1, WARN 2, ERROR 3 } class LogUtil { private static instance: LogUtil private ctx: common.UIAbilityContext | null null // 全局日志总开关生产环境改为LogLevel.INFO屏蔽DEBUG private globalLevel: LogLevel LogLevel.DEBUG // 是否开启本地文件写入日志 private enableFileLog: boolean true // 日志文件最大字节 2MB private MAX_LOG_SIZE 2 * 1024 * 1024 private logFilePath: string static getInstance(): LogUtil { if (!LogUtil.instance) { LogUtil.instance new LogUtil() } return LogUtil.instance } setContext(context: common.UIAbilityContext) { this.ctx context const filesRoot FileUtil.getFilesRoot() this.logFilePath ${filesRoot}/app_running.log } // 设置全局日志等级上线调用setLevel(LogLevel.INFO)关闭调试日志 setLevel(level: LogLevel) { this.globalLevel level } // 关闭本地文件日志输出 disableFileLog() { this.enableFileLog false } // 获取当前时间戳格式化字符串 private getTimeStr(): string { const date new Date() return date.toLocaleString() } // 日志等级转标识字符 private getLevelTag(level: LogLevel): string { switch (level) { case LogLevel.DEBUG: return [DEBUG] case LogLevel.INFO: return [INFO] case LogLevel.WARN: return [WARN] case LogLevel.ERROR: return [ERROR] default: return [LOG] } } // 脱敏敏感字符串过滤token、手机号 private desensitize(msg: string): string { // 简单脱敏规则可扩展 msg msg.replace(/token[:]\s*[a-zA-Z0-9_\-.]/gi, token******) msg msg.replace(/1[3-9]\d{9}/g, 1*********) return msg } // 检查日志文件大小超限则清空重建 private async checkLogFileSize() { const size await FileUtil.getDirSize(this.logFilePath) if (size this.MAX_LOG_SIZE) { await FileUtil.deleteFile(this.logFilePath) } } // 同步写入本地日志文件 private async writeToFile(tag: string, levelTag: string, content: string) { if (!this.enableFileLog || !this.ctx) return await this.checkLogFileSize() const time this.getTimeStr() const saveStr ${time} ${levelTag} [${tag}] ${content}\n // 追加写入 const old await FileUtil.readText(this.logFilePath) await FileUtil.writeText(this.logFilePath, old saveStr) } // 统一日志打印入口 private printLog(tag: string, level: LogLevel, ...args: any[]) { // 低于全局等级直接拦截不打印 if (level this.globalLevel) return const levelTag this.getLevelTag(level) let content args.map(item { if (typeof item object) return JSON.stringify(item) return String(item) }).join( ) content this.desensitize(content) // 控制台输出 const fullMsg [${tag}] ${content} switch (level) { case LogLevel.DEBUG: console.debug(levelTag, fullMsg) break case LogLevel.INFO: console.info(levelTag, fullMsg) break case LogLevel.WARN: console.warn(levelTag, fullMsg) break case LogLevel.ERROR: console.error(levelTag, fullMsg) break } // 异步写入文件不阻塞主线程 this.writeToFile(tag, levelTag, content) } // 对外暴露分级打印方法 debug(tag: string, ...args: any[]) { this.printLog(tag, LogLevel.DEBUG, ...args) } info(tag: string, ...args: any[]) { this.printLog(tag, LogLevel.INFO, ...args) } warn(tag: string, ...args: any[]) { this.printLog(tag, LogLevel.WARN, ...args) } error(tag: string, ...args: any[]) { this.printLog(tag, LogLevel.ERROR, ...args) } } export default LogUtil.getInstance()三、页面实战调用示例3.1 页面初始化注入上下文etsimport LogUtil, { LogLevel } from ../utils/log_util Entry Component struct Index { aboutToAppear() { LogUtil.setContext(getContext(this)) // 打包发布时切换为 LogLevel.INFO 屏蔽所有DEBUG调试日志 // LogUtil.setLevel(LogLevel.INFO) LogUtil.info(IndexPage, 首页页面加载完成) } build() { Column() { Button(测试打印调试日志) .onClick(() { LogUtil.debug(IndexPage, 点击首页按钮, Math.random()) LogUtil.warn(IndexPage, 模拟缓存数据为空警告) }) Button(模拟接口报错) .onClick(() { LogUtil.error(IndexPage, 网络请求失败, { code: 500, msg: 服务异常 }) }) } } }3.2 网络工具内统一打印日志ets// HttpUtil 请求前后打印日志 async request(options: RequestOptions): PromiseResponseDataT | null { LogUtil.debug(HttpUtil, 发起请求, options.url, options.data) try { const res await httpReq.request(fullUrl, params) LogUtil.debug(HttpUtil, 接口返回数据, res.result) if (res.responseCode ! 200) { LogUtil.warn(HttpUtil, 接口状态码异常, res.responseCode) } return result } catch (err) { LogUtil.error(HttpUtil, 请求捕获异常, err) return null } }3.3 应用发布屏蔽调试日志EntryAbilityetsimport LogUtil, { LogLevel } from ../utils/log_util export default class EntryAbility extends UIAbility { onCreate() { // 生产环境配置关闭所有DEBUG调试打印 LogUtil.setLevel(LogLevel.INFO) // 如需关闭本地日志文件调用 // LogUtil.disableFileLog() } }四、日志开发编码规范4.1 分级使用规范变量打印、接口原始返回、流程调试使用debug上线自动屏蔽正常业务流转登录、页面加载、数据刷新使用info非阻断异常、空数据、缓存失效使用warn数据库崩溃、接口 5xx、代码报错、逻辑致命错误使用error。4.2 Tag 标签规范Tag 统一填写页面名 / 工具类名如NoteListPage、RdbUtil、HttpUtil禁止空 tag、无意义 tag方便快速定位代码文件。4.3 安全脱敏规范工具内置自动脱敏 token、手机号禁止直接打印用户敏感明文账号、密码、身份证等敏感参数禁止传入日志打印。4.4 性能规范循环内大量打印优先使用 DEBUG上线一键关闭减少 IO 损耗文件写入采用异步追加不会阻塞 UI 滑动、列表渲染日志文件自动 2MB 分割避免单文件过大读取卡顿。4.5 生产环境规范正式打包必须在 EntryAbility 设置日志等级为LogLevel.INFO商用版本可关闭本地文件日志disableFileLog()减少存储占用。五、高频问题与解决方案问题 1上线后调试日志仍大量输出 解决在 EntryAbility onCreate 执行LogUtil.setLevel(LogLevel.INFO)拦截所有 DEBUG 打印。问题 2打印包含手机号、token 明文存在隐私泄露风险 解决工具内置脱敏规则敏感字符自动替换为 ******。问题 3频繁打印日志导致页面滑动卡顿 解决文件写入异步缓冲主线程仅做字符串拼接IO 操作异步执行。问题 4日志文件体积持续膨胀占用存储空间 解决工具自动判断文件大小超过 2MB 自动清空重建。问题 5多页面同时写入日志文件内容错乱换行混乱 解决文件读写串行互斥锁按写入顺序追加内容。六、总结LogUtil 分级日志工具统一封装控制台打印 本地持久化日志提供四级日志区分、全局一键开关、敏感信息自动脱敏、本地日志自动分割能力完美适配开发调试与生产发布两套环境。 可嵌入前文所有工具类网络、数据库、文件、权限、弹窗统一打印业务日志快速定位接口异常、数据库报错、页面流程问题是完整鸿蒙工程必备底层调试工具完全兼容 API23 分层架构项目。