3、HiLog 高级特性:私有参数格式化(%{private}s),日志流控与丢帧处理
好,咱们继续往下聊。前面两章我们把 HiLog 的基本用法和日志分级讲透了。这一章,我要带你看看 HiLog 真正「高级」的地方——私有参数格式化、日志流控和丢帧处理。
说实话,这三个特性平时你可能用不上。但一旦遇到线上问题排查、性能压测、或者安全审计,它们就是救命稻草。我自己就在项目里踩过坑,今天一并讲清楚。
3.1 私有参数格式化:%{private}s 到底在保护什么?
先问一个问题:你写的日志里,有没有出现过用户手机号、设备 IMEI、甚至明文密码?
嗯,我知道很多新手会直接这么写:
HiLog.info(LABEL, "用户登录成功,手机号:%s", phoneNumber);
这在开发阶段没问题。但一旦应用发布到生产环境,日志文件被第三方拿到……后果你懂的。
HiLog 提供了一个非常优雅的解决方案——私有参数格式化。说白了,就是给敏感数据加个「马赛克」。
3.1.1 三种格式化类型
| 类型 | 写法 | 效果 |
|---|---|---|
| 公开 | %{public}s |
明文输出,和普通 %s 一样 |
| 私有 | %{private}s |
输出为 <private> 占位符 |
| 敏感 | %{sensitive}s |
输出为 <sensitive> 占位符 |
举个例子你就明白了:
HiLog.info(LABEL, "公开信息:%{public}s", "Hello");
HiLog.info(LABEL, "私有信息:%{private}s", "13800138000");
HiLog.info(LABEL, "敏感信息:%{sensitive}s", "password123");
输出结果:
公开信息:Hello
私有信息:<private>
敏感信息:<sensitive>
你看,手机号和密码直接被屏蔽了。这在合规审计时特别有用。
3.1.2 什么时候用 %{public}s?
你可能会问:那 %{public}s 还有存在的必要吗?
有。比如打印设备型号、系统版本、或者你自定义的调试开关状态——这些不涉及用户隐私,用 %{public}s 反而方便排查。
HiLog.info(LABEL, "设备型号:%{public}s,系统版本:%{public}s", deviceModel, osVersion);
这样,线上日志里你能看到设备信息,但看不到用户手机号。两全其美。
3.2 日志流控:为什么日志会突然消失?
你有没有遇到过这种情况:程序跑着跑着,日志突然不打了?
别慌,大概率是触发了 HiLog 的流控机制。
HiLog 底层有一个环形缓冲区,大小有限。如果短时间内日志量太大,缓冲区满了,后面的日志就会被丢弃。这是为了保护系统性能,防止日志把 CPU 和存储打满。
3.2.1 流控触发条件
我记得官方文档里提过几个关键阈值:
- 单进程每秒日志条数超过 1000 条,触发流控
- 单条日志长度超过 1024 字节,会被截断
- 全局日志缓冲区写满,直接丢帧
说白了,HiLog 不是给你无限制刷屏用的。你想想看,如果某个循环里每秒打一万条日志,系统还怎么干活?
3.2.2 如何避免流控?
我个人的经验是:高频日志一定要降频。
比如你在一个定时器里每秒打一次日志,可以改成每 10 秒打一次:
static int logCounter = 0;
if (++logCounter % 10 == 0) {
HiLog.info(LABEL, "当前状态:运行中,计数:%{public}d", counter);
}
或者用 HiLog 自带的 HiLog.isLoggable() 先判断一下:
if (HiLog.isLoggable(LABEL, HiLog.INFO)) {
HiLog.info(LABEL, "这条日志只在 INFO 级别开启时才会打印");
}
这样既能保留关键信息,又不会刷爆缓冲区。
3.3 丢帧处理:日志丢了怎么办?
流控触发后,日志就丢了。这其实是个设计取舍——宁可丢日志,也不能丢性能。
但问题来了:如果丢的日志恰好是崩溃前的关键信息,你怎么排查?
3.3.1 丢帧的典型场景
- 崩溃前瞬间:应用 crash 前疯狂打日志,缓冲区满了,最后几条关键日志被丢弃
- 启动阶段:应用启动时大量初始化日志,容易触发流控
- 高并发场景:多线程同时写日志,竞争激烈导致丢帧
3.3.2 如何检测丢帧?
HiLog 提供了一个 API 可以查询丢帧情况:
int droppedCount = HiLog.getDroppedCount();
if (droppedCount > 0) {
HiLog.warn(LABEL, "检测到日志丢帧,已丢弃 %{public}d 条", droppedCount);
}
我建议你在应用启动后、或者关键业务流程结束后,主动检查一次。如果丢帧严重,说明你的日志策略需要优化。
3.3.3 丢帧后的补救措施
说实话,丢帧了就是丢了,没法恢复。但我们可以做几件事来降低影响:
- 关键日志用 HiLog.FATAL:FATAL 级别的日志优先级最高,几乎不会被流控丢弃
- 日志分级:DEBUG 日志可以丢,但 ERROR 和 FATAL 必须保留
- 本地文件备份:对于特别重要的日志,可以同时写入本地文件(但要注意性能)
// 关键信息双重保障
HiLog.fatal(LABEL, "严重错误:内存不足,%{private}s", errorDetail);
// 同时写入本地文件
saveToLocalFile("crash.log", errorDetail);
3.4 实战:一个完整的日志配置示例
最后,我给你一个我在项目中实际用过的日志配置模板。你可以直接拿来用:
// 日志标签
static constexpr HiLogLabel LABEL = {LOG_CORE, 0, "MyApp"};
// 初始化时检查丢帧
void initLogging() {
int dropped = HiLog.getDroppedCount();
if (dropped > 100) {
HiLog.warn(LABEL, "启动时丢帧严重:%{public}d 条", dropped);
// 考虑调整日志级别或降频
}
}
// 安全打印用户信息
void logUserLogin(const std::string& phone, const std::string& deviceId) {
HiLog.info(LABEL, "用户登录 - 手机号:%{private}s,设备:%{public}s",
phone.c_str(), deviceId.c_str());
}
// 高频日志降频
void logHeartbeat(int counter) {
static int beatCount = 0;
if (++beatCount % 100 == 0) {
HiLog.info(LABEL, "心跳正常,计数:%{public}d", counter);
}
}
这个模板里,我做了三件事:
- 启动时检查丢帧,提前预警
- 用户信息用 %{private}s 保护
- 心跳日志每 100 次才打一次,避免流控
嗯,基本上够用了。你如果遇到更复杂的场景,比如多进程日志隔离、或者日志加密,那又是另一个话题了。咱们后面章节再聊。
好,这一章就到这里。记住:日志不是越多越好,而是越精越好。用好私有参数和流控,你的日志系统才算真正「高级」了。