开发 ArkTS 版 HarmonyOS 日志库 —— logger

293 阅读5分钟
# 
# 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+
  • 支持 堆栈信息输出

前期准备

  1. 前往鸿蒙三方库中心仓,注册 ohpm 账号 (openharmony)

  2. 前往【个人中心 / 认证管理】,新增 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):
      
    
  3. 前往【个人中心】复制发布码,本地设备配置 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继承作用详细描述
    SafeFilterFilter基础日志过滤基于日志级别的基础过滤 (DEBUG、INFO、WARN、ERROR)
    SafeWriteFileOutputOutput文件系统日志输出支持自动创建日志目录(按天)、日志文件轮转存储、异常恢复机制, 同时定义了异步锁 AsyncMutex 来优化极短时间内的高频写入和读取
    SafeConsoleOutputOutput控制台日志输出适配 DevEco Studio 控制台,支持颜色区分和高亮
    SafeMultiOutputOutput多目标组合输出适配多端输出源,支持输出权重配置
    SafeSimplePrinterPrinter简约日志格式单行文本输出,无额外装饰(无时间戳/无边框),适合高频调试日志
    SafePrettyPrinterPrinter美观格式化输出带边框的多行格式化,自动对齐键值对,支持显示堆栈跟踪记录,支持复杂对象树形展示
    SafeHybridPrinterPrinter混合策略打印机不同日志级别使用不同打印策略,支持自定义级别映射
    SafeLoggerLogger开发者调用入口提供开发者调用日志接口, 多级别日志方法 (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 👍