HarmonyOS 6.1 全场景实战|《灵犀厨房》【日志系统】从 console 到 Logger:百万级日志的优雅升级之路
摘要:你的《灵犀厨房》里,
console.log满天飞、hilog.info各写各的 tag、错误日志和生产日志搅成一锅粥——上架时你敢让这些日志全量输出到用户设备上吗?关了又怕线上问题无法排查,开着又担心性能拖垮。你可能觉得“不就是打个日志嘛,console.log一下就行了”。但当你的 App 用console打了 200 处日志,用hilog打了 80 处日志,分布在 35+ 个文件里,你就知道什么叫“日志地狱”。本篇将基于 HarmonyOS 6.1.0(API 23),从日志架构的底层设计出发,带你完成一场“一行开关,全网静默”的统一日志升级。
一、引言:从“日志地狱”到“一行静默”
《灵犀厨房》已经迭代了20余篇专栏,功能越来越丰富——语音操控、深色模式、桌面卡片、健康仪表盘。每加一个功能,每写一个 Service,顺手就是一个 console.info('[XXX] 数据加载成功')。久而久之:
| 场景 | 痛点 | 后果 |
|---|---|---|
| 上架前日志清理 | 200+ 处 console.log 散落在 35+ 个文件中 | 不敢删、不敢留,纠结一整天 |
| 线上排查 Bug | hilog.info 的 tag 五花八门,查一条日志要翻 8 个文件 | 排查效率极低,一个问题查半天 |
| 生产环境性能 | console.log 在真机上也有开销,高频日志拖慢 UI | 用户感知到卡顿,体验扣分 |
| ForEach 缺少 key | 列表滚动时整个列表重绘,CPU 飙升 | 低端设备直接卡成 PPT |
| 定时器未清理 | 页面离开后 setTimeout 还在跑,回调访问已销毁组件 | 内存泄漏 + 偶发崩溃 |
而统一日志工具的一行配置解决所有问题:
🎯
IS_RELEASE = true→ 所有日志瞬间静默。IS_RELEASE = false→ 恢复全量输出。一行开关,全网静默。module 标识自动注入,调用栈精准定位。零运行时开销判定,发布性能无损耗。
二、核心原理:Logger 的四字真言
2.1 旧日志体系的三宗罪
在正式动手前,先看清旧体系的本质问题:
| 旧体系 | 问题本质 | 典型表现 |
|---|---|---|
console.log/info/error | 原生 API,无法统一管理 | 每个文件自己加 [TagName] 前缀,风格不统一 |
hilog.info(domain, tag, msg) | 需要手动传 DOMAIN,API 冗长 | hilog.info(0x0000, 'LingxiKitchen', ...) 每次写一大串 |
| 混合使用 | console 和 hilog 互不通信 | 生产环境关了 hilog,console 还在输出 |
核心矛盾:开发调试阶段需要详细日志,生产发布阶段需要静默。但 console 和 hilog 都没有统一的“全局开关”,你必须手动注释/删除每一处日志。
2.2 Logger 的一行开关原理
// ============================================================
// 层级:Foundation(基础设施层)— 全局工具
// 职责:统一日志工具类
// - 封装 hilog 调用,统一 tag 前缀
// - IS_RELEASE 开关控制全站日志输出
// - 支持 info / warn / error / debug 四个级别
// ============================================================
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN: number = 0x0001;
const TAG_PREFIX: string = 'LingxiKitchen';
/**
* 🔑 核心开关:上架前将此值改为 true
*/
export const IS_RELEASE: boolean = false;
class Logger {
private formatTag(module: string): string {
return `${TAG_PREFIX}-${module}`;
}
info(module: string, message: string, ...args: Object[]): void {
if (IS_RELEASE) return;
const formattedMsg = `[${module}] ${message}`;
hilog.info(DOMAIN, this.formatTag(module), formattedMsg, args);
}
warn(module: string, message: string, ...args: Object[]): void {
if (IS_RELEASE) return;
const formattedMsg = `[${module}] ${message}`;
hilog.warn(DOMAIN, this.formatTag(module), formattedMsg, args);
}
error(module: string, message: string, ...args: Object[]): void {
if (IS_RELEASE) return;
const formattedMsg = `[${module}] ${message}`;
hilog.error(DOMAIN, this.formatTag(module), formattedMsg, args);
}
debug(module: string, message: string, ...args: Object[]): void {
if (IS_RELEASE) return;
const formattedMsg = `[${module}] ${message}`;
hilog.debug(DOMAIN, this.formatTag(module), formattedMsg, args);
}
}
export const logger: Logger = new Logger();
四层设计逻辑:
┌─────────────────────────────────────────────────────────┐
│ logger.info('ModuleName', msg) │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ IS_RELEASE ? (运行时零开销) │ │
│ │ true → 静默 | false → 继续执行 │ │
│ └──────────────────┬───────────────────────────────┘ │
│ ↓ false │
│ ┌──────────────────▼───────────────────────────────┐ │
│ │ 格式化:[module] message + args │ │
│ └──────────────────┬───────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────▼───────────────────────────────┐ │
│ │ hilog.info(DOMAIN, TAG_PREFIX-module, msg) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
💡 核心精髓:
IS_RELEASE是模块级常量,V8 引擎在运行时读到第一个if (IS_RELEASE) return后,整条日志调用链的耗时 ≈ 一次布尔值读取。零函数调用开销,零字符串拼接浪费。
三、分层架构:日志服务的四层渗透
按照《灵犀厨房》的四层架构,logger 作为 Foundation 层的基础设施,被上层所有模块单向依赖:
| 层级 | 日志使用者 | 典型 module 标识 |
|---|---|---|
| UI 层 | 页面 / 组件 | LoginPage, RecipeDetailPage, HomeTabContent |
| ViewModel 层 | 状态管理 | HomeViewModel, AuthViewModel, SearchViewModel |
| Services 层 | 服务能力 | ApiService, TtsServiceHelper, RelationalStoreHelper |
| Business 层 | 业务流程 | VoiceControlManager, WatchTimerManager, DistributedFlowManager |
分层渗透图:
四、关键实现步骤
Step 1:创建统一的 Logger 工具类
在 entry/src/main/ets/common/utils/Logger.ets 中创建上述 Logger 类。
设计要点:
- 单例实例:
export const logger = new Logger(),全站共享一个实例,避免重复创建 - module 参数前置:每个日志调用必须传入
module标识,强制规范 - IS_RELEASE 前置判定:所有日志方法的第一行就是
if (IS_RELEASE) return,零开销 - hilog 封装:底层统一使用
@kit.PerformanceAnalysisKit的hilog,利用系统级日志管道
Step 2:批量迁移 console / hilog 调用
迁移策略:
| 原调用 | 新调用 |
|---|---|
console.info('[LoginPage] 登录成功') | logger.info('LoginPage', '登录成功') |
console.error('[ApiService] 请求失败: ' + err) | logger.error('ApiService', '请求失败', err) |
hilog.info(0x0000, 'Tag', 'msg') | logger.info('Module', 'msg') |
影响范围:
| 指标 | 数量 |
|---|---|
| console 调用替换 | ~100 处 |
| hilog 调用替换 | ~10 处 |
新增 import { logger } | 35+ 个文件 |
| 涉及文件 | 35+ 个 |
迁移清单:
| 文件 | 替换项 |
|---|---|
pages/RecipeDetailPage.ets | 6 处 console + 定时器清理修复 |
pages/LoginPage.ets | 3 处 console.error |
pages/RegisterPage.ets | 1 处 console.error |
pages/SearchPage.ets | 5 处 console |
pages/CookingMonitorPage.ets | 4 处 console |
pages/DeviceConnectPage.ets | 3 处 console |
pages/FavoriteRecipesPage.ets | 2 处 console |
pages/CommunityPage.ets | 2 处 console |
pages/ProfileEditPage.ets | 3 处 console |
components/ProfileTabContent.ets | 4 处 console |
components/HomeTabContent.ets | 5 处 console |
components/DeviceTabContent.ets | 3 处 console |
services/ApiService.ets | 8 处 console/hilog |
services/RelationalStoreHelper.ets | 5 处 console |
services/TtsServiceHelper.ets | 3 处 console |
services/SpeechRecognizerHelper.ets | 3 处 console |
services/WatchServiceHelper.ets | 2 处 console |
services/HiAIImageClassifier.ets | 2 处 console |
services/MultiIngredientDetector.ets | 3 处 console |
business/VoiceControlManager.ets | 4 处 console |
business/TtsSpeechManager.ets | 3 处 console |
business/WatchTimerManager.ets | 3 处 console |
business/DistributedFlowManager.ets | 2 处 console |
viewmodel/AuthViewModel.ets | 4 处 console |
viewmodel/HomeViewModel.ets | 4 处 console |
viewmodel/HistoryViewModel.ets | 3 处 console |
viewmodel/HealthDashboardViewModel.ets | 3 处 console |
viewmodel/SearchViewModel.ets | 3 处 console |
viewmodel/KitchenDeviceViewModel.ets | 2 处 console |
common/ToastUtil.ets | 1 处 console |
common/IngredientCamera.ets | 2 处 console |
entryability/EntryAbility.ets | 3 处 hilog |
Step 3:修复 ForEach 缺少 key 函数
在迁移日志的过程中,审查代码发现多处 ForEach 缺少 key 函数,导致列表重绘性能问题:
// ❌ 修复前:缺少 key 函数,整个列表每次重绘
ForEach(this.recipeList, (item: Recipe) => {
RecipeCard({ recipe: item })
})
// ✅ 修复后:指定 key 函数,按 ID 精确 diff
ForEach(this.recipeList, (item: Recipe) => {
RecipeCard({ recipe: item })
}, (item: Recipe) => item.id.toString())
修复清单:
| 文件 | 位置 |
|---|---|
pages/FavoriteRecipesPage.ets | 收藏列表 |
pages/RecipeHistoryPage.ets | 历史记录列表 |
pages/SearchPage.ets | 搜索结果列表 |
components/HomeTabContent.ets | 首页推荐列表 |
components/DeviceTabContent.ets | 设备列表 |
Step 4:修复定时器清理问题
RecipeDetailPage 中存在 voiceTimeoutId 语音超时定时器未在 aboutToDisappear 中清理的问题:
// ✅ 修复后:在组件销毁时清理定时器
aboutToDisappear(): void {
if (this.voiceTimeoutId !== -1) {
clearTimeout(this.voiceTimeoutId);
this.voiceTimeoutId = -1;
logger.debug('RecipeDetailPage', '已清理语音超时定时器');
}
// 其他清理逻辑...
}
Step 5:编译验证
$ hvigorw assembleHap
> Build success.
所有迁移完成后,编译一次通过,无语法错误,无链接错误。
五、代码增删改清单
| 文件 | 操作 | 职责 |
|---|---|---|
entry/src/main/ets/common/utils/Logger.ets | 新增 | 统一日志工具类,单例模式,IS_RELEASE 全局开关 |
entry/src/main/ets/viewmodel/HomeViewModel.ets | 修改 | 新增 import { logger } + 替换 4 处 console 调用 |
entry/src/main/ets/pages/RecipeDetailPage.ets | 修改 | 替换 6 处 console + 新增 aboutToDisappear 定时器清理 |
entry/src/main/ets/pages/**/*.ets (11 个文件) | 修改 | 替换 console 调用为 logger |
entry/src/main/ets/components/**/*.ets (3 个文件) | 修改 | 替换 console 调用 + ForEach key 修复 |
entry/src/main/ets/services/**/*.ets (8 个文件) | 修改 | 替换 console/hilog 调用为 logger |
entry/src/main/ets/business/**/*.ets (4 个文件) | 修改 | 替换 console 调用为 logger |
entry/src/main/ets/viewmodel/**/*.ets (6 个文件,含 HomeViewModel) | 修改 | 替换 console 调用为 logger |
entry/src/main/ets/common/ToastUtil.ets | 修改 | 替换 console.error 为 logger.error |
entry/src/main/ets/common/IngredientCamera.ets | 修改 | 替换 console 调用为 logger |
entry/src/main/ets/entryability/EntryAbility.ets | 修改 | 替换 hilog 调用为 logger |
六、血泪避坑总结
| 现象 | 真相 | 解决方案 |
|---|---|---|
迁移后编译报 Cannot find name 'logger' | 文件只替换了调用,忘记添加 import | 每个迁移的文件都必须加 import { logger } from '../common/utils/Logger' |
console.log 和 hilog.info 混用 | 两套 API 互不通信,无法统一管理 | 统一使用 logger,底层全走 hilog |
| ForEach 不写 key,列表滚动卡顿 | 缺少 diff 依据,整个列表每次全量重绘 | 指定 key 函数,利用 ID 精确 diff |
setTimeout 回调中访问已销毁组件 | 页面退出了定时器还在跑,回调读 undefined | aboutToDisappear 中用 clearTimeout 清理 |
| 日志迁移后文件层级混乱 | import 路径写错,相对路径不统一 | 以目标文件所在目录为基准,用 ../common/utils/Logger 计算正确相对路径 |
IS_RELEASE 改成 true 后忘了改回来 | 开发时日志全静默,查问题无从下手 | 在 hvigorfile 中通过构建参数注入,Debug 自动 false,Release 自动 true |
七、设计决策
| 决策 | 选择 | 理由 |
|---|---|---|
| 底层日志引擎 | hilog | HarmonyOS 系统级日志管道,DevEco Studio Log 窗口原生支持 |
| 日志工具封装形式 | 单例 const logger | 全站共享一个实例,避免每个文件 new Logger() 的冗余 |
| 全局静默开关 | 模块级 IS_RELEASE 常量 | if (false) return 被引擎优化为零开销,比 if (变量) 更快 |
| module 参数 | 调用时传入字符串 | 强制每个日志标注来源,比自动提取调用栈更轻量且可控 |
| 日志级别 | info / warn / error / debug | 四级别覆盖 99% 场景,比 console 的五级别更精简 |
| 参数传递 | ES6 扩展运算符 | logger.info('Mod', 'msg', obj1, obj2) 灵活传参 |
八、运行验证
验证场景 1:发布模式日志静默
- 将
Logger.ets中IS_RELEASE设为true - 执行
hvigorw assembleHap→ 安装到真机 - 期望结果:DevEco Studio Log 窗口无任何
LingxiKitchen-*日志输出,应用运行流畅
验证场景 2:调试模式日志全开
- 将
Logger.ets中IS_RELEASE设为false - 重新编译并运行
- 期望结果:Log 窗口按
LingxiKitchen-<ModuleName>tag 输出所有日志,过滤方便
验证场景 3:ForEach 性能对比
- 在迁移前后分别进入收藏列表页(含有 50+ 条数据)
- 快速上下滚动
- 期望结果:修复后滚动流畅,DevEco Studio Profiler 显示重绘次数大幅减少
验证场景 4:定时器清理验证
- 进入菜谱详情页 → 触发语音识别 → 在 5 秒超时前快速返回
- 期望结果:无崩溃,Log 窗口出现
已清理语音超时定时器日志
九、总结与下篇预告
本篇我们基于 HarmonyOS 6.1.0(API 23),为《灵犀厨房》完成了一场从 console 到 Logger 的日志体系升级。核心要点:
- 一行开关:
IS_RELEASE = true→ 全网静默,零运行时开销 - 统一接口:
logger.info(module, msg, ...args)替代 console 和 hilog 的所有调用 - 批量迁移:35+ 个文件、110+ 处调用,编译一次通过
- 附带收益:ForEach key 修复(5 处)、定时器清理修复(1 处)
- 架构哲学:日志工具属于 Foundation 层,被所有上层单向依赖
Logger API 速查表
| 场景 | 调用 |
|---|---|
| 通用信息 | logger.info('ModuleName', '数据加载完成', data) |
| 警告信息 | logger.warn('ModuleName', '网络延迟较高', latency) |
| 错误信息 | logger.error('ModuleName', '接口调用失败', error) |
| 调试信息 | logger.debug('ModuleName', '进入 onPageShow') |
| 发布静默 | IS_RELEASE = true |
📚 本系列持续更新中,敬请期待,下一篇更精彩。
🔗 专栏入口:[《HarmonyOS6.1全场景实战》合集]
📦 获取基线版本源码包:包括本系列所有代码 + 架构文档 + Flask 后端
如果你觉得这篇文章对你有帮助,请不要吝啬你的点赞 👍、收藏 ⭐ 和评论 💬。你的支持,是我继续输出高质量技术内容的全部动力。
纯血鸿蒙,用心造厨。我们下一篇见!

1500

被折叠的 条评论
为什么被折叠?



