kratos日志脱敏必须在日志输出前通过filter机制实现字段级精准替换,而非后端拼接或前端掩盖;需基于白名单(如phone、id_card)匹配字段名,调用健壮的掩码函数处理,确保error上下文等场景全覆盖。

脱敏必须在日志输出前拦截,不能靠后端拼接或前端掩盖
Kratos 的日志脱敏不是“把敏感字段藏起来”,而是防止它被写进日志文件或上报平台。一旦 logger.Info 或 logger.Error 调用时传入了含手机号、身份证的结构体,原始值就可能直接落盘。你不能指望在业务代码里手动删字段,也不能依赖前端 JS 做掩码——日志根本不会过前端。
正确做法是利用 Kratos 日志系统的 Filter 机制,在日志真正写入前扫描并替换敏感内容。这层过滤对所有日志调用生效,无需修改业务逻辑。
-
Filter是 Kratos 日志链路中可插拔的一环,接收log.Record,可读取其Fields并原地修改 - 不要在
Helper层做字符串拼接脱敏(如"user: " + maskPhone(u.Phone)),这样会丢失结构化日志能力,也漏掉 error stack 中的敏感上下文 - 避免用正则全局匹配日志消息文本(如
strings.ReplaceAll(msg, "138", "138****")),容易误伤非敏感字段(比如订单号、traceID 也以 138 开头)
用 Filter 实现字段级精准脱敏,而非模糊文本替换
Kratos 的 log.Record.Fields 是一个 []log.Field 切片,每个 Field 包含键名和值。脱敏应基于字段名做白名单匹配,而不是对整个日志字符串扫一遍。
例如只对明确叫 phone、id_card、email 的字段做处理,其他同名但语义不同的字段(如 order_id)完全跳过。
- 定义脱敏字段白名单:
[]string{"phone", "id_card", "email", "bank_card"} - 遍历
record.Fields,对每个field.Key检查是否在白名单中 - 若命中,调用对应函数(如
maskPhone(field.Value.String()))替换field.Value,注意保持原始类型(string、int64等) - 不建议在 Filter 中做复杂正则清洗(如去空格、括号),那属于业务层职责;Filter 只做“已知字段 → 已知掩码”的确定性替换
别把脱敏逻辑塞进 Valuer 或 Logger 实现里
Valuer 是用来注入全局上下文(如 trace_id、ip)的,它在每条日志生成时被调用一次,返回固定或动态值。它不接收当前日志的字段数据,因此无法判断哪条日志带了手机号——没法做条件脱敏。
Logger 是底层驱动接口(对接 zap、logrus 等),实现它只是把日志转给第三方库,没有字段解析能力。想在这里做脱敏,就得重写整个日志序列化流程,得不偿失。
- 唯一该写逻辑的地方是
Filter:它能看到完整Record,且执行时机恰在序列化之前 - 不要试图在
Helper上封装带脱敏的Infof方法(如SafeInfof("user %s", maskPhone(phone))),这会让团队成员忘记调用,导致漏脱敏 - 如果用了 Kratos 的
log.With链式传参(如log.With("phone", u.Phone).Info("login")),Filter 仍能捕获到phone字段——这是最自然的使用姿势
手机号脱敏函数要防误判,别用硬编码正则
常见错误是写个 regexp.MustCompile(`^1[3-9]\d{9}$`) 去匹配再替换,结果把带区号的 "+86 138 1234 5678" 或数据库里存的 "138-1234-5678" 全放过了,或者把 "1381234567890"(13 位)也当成手机号打码。
真正健壮的做法是:先清洗干扰字符,再按长度和纯数字校验,不满足条件就原样返回,绝不强行掩码。
<pre class="brush:php;toolbar:false;">func maskPhone(s string) string {
cleaned := regexp.MustCompile(`[\s\-\(\)\+]`).ReplaceAllString(s, "")
if len(cleaned) != 11 || !regexp.MustCompile(`^\d{11}$`).MatchString(cleaned) {
return s
}
return cleaned[:3] + "****" + cleaned[7:]
}
- 这个函数可以安全用在 Filter 里,也可以复用于 HTTP 响应层(如 Gin handler),保持策略一致
- 身份证脱敏同理:先判断长度是 15 还是 18,再按规则截取,不匹配就不动
- 注意:Filter 中调用该函数时,需确保
field.Value 确实是 <code>string类型,否则field.Value.String()可能返回意外内容(如 struct 的默认字符串表示)
db.QueryRowContext(ctx, sql, phone) 报错时,Kratos 默认会把 phone 作为 args 字段打进去——这个字段名未必叫 phone,可能是 arg0 或 args,需要在 Filter 里额外覆盖常见 key。











