别再满屏 `console.info` 了:用 HiLog 的 domain 和 tag 给项目日志立个规矩【鸿蒙心迹】

你是不是也在想——“鸿蒙这么火,我能不能学会?”
答案是:当然可以!
这个专栏专为零基础小白设计,不需要编程基础,也不需要懂原理、背术语。我们会用最通俗易懂的语言、最贴近生活的案例,手把手带你从安装开发工具开始,一步步学会开发自己的鸿蒙应用。
不管你是学生、上班族、打算转行,还是单纯对技术感兴趣,只要你愿意花一点时间,就能在这里搞懂鸿蒙开发,并做出属于自己的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 分类之后,遇到问题时排查路径会清晰很多:
- 先按 domain 定位模块:是网络问题还是数据库问题,先缩小范围;
- 再按 tag 定位组件:同一个 domain 下,具体是哪个类或哪个接口出了问题;
- 再看日志级别:先看
error,再看warn,最后看info; - 最后结合时间线:确认日志的时序是否符合预期。
如果日志全部是 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,请留言,我帮你踩坑!
更多推荐

所有评论(0)