HarmonyOS7 日志打印要有模块名:ArkUI/ArkTS 实战拆解
文章目录
前言
日志打印不是开发时随手写一句 success。等问题到了测试机、灰度包、线上用户那里,能不能按模块和动作搜到日志,直接决定排查效率。
这篇单独聊 登录流程 这个场景。重点不是堆 API,而是让日志能按模块、动作和关键参数串起一条可追踪链路。
为什么这个问题经常被写乱
日志打印要有模块名 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。
所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。
场景:登录失败只能靠日志还原
登录流程的问题经常不是“必现”的。测试同学说某台设备偶尔登录失败,用户说验证码收不到,服务端说没有收到请求。这个时候如果日志只有 success、fail、click,基本等于没有日志。真正有用的日志应该能回答三个问题:哪个模块、哪个动作、带了哪些非敏感关键参数。
我通常会给每条日志固定一个模块前缀,比如 [Login],再把动作拆成 input_change、validate_failed、request_start、request_success。这样无论是在控制台、hilog 还是测试截图里,都能快速串起流程。
好日志不是越多越好,而是出问题时能顺着同一条线查下去。
日志字段约定
| 字段 | 示例 | 说明 |
|---|---|---|
| 模块名 | [Login] | 方便过滤和归属 |
| 动作名 | request_start | 描述当前链路节点 |
| 请求标识 | trace=login-001 | 串联一次操作 |
| 关键参数 | accountLength=8 | 只记录非敏感信息 |
| 结果 | result=success | 记录最终状态 |
实操步骤
- 每个模块定义固定
moduleName,不要临时手写前缀。 - 每次提交生成一个
traceId,同一次操作的日志都带上它。 - 点击、校验、请求开始、请求结果、页面跳转都打关键日志。
- 只打印账号长度、验证码长度、错误码,不打印手机号、token、验证码明文。
- 测试环境可以在 UI 上展示最近日志,方便截图复现。
- 日志文案保持结构化,避免一会儿中文一句、一会儿英文一句。
先把页面目标想清楚
在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。
当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。
完整示例:带模块名和 traceId 的登录日志
@Entry
@Component
struct LoginLogPage {
@State account: string = ''
@State code: string = ''
@State lastLog: string = '等待操作'
@State traceId: string = 'login-idle'
private moduleName: string = 'Login'
private submitCount: number = 0
private log(action: string, detail: string) {
const message: string = `[${this.moduleName}] ${action} trace=${this.traceId} ${detail}`
console.info(message)
this.lastLog = message
}
private nextTraceId(): string {
this.submitCount += 1
return `login-${this.submitCount}`
}
private maskAccountLength(): string {
return `accountLength=${this.account.length}`
}
private submit() {
this.traceId = this.nextTraceId()
this.log('submit_click', `${this.maskAccountLength()} codeLength=${this.code.length}`)
if (this.account.length < 4) {
this.log('validate_failed', 'field=account reason=too_short')
return
}
if (this.code.length !== 6) {
this.log('validate_failed', 'field=code reason=length_invalid')
return
}
this.log('request_start', 'api=/login method=POST')
const success: boolean = this.code === '123456'
if (success) {
this.log('request_success', 'next=home')
} else {
this.log('request_failed', 'errorCode=LOGIN_CODE_INVALID')
}
}
build() {
Column({ space: 14 }) {
Text('登录日志示例')
.fontSize(22)
.fontWeight(FontWeight.Bold)
TextInput({ placeholder: '请输入账号', text: this.account })
.onChange((value: string) => {
this.account = value
this.log('input_change', `field=account accountLength=${value.length}`)
})
TextInput({ placeholder: '请输入 6 位验证码', text: this.code })
.onChange((value: string) => {
this.code = value
this.log('input_change', `field=code codeLength=${value.length}`)
})
Button('登录')
.width('100%')
.onClick(() => this.submit())
Column({ space: 6 }) {
Text('最近日志')
.fontSize(14)
.fontWeight(FontWeight.Medium)
Text(this.lastLog)
.fontSize(12)
.fontColor('#666666')
}
.alignItems(HorizontalAlign.Start)
.backgroundColor('#F1F3F5')
.padding(10)
.borderRadius(8)
}.padding(16)
}
}
把关键代码一段段拆开
moduleName 是固定前缀,不应该每次日志手写。手写前缀很容易出现 [login]、[LoginPage]、[UserLogin] 混用,后面过滤日志会很痛苦。
traceId 用来串联一次登录动作。用户点一次登录,从点击、校验、请求到结果都带同一个 trace,排查时可以直接搜索 login-3 还原全过程。
maskAccountLength() 只输出账号长度,不输出账号本身。验证码、token、手机号、身份证、地址这类信息都不应该进入普通日志。日志不是越完整越好,安全边界要先守住。
容易踩坑的点
- 日志没有模块名,多个页面混在一起看不出来源。
- 只记录成功,不记录前端校验失败,导致“请求没发出去”无法定位。
- 直接打印手机号、验证码、token,给测试包和线上排查留下风险。
- 每条日志格式不同,搜索时无法用固定关键词过滤。
- 只打
request_failed,没有错误码和 traceId。
优化建议
复杂业务可以再加一层轻量日志封装,把模块名、traceId、动作名统一拼好。注意不要过度封装成难用的工具类,日志如果写起来太费劲,最后大家还是会回到 console.info('success')。
线上日志要控制量。输入框 onChange 在真实项目里不建议每个字符都打日志,示例是为了演示链路。生产环境更适合记录提交、校验失败、请求结果这些关键节点。
日志要能串起一次操作
真正有用的日志,应该能从用户点击一路追到请求结果。固定模块名解决“日志归谁管”的问题,traceId 解决“这几条是不是同一次操作”的问题,动作名解决“流程走到哪一步”的问题。
示例里把登录点击、校验失败、请求开始和请求结果都打到同一条链路里。排查时只要搜索某个 trace,就能知道请求有没有发出、失败在前端校验还是服务端返回。
写在最后
生产环境还要控制日志边界。账号、手机号、验证码、token 都不应该明文打印;输入框变化这类高频日志也要降级或关闭。日志的目标是定位问题,不是把用户隐私和无效噪声都塞进控制台。
更多推荐



所有评论(0)