在这里插入图片描述

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的App!
📌 关注本专栏《零基础学鸿蒙开发》,一起变强!
每一节内容我都会持续更新,配图+代码+解释全都有,欢迎点个关注,不走丢,我是小白酷爱学习,我们一起上路 🚀

前言

很多开发者刚开始写 HarmonyOS 应用时,日志是这样的:

console.info('进来了')
console.info('网络请求发出去了')
console.info('数据库查完了')
console.info('页面跳转了')

运行起来没问题,日志也能打出来,但打开 DevEco Studio 的 Log 面板一看——几十条日志混在一起,没有来源,没有分类,出了问题根本不知道从哪里开始看。

HarmonyOS 提供了 @ohos.hilog 模块,专门用来解决这个问题。它的核心设计就是 domain 和 tag 两个参数。搞清楚这两个参数的含义,再给项目定一套日志规范,日志从此就有了结构。

一、console.info 有什么问题

console.info、console.warn、console.error 在 HarmonyOS 应用开发中可以用,但它没有给开发者提供分类手段。

所有用 console 输出的日志,在系统日志流里无法按模块过滤,也无法按业务标识检索。项目规模一大,同时有网络、数据库、UI 三个方向的日志,它们全部平铺在同一个流里,区分起来非常麻烦。

@ohos.hilog 的设计就是为了给这个问题提供解法:通过 domain(日志域)和 tag(标签)两个维度,让每一条日志都带着自己的来源信息。

二、HiLog 的基础结构

模块引入

import hilog from '@ohos.hilog';

@ohos.hilog 是 HarmonyOS 系统级日志模块,从 API 7 开始支持,当前仍然是推荐使用的应用日志方案。

基本调用形式

hilog.info(domain, tag, format, ...args);
hilog.warn(domain, tag, format, ...args);
hilog.error(domain, tag, format, ...args);
hilog.debug(domain, tag, format, ...args);
hilog.fatal(domain, tag, format, ...args);

每一个日志级别的方法,参数结构都是一样的:domain、tag、格式字符串、可变参数。

三、domain 是什么,怎么用

定义

domain 是一个整型数值,表示日志输出的业务域标识,类型为 number。

按照官方规定:

  • 取值范围是 0x0 到 0xFFFF
  • 用于标识这条日志来自应用的哪个业务模块

官方建议开发者为自己的应用自行分配一段 domain 区间,在 0x0 到 0xFFFF 范围内选取有意义的值,避免与系统域冲突。

在 Log 过滤器中的作用

DevEco Studio 的 Log 面板支持按 domain 过滤日志。当你给不同的业务模块分配了不同的 domain 值,就可以在工具里快速定位某个模块的全部日志,而不是手动在几百条里逐条翻。

实际分配建议

一个中型应用可以这样划分 domain:

模块domain 值
Ability(生命周期)0x0001
Network(网络请求)0x0002
Database(数据库)0x0003
UI(页面交互)0x0004

这个分配没有硬性规定,关键是团队内部统一,写进文档。

四、tag 是什么,怎么用

定义

tag 是一个字符串,对日志做进一步的精细标识,类型为 string。

按照官方规定:

  • 最大长度为 31 个字节
  • 超过这个长度的 tag 会被截断

tag 相当于在 domain 的大分类下,再细分到具体的类或文件。比如 Network 模块下,可以用 HttpRequest、WebSocket 这样的 tag 区分具体的网络组件。

tag 命名的实际意义

Android 开发者对 Log.d(TAG, message) 这种用法很熟悉,HiLog 的 tag 和这个思路类似,但因为同时有了 domain 分层,所以 tag 可以写得更聚焦。

五、格式化参数的用法

HiLog 支持在格式字符串中使用占位符:

占位符对应类型
%d整型
%f浮点型
%s字符串
%{public}s公开字符串(不脱敏)
%{private}s私有字符串(脱敏,日志中显示 <private>)

这里有一个值得注意的设计:HiLog 默认会对 %s 类型的字符串进行脱敏处理。在 release 版本中,如果你直接用 %s 输出用户数据,日志里看到的会是 <private>,而不是实际内容。

如果你想在 debug 阶段明确输出某个字符串的值,应该使用 %{public}s。

hilog.info(0x0002, 'HttpRequest', 'url is %{public}s, status is %d', url, statusCode);

六、搭一套项目日志规范

光知道参数含义还不够,真正的价值是把它变成项目里的规范。

第一步:统一封装一个 Logger 工具类

把 domain 和 tag 的定义收进一个地方,避免散落在各个文件里。

// logger/Logger.ets

import hilog from '@ohos.hilog';

// 统一定义各模块的 domain
const Domain = {
  ABILITY: 0x0001,
  NETWORK: 0x0002,
  DATABASE: 0x0003,
  UI: 0x0004,
} as const;

// 统一定义各模块的 tag
const Tag = {
  ABILITY: 'AbilityLifecycle',
  NETWORK: 'HttpRequest',
  DATABASE: 'RelationalStore',
  UI: 'PageAction',
} as const;

export class Logger {
  // Ability 生命周期日志
  static abilityInfo(format: string, ...args: Object[]): void {
    hilog.info(Domain.ABILITY, Tag.ABILITY, format, ...args);
  }

  static abilityError(format: string, ...args: Object[]): void {
    hilog.error(Domain.ABILITY, Tag.ABILITY, format, ...args);
  }

  // 网络模块日志
  static networkInfo(format: string, ...args: Object[]): void {
    hilog.info(Domain.NETWORK, Tag.NETWORK, format, ...args);
  }

  static networkError(format: string, ...args: Object[]): void {
    hilog.error(Domain.NETWORK, Tag.NETWORK, format, ...args);
  }

  // 数据库模块日志
  static databaseInfo(format: string, ...args: Object[]): void {
    hilog.info(Domain.DATABASE, Tag.DATABASE, format, ...args);
  }

  static databaseError(format: string, ...args: Object[]): void {
    hilog.error(Domain.DATABASE, Tag.DATABASE, format, ...args);
  }

  // UI 操作日志
  static uiInfo(format: string, ...args: Object[]): void {
    hilog.info(Domain.UI, Tag.UI, format, ...args);
  }

  static uiWarn(format: string, ...args: Object[]): void {
    hilog.warn(Domain.UI, Tag.UI, format, ...args);
  }
}

这段代码的核心思路是:不让 domain 值和 tag 字符串散落在业务代码里。一旦某个 tag 需要修改,只改这一个地方,不需要全项目搜索替换。

第二步:在各模块中调用

Ability 生命周期场景:

// EntryAbility.ets
import { Logger } from '../logger/Logger';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    Logger.abilityInfo('EntryAbility onCreate, want action: %{public}s', want.action ?? '');
  }

  onDestroy(): void {
    Logger.abilityInfo('EntryAbility onDestroy');
  }
}

网络请求场景:

// service/NetworkService.ets
import { Logger } from '../logger/Logger';
import http from '@ohos.net.http';

async function fetchData(url: string): Promise<string> {
  Logger.networkInfo('fetchData start, url: %{public}s', url);

  let httpRequest = http.createHttp();
  try {
    let response = await httpRequest.request(url, {
      method: http.RequestMethod.GET,
    });
    Logger.networkInfo('fetchData success, status: %d', response.responseCode);
    return response.result as string;
  } catch (err) {
    Logger.networkError('fetchData failed, code: %d', (err as BusinessError).code);
    throw err;
  } finally {
    httpRequest.destroy();
  }
}

数据库操作场景:

// database/UserStore.ets
import { Logger } from '../logger/Logger';

function queryUser(userId: string): void {
  Logger.databaseInfo('queryUser start, id: %{public}s', userId);
  // 数据库查询逻辑...
  Logger.databaseInfo('queryUser complete');
}

UI 操作场景:

// pages/HomePage.ets
import { Logger } from '../logger/Logger';

@Entry
@Component
struct HomePage {
  build() {
    Button('提交')
      .onClick(() => {
        Logger.uiInfo('submit button clicked');
        // 业务逻辑...
      })
  }
}

七、几个容易忽略的地方

1. tag 长度限制是字节而不是字符

官方文档中明确说明 tag 最大 31 字节。中文字符在 UTF-8 编码下通常占 3 个字节,所以如果你用中文写 tag,实际能写的字符数比你想象的少得多。

建议 tag 始终用英文,既避开字节计算问题,也方便在 Log 面板里输入关键词过滤。

2. %s 默认脱敏,不是 bug

不少开发者第一次看到日志里显示 <private> 时,以为是打印失败或者代码写错了。实际上这是 HiLog 的设计行为——在 release 包中保护用户数据不泄露到日志文件里。

如果在 debug 阶段需要看到完整内容,请改用 %{public}s。

// 这样写在 release 包日志里可能看到 <private>
hilog.info(0x0001, 'Test', 'value: %s', userInput);

// 这样写在 debug 阶段可以看到完整内容
hilog.info(0x0001, 'Test', 'value: %{public}s', userInput);

3. domain 值一旦确定建议不要频繁改动

因为日志系统通常会被持久化或者用于排查线上问题,如果 domain 值在版本迭代中来回变动,历史日志的检索就会失效。建议在项目初期就把 domain 分配表写进开发规范文档,作为长期约定。

4. isLoggable 方法可以用来做条件判断

HiLog 提供了 hilog.isLoggable(domain, tag, level) 方法,可以在输出日志前先判断当前日志级别是否开启,避免不必要的格式化开销。

import hilog from '@ohos.hilog';
import { hilog as HilogLevel } from '@ohos.hilog';

if (hilog.isLoggable(0x0002, 'HttpRequest', hilog.LogLevel.DEBUG)) {
  hilog.debug(0x0002, 'HttpRequest', 'detailed request body: %{public}s', body);
}

这在打印体量较大的调试信息时,可以避免字符串拼接带来的额外消耗。

八、日志规范的排查价值

建立了 domain 和 tag 分类之后,遇到问题时排查路径会清晰很多:

  1. 先按 domain 定位模块:是网络问题还是数据库问题,先缩小范围;
  2. 再按 tag 定位组件:同一个 domain 下,具体是哪个类或哪个接口出了问题;
  3. 再看日志级别:先看 error,再看 warn,最后看 info;
  4. 最后结合时间线:确认日志的时序是否符合预期。

如果日志全部是 console.info,这四步中的前两步都无法做,只能靠肉眼在无结构的日志流里逐条识别。

开发经验总结

  • @ohos.hilog 从 API 7 起支持,domain 取值范围 0x0 到 0xFFFF,tag 最大 31 字节,建议统一用英文。
  • 项目中不要让 domain 和 tag 的值散落在业务代码里,封装一个统一的 Logger 工具类,是成本最低、收益最直接的做法。
  • %s 默认脱敏是 HiLog 的设计行为,debug 阶段如果需要看明文,改用 %{public}s。
  • 四个分类(Ability、Network、Database、UI)只是一个起点,实际项目可以根据模块数量适当扩充,但分类粒度不建议过细,tag 足够区分具体组件。
  • hilog.isLoggable 在需要打印大体量调试信息时值得用,可以减少不必要的格式化开销。

如果你现在的项目日志已经是一锅粥的状态,可以考虑从下一个新模块开始引入 Logger 封装,不必一次性改完所有历史代码,逐步迁移即可。


你的项目里有没有遇到过因为日志没有分类,导致线上排查时不知道从哪里开始看的情况?可以聊聊你们现在的日志策略,也许有更适合你们业务结构的分类方式。

❤️ 如果本文帮到了你…

  • 请点个赞,让我知道你还在坚持阅读技术长文!
  • 请收藏本文,因为你以后一定还会用上!
  • 如果你在学习过程中遇到bug,请留言,我帮你踩坑!
Logo

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

更多推荐