go标准库log包不支持日志级别,所谓“配置级别”是能力缺失;zap.newdevelopment()与newproduction()行为差异极大;logrus.setlevel()无锁保护,并发调用会导致级别错乱。

Go 标准库的 log 包不支持日志级别(Debug/Info/Warn/Error),所谓“配置级别”本质是能力缺失,不是参数没调对——别在 log.SetFlags 或 log.SetPrefix 上浪费时间试图模拟。
为什么 log.SetFlags + "[DEBUG]" 前缀不是真级别控制
常见错误现象:用 log.Printf("[DEBUG] %s", msg),上线后手动删或加 // 注释;或靠 grep 过滤,但日志文件里混着 INFO/WARN,grep 一跑全崩。
-
log.SetFlags只影响前缀内容(时间、文件行号等),不影响输出开关 -
log.SetPrefix是纯字符串拼接,无法做条件判断 - 所有日志都走同一
io.Writer,没有拦截/丢弃机制 - 多 goroutine 并发写时,
log.SetOutput切换目标可能引发竞态(比如切到文件又切回 stdout)
zap.NewDevelopment() 和 zap.NewProduction() 别混用
两者行为差异极大,不是“开发开 Debug、生产关 Debug”那么简单——连字段名大小写、是否刷盘、JSON 键名格式都不同。
-
zap.NewDevelopment()默认启用DebugLevel、彩色输出、CallerSkip、完整调用栈,适合本地调试 -
zap.NewProduction()默认只输出InfoLevel及以上,EncoderConfig.EncodeLevel用的是zapcore.CapitalLevelEncoder,输出"LEVEL":"INFO"而非小写"level":"info" - 若用
zapcore.NewCore自定义 encoder,必须显式传zapcore.AddSync(os.Stdout),否则日志直接消失(不会报错) - 临时提级某个 handler 的日志,用
logger.WithOptions(zap.IncreaseLevel(zap.DebugLevel)),别动全局 logger
logrus.SetLevel() 在运行时调用很危险
logrus 已停止维护,但遗留项目还在用。它的 SetLevel 直接写全局变量 logrus.level,无锁保护。
- 多个 goroutine 同时调
logrus.SetLevel(logrus.DebugLevel)和logrus.SetLevel(logrus.WarnLevel),最终级别可能错乱 - 上线后突然涌出大量 Debug 日志,不是配置漏了,是并发改坏了
- 正确做法:启动时一次设好,例如
logrus.SetLevel(logrus.LevelFromString(os.Getenv("LOG_LEVEL"))) - 真需运行时切换,自己包一层
sync.RWMutex,并统一用logrus.StandardLogger().SetLevel
标准库 log 如何勉强分级(仅限无法引入第三方时)
如果项目强约束不能加依赖,只能靠构建 tag + 条件编译硬切,而不是 runtime 判断。
- 写三个独立 logger 实例:
infoLog := log.New(...)、warnLog := log.New(...)、errLog := log.New(...) - 在
main.go里用//go:build debug控制是否调infoLog.Println,发布时用go build -tags=prod - 不要在
init()里偷偷 wraplog.Printf加 if 判断——它会污染所有 logger 输出,且和log.SetOutput冲突 - 文件输出务必用
os.O_CREATE | os.O_WRONLY | os.O_APPEND模式打开,否则每次启动清空日志
真正麻烦的从来不是“怎么写日志”,而是“怎么让日志在不同环境里既可读、又可控、还不拖慢服务”。zap 的 NewProduction() 默认同步刷盘,高并发下容易卡住主线程;logrus 的 level 变量裸写,看着简单实则埋雷。这些细节不提前踩过,上线后查日志反而比修 bug 还费劲。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











