OpenHarmony 日志打印统一封装 LogUtil(分级日志、生产环境屏蔽日志 API23)
摘要
原生 console 日志存在缺陷:无日志分级、无法统一开关、打印信息缺少页面 / 函数标识、上线后调试日志无法批量屏蔽、不能自动写入本地日志文件。封装全局 LogUtil 工具,区分 DEBUG/INFO/WARN/ERROR 四级日志,支持全局日志总开关、自动携带类名 / 行号、可同步输出至沙盒本地日志文件,开发环境完整打印,打包生产环境一键关闭调试日志,避免日志泄露敏感信息、减少日志打印性能损耗。API23 优化日志输出缓冲区、文件写入异步队列,修复高频打印阻塞 UI、日志文件过大不分割、多页面并发写日志错乱等问题。
关键词
OpenHarmony;ArkTS;日志工具;分级日志;日志开关;本地日志持久化;console 封装
一、引言
1.1 原生 console 开发痛点
- 无日志分级,调试打印、报错日志混杂,排查问题困难;
- 上线后无法快速屏蔽所有调试打印,存在账号、token 等敏感信息泄露风险;
- 每条日志无法自动携带页面、函数、代码行号,定位代码繁琐;
- 大量循环打印 console 会阻塞主线程,造成页面滑动卡顿;
- 日志仅输出控制台,崩溃日志无法本地留存,复现 bug 无记录;
- 多页面同时打印日志,输出顺序混乱,无时间戳区分。
1.2 日志分级定义
表格
| 级别 | 用途 | 生产环境控制 |
|---|---|---|
| DEBUG | 开发调试变量、接口返回、流程节点 | 线上默认关闭 |
| INFO | 正常业务流程:登录成功、页面加载、数据刷新 | 线上可保留 |
| WARN | 非致命异常:缓存失效、接口超时、空数据 | 线上保留 |
| ERROR | 崩溃、数据库异常、接口报错、逻辑致命错误 | 强制永久开启 |
API23 日志核心升级点:
- 新增异步日志输出缓冲,高频打印不阻塞 UI 渲染线程;
- 文件日志自动按大小分割,单文件到达阈值新建日志;
- 全局日志开关全局生效,无需逐个注释 console 代码;
- 过滤敏感字段自动脱敏,防止 token、手机号明文打印;
- 多线程并发写入日志文件内置互斥锁,内容不错乱。
二、完整日志工具封装 utils/log_util.ets
ets
import 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 页面初始化注入上下文
ets
import 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): Promise<ResponseData<T> | 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 应用发布屏蔽调试日志(EntryAbility)
ets
import 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 分层架构项目。
更多推荐


所有评论(0)