#
# file: 20250406.log
# logger.info('Test logger.info...')
# logger.warn(new Map([['name', '这是一个 Map 对象']]))
#
┌────────────────────────────────────────────────────────────────────────────────
│ Info | 2025-04-06 14:58:59.810 (+00:00:25.276)
├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│ Test logger.info...
├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│ #0 at print (library/src/main/ets/service/printer.ets:235:45)
│ #1 at print (library/src/main/ets/service/printer.ets:269:12)
│ #2 at log (library/src/main/ets/abstract/logger.ets:114:22)
└────────────────────────────────────────────────────────────────────────────────
┌────────────────────────────────────────────────────────────────────────────────
│ Warn | 2025-04-12 12:25:13.608 (+00:00:14.910)
├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│ {
│ "__type__": "Map",
│ "__entries__": [
│ [
│ "name",
│ "这是一个 Map 对象"
│ ]
│ ]
│ }
├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
│ #0 at print (library/src/main/ets/service/printer.ets:259:45)
│ #1 at print (library/src/main/ets/service/printer.ets:293:12)
│ #2 at log (library/src/main/ets/abstract/logger.ets:154:22)
└────────────────────────────────────────────────────────────────────────────────
前言
在 HarmonyOS 应用开发过程中,高效的日志系统 对开发调试和问题排查至关重要。传统的 hilog 原生接口虽然功能完备,但在实际开发中仍存在诸多不便:
- 复杂数据:需要手动序列化对象和集合类型
- 日志过滤:缺乏运行时动态调整日志级别的能力
- 本地缓存:没有内置的日志文件管理和轮转机制
- 日志上报:缺少开箱即用的日志上报云端的方案
之前在开发 Flutter App 时,使用过 Flutter logger 日志库,其优雅的设计给我留下了深刻印象。受此启发,我决定开发 HarmonyOS 类似的日志解决方案 (logger)
- 支持 多种数据类型,如基本数据类型、对象、Class、Map、Set 等支持 JSON 序列化数据
- 支持 自定义日志行为,如日志动态过滤、本地文件读写(定期自动清理)、云端日志上报等
- 支持 HarmonyOS Next API12+
- 支持 堆栈信息输出
前期准备
-
前往鸿蒙三方库中心仓,注册 ohpm 账号 (openharmony)
-
前往【个人中心 / 认证管理】,新增 OHPM 公钥
# ssh-keygen 创建公私密钥,需要注意的是 passphrase 不可省略!!! # -------------------------------------------------------------- ssh-keygen -m PEM -t RSA -b 4096 -f ~/.ssh_ohpm/id_rsa_publish Generating public/private RSA key pair. Enter passphrase (empty for no passphrase): -
前往【个人中心】复制发布码,本地设备配置 publish_id
# your_publish_id: 你复制的发布码 # ---------------------------------------------- ohpm config set publish_id your_publish_id
开发讲解
-
梳理基本功能,定义抽象类 API
API 作用 详细描述 Filter 日志过滤 控制哪些日志需要被记录,支持日志级别过滤 (DEBUG/INFO/WARN/ERROR) 等 Output 日志输出流管理 定义日志最终输出位置和行为, 如控制台、本地文件、网络上报、多输出源组合 Printer 日志内容格式化 负责日志视觉呈现, 如文本格式化(颜色/缩进/边框)、结构化输出、堆栈跟踪 Logger 日志触发执行入口 提供开发者调用日志接口, 多级别日志方法 (debug、info、warn、error等) -
基于上述 抽象类 API,实现核心功能
API 继承 作用 详细描述 SafeFilter Filter基础日志过滤 基于日志级别的基础过滤 (DEBUG、INFO、WARN、ERROR) SafeWriteFileOutput Output文件系统日志输出 支持自动创建日志目录(按天)、日志文件轮转存储、异常恢复机制, 同时定义了异步锁 AsyncMutex 来优化极短时间内的高频写入和读取 SafeConsoleOutput Output控制台日志输出 适配 DevEco Studio 控制台,支持颜色区分和高亮 SafeMultiOutput Output多目标组合输出 适配多端输出源,支持输出权重配置 SafeSimplePrinter Printer简约日志格式 单行文本输出,无额外装饰(无时间戳/无边框),适合高频调试日志 SafePrettyPrinter Printer美观格式化输出 带边框的多行格式化,自动对齐键值对,支持显示堆栈跟踪记录,支持复杂对象树形展示 SafeHybridPrinter Printer混合策略打印机 不同日志级别使用不同打印策略,支持自定义级别映射 SafeLogger Logger开发者调用入口 提供开发者调用日志接口, 多级别日志方法 (debug、info、warn、error 等) -
预设了默认选项,以便开发者快速入手使用
// 默认预设 import { SafeLogger } from 'logger' import { SafeFilter } from 'logger' import { SafeMultiOutput } from 'logger' import { SafeConsoleOutput } from 'logger' import { SafeWriteFileOutput } from 'logger' import { SafePrettyPrinter } from 'logger' import { SafeSimplePrinter } from 'logger' import { SafeHybridPrinter } from 'logger' export const logger = new SafeLogger({ printer: new SafeHybridPrinter(new SafePrettyPrinter(), { debug: new SafeSimplePrinter() }), output: new SafeMultiOutput([new SafeConsoleOutput(), new SafeWriteFileOutput()]), filter: new SafeFilter(), }) // 当然,也支持开发者自定义,例如: import { logger } from 'logger' logger.reset({ printer: new SafeHybridPrinter(new SafePrettyPrinter({ lineLength: 120 }), { debug: new SafeSimplePrinter() }), output: new SafeMultiOutput([ new SafeWriteFileOutput({ storage: 'example/logger/cache', fileFormatter: 'harmony.[yyyyMMdd]' }), new SafeConsoleOutput() ]), })// 开发者使用示范 import { logger } from 'logger' /** * logger 写入 */ const error = new Error('参数格式有误') logger.error('Test logger.error...', error.message, error.stack) logger.warn(new Map([['name', '这是一个 Map 对象']])) /** * logger 读取文件 */ await logger.info('Test logger.info...') const string = await logger.readAsString() -
其他高级功能,云端日志上报示范
import axios, { FormData } from '@ohos/axios' import { logger } from 'logger' const report = async (limit: number) => { let bytes = 0 const data = new FormData() const bufs = await logger.readAsArrayBuffers() for (const buf of bufs) { if (bytes > 0 && bytes + buf.buffer.byteLength > limit) { break } data.append('file', buf.buffer, { filename: buf.filename, type: buf.type }) bytes = bytes + buf.buffer.byteLength } axios.post('/upload', data, { headers: { 'Content-Type': 'multipart/form-data' } }); } report(10 * 1024 * 1024) // 限制最大10M
常用 API
| 分类 | 方法 | 说明 | 返回类型 |
|---|---|---|---|
| 自定义选项 | reset | 自定义日志配置 | Promise<void> |
| 日志记录 | debug | 记录 DEBUG 级别日志 | Promise<void> |
info | 记录 INFO 级别日志 | Promise<void> | |
warn | 记录 WARN 级别日志 | Promise<void> | |
error | 记录 ERROR 级别日志 | Promise<void> | |
fatal | 记录 FATAL 级别(最高级)日志 | Promise<void> | |
| 文件读取 | readAsRequestFile | 读取最新日志文件(文件完整路径) | Promise<IRequestFile | null> |
readAsRequestFiles | 读取近期日志文件(文件完整路径) | Promise<IRequestFile[] | null> | |
readAsArrayBuffer | 读取最新日志文件(文件二进制数据) | Promise<IArrayBuffer | null> | |
readAsArrayBuffers | 读取近期日志文件(文件二进制数据) | Promise<IArrayBuffer[] | null> | |
readAsString | 读取最新日志文件(文件文本内容) | Promise<IString | null> | |
readAsStrings | 读取近期日志文件(文件文本内容) | Promise<IString[] | null> | |
| 事件监听 | addWriteListener | 注册日志写入事件的监听器 | void |
removeWriteListener | 移除日志写入事件的监听器 | void | |
| 清理日志 | clearHistory | 清除过期的日志文件 | Promise<void> |
clearAll | 清除所有日志文件(包括当前日志) | Promise<void> | |
| 检查日志 | isEmpty | 检查日志文件是否为空 (即不存在) | Promise<boolean> |
// TS 类型说明
export interface IString {
content: string; // 文件内容
filename: string; // 文件名 如 '20250410.log'
type: string; // 文件类型 如 'text/plain'
}
export interface IArrayBuffer {
buffer: ArrayBuffer; // 文件内容 (字节)
filename: string; // 文件名 如 '20250410.log'
type: string; // 文件类型 如 'text/plain'
}
export interface IRequestFile {
name: string; // 文件名 (不含后缀) 如 '20250410'
filename: string; // 文件名 (含后缀) 如 '20250410.log'
uri: string; // 文件完整路径 如 context.filesDir + '/xxxx/20250410/.log'
type: string; // 文件后缀,如 'log'
}
源码/仓库
OpenHarmony logger
Github logger
欢迎给个 Star 👍