必须在main()最开头、任何ctrl.Log调用前用ctrl.SetLogger注入自定义logr.Logger实例,推荐zapr.NewLogger包装zap.Logger以支持JSON输出和结构化字段;WithName仅添加字段不控制级别,per-controller级别需通过zap.AtomicLevel动态设置。

如何用 logr.Logger 替换默认日志输出格式
Kubernetes Operator 默认用 ctrl.Log(底层是 logr.Logger),它本身不直接支持 JSON 或带 traceID 的格式,必须替换底层实现。关键不是“配置 logger”,而是“注入自定义 logr.Logger 实例”。
常见错误是试图在 main.go 里调用 ctrl.SetLogger 前就打日志——此时日志已走默认实现,后续替换无效。
- 在
main()最开头、任何ctrl.Log.Info调用前,用ctrl.SetLogger注入你自己的logr.Logger - 推荐用
zapr.NewLogger包装zap.Logger,它支持结构化字段、JSON 输出、采样和 level 控制 - 别用
log.Printf或标准库log,它们绕过logr生态,无法被 controller-runtime 统一管理
示例:
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"sigs.k8s.io/controller-runtime/pkg/log/zapr"
)
func main() {
// 必须在 SetupSignalHandler 之前
ctrl.SetLogger(zapr.NewLogger(zap.Must(zap.NewDevelopment())))
// ...
}
怎么动态控制不同 controller 的日志级别
logr.Logger 本身没有“按模块设 level”的内置机制;level 是底层实现(如 zap)决定的,而 controller-runtime 只透传 Info/Error 等方法。所以真正生效的是 zap 的 LevelEnabler 配置,不是 ctrl.Log.WithName("xxx")。
立即学习“go语言免费学习笔记(深入)”;
-
WithName只添加logger字段(如"controller": "pod-scaler"),不影响 level - 若需 per-controller level,得在 zap 初始化时用
zap.WrapCore+ 自定义LevelEnabler,根据logger字段名过滤 - 更实际的做法:用 zap 的
AtomicLevel,运行时调用SetLevel全局调整,再配合WithName字段做日志筛选
例如临时提高某个 controller 的 debug 日志:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
atomicLevel := zap.NewAtomicLevel() atomicLevel.SetLevel(zap.DebugLevel) // 全局设为 Debug ctrl.SetLogger(zapr.NewLogger(zap.Must(zap.NewDevelopment(zap.Level(atomicLevel))))) // 后续可通过 HTTP handler 动态修改 atomicLevel.SetLevel(...)
为什么 ctrl.Log.Info 不输出,或 level 不生效
最常见原因是 zap core 的 LevelEnabler 和 controller-runtime 的日志方法不匹配。比如你调用 ctrl.Log.Info("msg", "key", "val"),但 zap core 的 Enabled 方法返回 false,整条日志就被静默丢弃。
- 检查 zap 初始化是否用了
zap.NewProduction()—— 它默认只输出InfoLevel及以上,但Info方法实际映射到InfoLevel,而Debug方法才映射到DebugLevel - controller-runtime 的
Info≠ 标准库的info,它对应logr.Info,在 zap 中默认映射为InfoLevel,不是DebugLevel - 如果你希望
Info方法也能输出 debug 内容,必须显式把 zap level 设为DebugLevel,且确保Enabled(zapcore.DebugLevel)返回 true
验证方式:在 main() 中加一行测试日志:
ctrl.Log.Info("startup-check", "level", "should-appear")
// 如果没输出,说明 zap core 的 Enabled 拒绝了 InfoLevel
要不要在 Reconcile 中用 req.NamespacedName.String() 打日志
要,但别拼接进 message 字符串,应该作为结构化字段传入。否则日志无法被采集系统(如 Loki、ELK)正确解析,也丧失字段过滤能力。
- 错误写法:
log.Info("reconciling pod " + req.NamespacedName.String()) - 正确写法:
log.Info("reconciling pod", "name", req.NamespacedName.String()) - 额外建议:加一个固定字段如
"reconciler",方便 Grafana 查询某 controller 的所有日志 - 避免在日志中打印整个
obj(如pod.DeepCopy()),可能触发大量反射和内存分配,尤其在高频 reconcile 场景下
字段命名保持小写+短横线风格(如 "pod-name"),与 k8s API 一致,便于后续和 event、audit 日志对齐。
真正麻烦的是跨 goroutine 日志上下文传递——logr.Logger 不自带 context 绑定,如果 reconcile 中启了子 goroutine,得手动把 logger 传进去,不然会丢失 WithName 和字段。

















