Hilog规范化封装:分级日志、线上裁剪、防止敏感信息泄露

日志是开发调试和线上排查问题的核心工具。但很多项目里的日志写得很随意:到处console.info,没有分级,线上Debug日志没关掉,甚至把手机号、身份证号、Token直接打出来。调试的时候方便了,上线之后问题一堆:日志太多影响性能、敏感信息泄露被审核打回、出了问题找不到关键日志。

本文基于最新鸿蒙API,把Hilog的规范化封装讲清楚:分级怎么设、线上怎么裁剪、敏感信息怎么过滤,代码可以直接复制到项目里用。

01 Hilog基础用法与分级

鸿蒙的日志系统叫Hilog,由@kit.PerformanceAnalysisKit提供。基础用法很简单,直接调用info、debug、error这些方法就行。

import { hilog } from '@kit.PerformanceAnalysisKit';

const DOMAIN = 0x0001; // 业务域ID,自定义
const TAG = 'MyApp';

hilog.info(DOMAIN, TAG, '这是一条普通日志');
hilog.debug(DOMAIN, TAG, '这是调试日志');
hilog.error(DOMAIN, TAG, '出错了');
hilog.warn(DOMAIN, TAG, '警告信息');

Hilog有五个日志级别,从低到高:

  • DEBUG:调试级,最详细,开发时用,上线必须关

  • INFO:信息级,关键流程节点,比如页面打开、接口成功

  • WARN:警告级,不影响运行但有隐患,比如接口重试、缓存过期

  • ERROR:错误级,功能出错了,比如接口失败、数据解析异常

  • FATAL:致命级,应用崩溃了,一般系统级用

日志级别的核心作用是过滤。你可以设置一个最低级别,低于这个级别的日志就不输出。比如线上设置成INFO,那DEBUG日志就全部丢弃,不会输出到控制台,也不会占性能。

02 分级日志封装

直接用原生Hilog有几个问题:每个地方都要传domain和tag,写起来麻烦;想统一改日志格式,要改所有地方;没法控制线上哪些级别输出。所以封装一层很有必要。

import { hilog } from '@kit.PerformanceAnalysisKit';

class Logger {
  private domain: number = 0x0001;
  private tag: string = 'MyApp';

  // 配置全局tag
  setTag(tag: string) {
    this.tag = tag;
  }

  debug(message: string) {
    hilog.debug(this.domain, this.tag, message);
  }

  info(message: string) {
    hilog.info(this.domain, this.tag, message);
  }

  warn(message: string) {
    hilog.warn(this.domain, this.tag, message);
  }

  error(message: string) {
    hilog.error(this.domain, this.tag, message);
  }
}

// 全局单例
export const logger = new Logger();

用的时候直接调:

import { logger } from './Logger';

logger.info('首页加载完成');
logger.debug('接口返回数据', JSON.stringify(data));
logger.error('请求失败', error.message);

这样比直接写hilog.info要简洁,而且全局统一了domain和tag。以后想改domain或者加统一前缀,只改Logger类就行,不用动所有调用的地方。

更进一步,可以给每个模块单独设tag。比如用户模块的日志前缀是User,订单模块是Order,排查问题的时候按tag过滤就很方便。

03 线上日志裁剪

开发环境里DEBUG日志越多越好,但上线之后必须关掉。不然用户手机上跑着DEBUG级别日志,性能受影响,日志文件占空间,还可能泄露信息。

裁剪的核心是:根据BuildType判断当前是Debug还是Release版本,决定哪些日志输出。

import { hilog } from '@kit.PerformanceAnalysisKit';
import { BuildTime } from 'build_time';

class Logger {
  private domain: number = 0x0001;
  private tag: string = 'MyApp';
  private isDebug: boolean = BuildTime.DEBUG; // 自动注入是否Debug版本

  debug(message: string) {
    // 只有Debug版本才输出debug日志
    if (this.isDebug) {
      hilog.debug(this.domain, this.tag, message);
    }
  }

  info(message: string) {
    hilog.info(this.domain, this.tag, message);
  }

  warn(message: string) {
    hilog.warn(this.domain, this.tag, message);
  }

  error(message: string) {
    hilog.error(this.domain, this.tag, message);
  }
}

export const logger = new Logger();

BuildTime.DEBUG是编译时自动注入的,Debug打包是true,Release打包是false。这样打包上线之后,所有debug方法直接不执行,连字符串拼接的开销都省了。

除了DEBUG,INFO也可以裁剪。比如上线后只保留WARN和ERROR,普通信息日志全部关掉,性能更好。但一般建议保留INFO,线上出问题排查的时候,关键流程的日志很有用。

还有一个细节:日志里不要拼大对象。比如直接把整个接口返回结果JSON.stringify打出来,数据量大的时候很耗性能。线上版本这些详细日志应该只在DEBUG下输出。

04 敏感信息过滤

这是最容易出事的地方。手机号、身份证号、银行卡号、密码、Token,这些绝对不能打到日志里。一不小心就被应用市场审核打回,甚至触发安全合规问题。

封装日志的时候,统一加一层敏感信息过滤,自动把常见的敏感字段替换成***。

import { hilog } from '@kit.PerformanceAnalysisKit';
import { BuildTime } from 'build_time';

class Logger {
  private domain: number = 0x0001;
  private tag: string = 'MyApp';
  private isDebug: boolean = BuildTime.DEBUG;

  // 敏感字段正则
  private sensitivePatterns: RegExp[] = [
    /1[3-9]\d{9}/g,           // 手机号
    /\d{17}[\dXx]/g,          // 身份证号
    /\d{16,19}/g,             // 银行卡号
    /(?<=token=)\w+/gi,       // Token参数
    /(?<=password=)\w+/gi     // 密码参数
  ];

  // 过滤敏感信息
  private filterSensitive(message: string): string {
    let result = message;
    this.sensitivePatterns.forEach(pattern => {
      result = result.replace(pattern, '***');
    });
    return result;
  }

  // 统一输出入口
  private output(level: string, message: string) {
    // 线上版本过滤敏感信息
    const filtered = this.isDebug ? message : this.filterSensitive(message);
    switch (level) {
      case 'debug':
        if (this.isDebug) hilog.debug(this.domain, this.tag, filtered);
        break;
      case 'info':
        hilog.info(this.domain, this.tag, filtered);
        break;
      case 'warn':
        hilog.warn(this.domain, this.tag, filtered);
        break;
      case 'error':
        hilog.error(this.domain, this.tag, filtered);
        break;
    }
  }

  debug(message: string) {
    this.output('debug', message);
  }

  info(message: string) {
    this.output('info', message);
  }

  warn(message: string) {
    this.output('warn', message);
  }

  error(message: string) {
    this.output('error', message);
  }
}

export const logger = new Logger();

这样不管谁打日志,线上版本都会自动过滤掉手机号、身份证号这些敏感信息。不用靠开发人员自觉,从底层就兜住了。

正则可以根据自己项目的业务扩展,比如订单号、用户ID这些需要脱敏的,都加进去。Debug版本不过滤,方便开发调试;Release版本自动脱敏,安全合规。

05 完整日志工具类

把分级、裁剪、敏感信息过滤都整合到一起,就是一个可以直接用的日志工具类。

import { hilog } from '@kit.PerformanceAnalysisKit';
import { BuildTime } from 'build_time';

class LogManager {
  private domain: number = 0xA001;
  private defaultTag: string = 'HarmonyApp';
  private isDebug: boolean = BuildTime.DEBUG;

  private patterns: RegExp[] = [
    /1[3-9]\d{9}/g,
    /\d{17}[\dXx]/g,
    /[A-Za-z0-9]{20,}/g // 长字符串模拟Token
  ];

  private mask(text: string): string {
    let result = text;
    this.patterns.forEach(p => result = result.replace(p, '***'));
    return result;
  }

  private log(level: 'D'|'I'|'W'|'E', tag: string, msg: string) {
    const content = this.isDebug ? msg : this.mask(msg);
    const fullTag = `${this.defaultTag}_${tag}`;
    switch(level) {
      case 'D':
        if (this.isDebug) hilog.debug(this.domain, fullTag, content);
        break;
      case 'I':
        hilog.info(this.domain, fullTag, content);
        break;
      case 'W':
        hilog.warn(this.domain, fullTag, content);
        break;
      case 'E':
        hilog.error(this.domain, fullTag, content);
        break;
    }
  }

  d(tag: string, msg: string) { this.log('D', tag, msg); }
  i(tag: string, msg: string) { this.log('I', tag, msg); }
  w(tag: string, msg: string) { this.log('W', tag, msg); }
  e(tag: string, msg: string) { this.log('E', tag, msg); }
}

export const Log = new LogManager();

// 使用示例:
// Log.d('Order', '订单列表请求开始');
// Log.i('User', '用户登录成功,userId: 123');
// Log.e('Pay', '支付失败: 余额不足');

调用的时候传两个参数:模块tag和日志内容。每个模块的日志带自己的前缀,排查问题的时候按tag过滤,一眼就能定位是哪个模块出的问题。

落地注意事项

**不要在循环里打日志。**列表数据循环的时候,不要每条数据都打一条日志。循环一千次就打一千条,性能直接掉下来。要打就打总数和关键统计信息。

**异常日志要带上下文。**error日志不能只写"出错了",要写清楚什么操作、什么参数、什么错误。比如"提交订单失败,orderId=xxx, reason=库存不足",这样线上看到日志就知道是哪里的问题。

**日志TAG要统一规范。**不要一个地方写OrderList,另一个地方写order_list,tag命名不统一,过滤的时候很麻烦。按模块名统一,比如User、Order、Pay、Net,一眼就懂。

**线上日志不要打大对象。**整个接口返回结果、整个数据库查询结果,这些大对象不要直接打到INFO级别。线上版本这些详细日志应该只在DEBUG下输出,不然日志文件会非常大。

小结

日志封装的核心就三件事:分级输出、按构建类型裁剪、敏感信息过滤。封装一层Logger,全局统一调用,不用每个地方都写原生Hilog。开发环境详细日志随便打,上线之后自动裁剪、自动脱敏,性能和安全都有保障。

实际项目里,把日志规范写进团队的代码评审标准里,不能随便console.info,必须走封装好的Logger。时间久了,日志质量上去了,线上排查问题的效率会高很多。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐