Day 16 / 共 20 天 · 第 4 周 高级特性与实战

日志与安全日志

第 4 周进入高级特性。第 3 周(W3)你学会了处理请求、调外部服务;从今天起是"让插件更专业、更安全、更能上生产"的进阶能力。今天从最基础也最容易忽视的日志开始——尤其是 wasm-go 一个很用心的设计:安全日志,防止把用户的敏感信息(密码、Token、body)打进日志被采集泄露。

📍 你在整门课的位置(wasm-go 20 天 · 第 4 周 高级特性与实战)
外部调用 W3 日志与安全日志 D16 Leader 选举 D17 Token 解析 D18 性能测试 D19
L01

Log 接口

💡 本质:六个级别 = 一个"重要性音量旋钮" 日志级别就像收音机音量:Trace/Debug 是"喃喃自语"(开发时才听),Info 是"正常播报",Warn 是"提醒音",Error/Critical 是"警报"。生产环境把旋钮拧到 Info 以上——低于它的(Debug)根本不采集。下面的"安全日志"正是利用这个旋钮:把敏感信息降级到 Debug 这个"生产听不见的频段",既不影响开发调试,又不会泄露到生产日志库。

log/log.go:17-30 定义 Log 接口,六个级别各有 XxxXxxf(带格式化)两版:

type Log interface {
    Trace(msg) / Tracef(format, args...)
    Debug / Debugf
    Info / Infof
    Warn / Warnf
    Error / Errorf
    Critical / Criticalf
}
读法:SetPluginLog:39-41)设置全局 logger,默认是 DefaultLog(底层调 proxywasm.LogXxx)。你也能用 WithLogger 选项(Day 05)换成自定义实现。
L02

全局函数

log/log.go:55-105 提供包级全局函数 log.Info(msg)/log.Infof(fmt, args...) 等——这就是 Day 05 说的"新 API 不带 log 参数"的原因:

import "github.com/higress-group/wasm-go/pkg/log"
log.Infof("处理请求 path=%s", path)   // 直接用,不用从回调参数拿 log
为什么全局函数更方便? 旧 API 每个回调都带 log log.Log 参数,你得一路传递。新 API 把 logger 存成包级全局变量,你在任何地方 log.Infof(...) 就能打日志——不用传参。底层它转发给 SetPluginLog 设的那个 logger。代码更干净。这也是为什么新版钩子签名去掉了 log 参数。
L03

六个日志级别

Trace < Debug < Info < Warn < Error < Critical。用途约定:

  • Trace/Debug:详细调试信息,生产通常关闭。
  • Info:正常流程的关键节点。
  • Warn:异常但可恢复(如配置为空用默认)。
  • Error/Critical:错误/严重错误(插件启动失败等)。
读法:级别是"重要性 + 采集策略"的信号——生产环境常只采 Info 及以上。这个细节和"安全日志"配合(下面):把敏感信息降级到 Debug,生产不采集就等于不泄露。
L04

SafeLog 是什么

log/log.go:46-53SetSafeLogEnabled(enabled) / IsSafeLogEnabled()。通过 Day 05 的 EnableSafeLog 选项开启。

🚨 错误驱动:一条随手的日志如何变成安全事故 开发时你为排查问题写了 log.Infof("req headers=%v", headers),headers 里有 Authorization: Bearer sk-真实密钥上线后这行照打 Info → 被日志采集系统收走 → 进了全公司可搜索的日志平台 → 任何有日志权限的人搜 "Bearer" 就能捞到用户密钥。这就是真实发生过的泄露路径。SafeLog + UnsafeInfo 就是为堵死它而生。
敏感信息泄露是真实风险 插件常要打日志排查——但请求头里有 Authorization token、body 里有用户数据、外部调用响应含隐私。如果这些无脑打进 Info 日志,被日志采集系统收走、进了可搜索的日志库,就是数据泄露!SafeLog 就是应对:开启后,用 UnsafeInfo/UnsafeInfof 打的"可能含敏感信息"的日志会被特殊处理(降级/抑制)。回想 Day 14:HttpCall 打请求/响应用的正是 log.UnsafeInfof。有个专门的例子 examples/safe-log-http-call
L05

Unsafe 系列

log/log.go:107-131UnsafeInfo/UnsafeInfof

func UnsafeInfof(format string, args ...interface{}) {
    if safeLogEnabled {
        pluginLog.Debugf("\n"+format, args...)   // 安全模式:降到 Debug + 前置换行
    } else {
        pluginLog.Infof(format, args...)          // 普通模式:正常 Info
    }
}
"Unsafe"= 我知道这条可能含敏感信息 约定:打可能含敏感数据的日志(头/体/外部响应)用 UnsafeInfo*,打普通日志用 Info*没开安全模式时 UnsafeInfoInfo 一样(方便开发调试);开了安全模式,UnsafeInfo 自动降级到 Debug——生产不采 Debug 就看不到,安全。这个"标记 + 开关"设计让"开发能看、生产不漏"两全。
UnsafeInfof(token)"这条可能含敏感信息" SafeLog? 当 Info 打印(开发看得见)方便本地调试 降级到 Debug + 前置换行 "\n"生产不采 Debug → 看不见 → 不泄露
图注:同一行 UnsafeInfof,安全模式关时当 Info(开发可见)、开时降到 Debug+换行(生产不采集)——一个开关两种命运。
📝 举个例子:同一行代码,两种环境两种结果 代码:log.UnsafeInfof("resp=%s", body),其中 body = {"api_key":"sk-xxx"}
本地开发(未开 SafeLog):日志里正常看到 Info resp={"api_key":"sk-xxx"},方便排查。
线上(开了 SafeLog、只采 Info+):它变成 Debug \nresp=...,被采集层过滤掉 → 日志库里查不到这条 → api_key 不外泄。
L06

降级技巧:前置换行

注意 UnsafeInfof 在安全模式下不只降级到 Debug,还加了 "\n" 前缀。注释(:120-123)解释了原因:

为什么加个换行? 很多日志采集器是"按行"采集的。给敏感日志前面加个换行,让它和正常日志错行——即使某些 Debug 被采集,采集器也难把"完整的敏感信息"抓在一条记录里。这是"纵深防御"的小技巧:降级到 Debug 是第一道防线,前置换行是第二道(万一 Debug 也被采)。可见作者对日志泄露考虑得多细致——这正是生产级 SDK 和玩具的区别。
L07

自定义日志复习

别混淆两类"日志":

  • 插件调试日志(本课):log.Infof——给开发/运维看的诊断信息,进网关日志。
  • 访问日志属性(Day 12):WriteUserAttributeToLog——把业务属性写进请求的访问日志 custom_log 字段,用于可观测/审计。
⚠️ 常见误解:小白以为"开了 SafeLog 就绝对安全,随便打"。其实 SafeLog 只对用 UnsafeInfo* 打的日志生效;你要是图省事用普通 Info 打了 token,它照样进生产日志。安全靠"你主动标记敏感行 + 开关"两者配合,不是万能开关。
读法:前者是"我这段代码干了啥",后者是"这条请求的业务特征"。两者都可能含敏感信息,都要注意脱敏——访问日志属性由控制面/运维决定采不采,调试日志由 SafeLog 控制。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 为什么新 API 能用全局 log.Infof 不带 log 参数?
  • SafeLog 解决什么真实风险?何时用 UnsafeInfo
  • 安全模式下 UnsafeInfo 怎么处理(降级+换行)?为什么加换行?
  • 调试日志和访问日志属性的区别?

✋ 动手

cd /Users/bitmart/work/codes/github/higress-group/wasm-go
sed -n '46,131p' pkg/log/log.go
cat examples/safe-log-http-call/main.go | head -60
明天预告 · Day 17Leader 选举与共享数据——多个 VM/实例里怎么选一个"领导"干独占的活(避免重复上报),以及 SharedData 的 CAS 机制(Redis 之外的另一种跨 VM 共享)。
← Day 15 Day 17 · Leader 选举 →