Go 标准库 log 不支持 Level 过滤,需通过自定义 io.Writer 的 Write 方法实现轻量级控制;slog 原生支持 Level 但需显式配置 HandlerOptions.Level;生产推荐 zerolog 或 logrus 等成熟库。

Go 标准库 log 本身不支持 Level 过滤
标准库 log 只提供 Print、Printf、Fatal 等方法,但没有内置的 Debug / Info / Warn 分级机制,更不支持运行时按 Level 动态过滤。直接调用 log.SetFlags 或封装函数无法改变这一事实——它只是输出格式控制,不是过滤开关。
所以,如果你看到有人用 log.Printf("[DEBUG] %s", msg) 再靠字符串匹配来“过滤”,那属于后期文本筛检,既低效又不可靠(比如日志内容里恰好含 [DEBUG] 就会误判)。
用 log.New + 自定义 io.Writer 实现轻量级 Level 控制
最简可行方案是:把 Level 判断逻辑塞进一个自定义 io.Writer 的 Write 方法里,再传给 log.New。这样所有写入都会先过一遍 Level 检查。
示例结构:
立即学习“go语言免费学习笔记(深入)”;
type LevelWriter struct {
level int
out io.Writer
}
func (w *LevelWriter) Write(p []byte) (n int, err error) {
if w.level <= LogLevelDebug { // 假设 LogLevelDebug 是最低等级
return w.out.Write(p)
}
return len(p), nil // 丢弃,不写入
}
关键点:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- Level 比较必须在
Write中完成,不能只在包装函数里判断——因为log内部会调用fmt.Fprint等,已格式化为字节流 - 不要在
Write里做字符串解析(如找"[DEBUG]"),性能差且易错 - 多个 logger(
debugLog,infoLog)可共用同一个LevelWriter实例,只需初始化时传不同level
为什么不用 golang.org/x/exp/slog?
slog 是 Go 1.21+ 官方推荐的结构化日志包,原生支持 Level(slog.LevelDebug)、属性、Handler 过滤等。但它默认 Handler(如 slog.NewTextHandler)仍需显式设置 Level 选项才能生效:
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug, // 必须设这里,否则默认只输出 Info 及以上
})
logger := slog.New(handler)
常见坑:
- 忘记传
HandlerOptions.Level,结果slog.Debug完全不输出,还以为 API 不工作 -
slog的 Level 是slog.Level类型,不是整数,不能直接和int比较 - 如果项目还混用标准
log,slog不会接管它——两者日志流完全独立
生产环境建议:用成熟第三方库(如 zerolog 或 logrus)
自己实现 Level 过滤容易漏掉边界情况:并发写入竞争、Level 动态调整(如 SIGUSR1 重载配置)、采样、Hook 扩展等。而 zerolog 这类库已把 Level 判断下沉到 Event 构建阶段,避免无谓格式化:
log := zerolog.New(os.Stdout).With().Timestamp().Logger()
log = log.Level(zerolog.DebugLevel) // 设定最低输出等级
log.Debug().Str("key", "val").Msg("hello") // DebugLevel 不够则整条 Event 被跳过
注意点:
-
zerolog默认不打印 Level 字符串,需手动加.Caller()或用zerolog.ConsoleWriter -
logrus的 Level 是可变的(logrus.SetLevel(logrus.DebugLevel)),但它的 Hook 和 Formatter 是同步执行的,高并发下可能成瓶颈 - 所有第三方库都依赖你在初始化时正确设置全局或实例 Level——没设就是默认
Info,这点极易忽略
Level 过滤真正的复杂点不在“怎么写个 if”,而在“什么时候判断”和“判断后是否还能被其他组件干预”。越早拦截(如 zerolog 在 Event 创建时),开销越小;越晚拦截(如在 io.Writer.Write),格式化成本已发生。

















