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 接口,六个级别各有 Xxx 和 Xxxf(带格式化)两版:
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-53:SetSafeLogEnabled(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-131 的 UnsafeInfo/UnsafeInfof:
func UnsafeInfof(format string, args ...interface{}) {
if safeLogEnabled {
pluginLog.Debugf("\n"+format, args...) // 安全模式:降到 Debug + 前置换行
} else {
pluginLog.Infof(format, args...) // 普通模式:正常 Info
}
}
"Unsafe"= 我知道这条可能含敏感信息
约定:打可能含敏感数据的日志(头/体/外部响应)用
UnsafeInfo*,打普通日志用 Info*。没开安全模式时 UnsafeInfo 和 Info 一样(方便开发调试);开了安全模式,UnsafeInfo 自动降级到 Debug——生产不采 Debug 就看不到,安全。这个"标记 + 开关"设计让"开发能看、生产不漏"两全。图注:同一行
UnsafeInfof,安全模式关时当 Info(开发可见)、开时降到 Debug+换行(生产不采集)——一个开关两种命运。📝 举个例子:同一行代码,两种环境两种结果
代码:
本地开发(未开 SafeLog):日志里正常看到
线上(开了 SafeLog、只采 Info+):它变成
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 17:Leader 选举与共享数据——多个 VM/实例里怎么选一个"领导"干独占的活(避免重复上报),以及 SharedData 的 CAS 机制(Redis 之外的另一种跨 VM 共享)。