根本原因是多个模块共享全局 logger 实例并反复调用 setoutput/setlevel/setflags 等方法覆盖彼此配置;解法是每个模块使用 log.new() 或 logrus.new() 创建独立实例并初始化专属输出、级别和格式。

log.SetOutput() 被多个模块反复覆盖,导致日志全写进同一个文件
这是最典型的“日志级别冲突”假象——实际不是级别设置冲突,而是多个模块(比如 A 和 B)都调用了 log.SetOutput(),后执行的会把前一个的输出目标彻底替换掉。最终所有 log.Println() 都流向最后一个设置的目标,比如只看到 service-b.log 有内容,service-a.log 始终为空。
- 根本原因:标准库
log包的默认 logger 是全局单例,log.SetOutput()修改的是包级变量,非线程安全、不可隔离 - 典型场景:你引入了两个第三方 SDK,它们各自在
init()里调用log.SetOutput(os.Stderr)或log.SetOutput(file) - 验证方法:在 main 启动后立刻打印
log.Writer()的地址,再让两个模块初始化后再次打印,地址相同说明被覆盖 - 解法唯一:禁止任何模块直接操作全局 logger;所有模块改用
log.New()创建私有实例,例如logger := log.New(os.Stdout, "[module-a] ", log.LstdFlags)
logrus 默认 logger 被多处修改,INFO/WARN 混在一起且无法单独控制
logrus.StandardLogger() 也是全局单例。如果模块 A 执行了 logrus.SetLevel(logrus.WarnLevel),模块 B 又执行 logrus.SetLevel(logrus.DebugLevel),最终生效的是后者,A 的日志全被屏蔽——这不是“级别冲突”,是全局状态被踩踏。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 错误做法:各模块都调用
logrus.SetLevel()或logrus.SetOutput() - 正确做法:每个模块持有自己的
*logrus.Logger实例,通过logrus.New()构造,并独立设置.SetLevel()和.SetOutput() - 注意:不要用
logrus.WithField()替代实例隔离——它不改变输出目标或级别,只是加字段 - 示例:
modALogger := logrus.New(); modALogger.SetLevel(logrus.InfoLevel); modALogger.SetOutput(aFile)
不同模块日志时间戳/格式不一致,终端输出错行、难以 grep
当模块 A 用 log.SetFlags(log.Ldate | log.Ltime),模块 B 用 log.SetFlags(log.LstdFlags),而它们又共用全局 logger,最终格式以最后一次设置为准。更糟的是,如果某个模块没设 Lshortfile,但另一个设了,你可能在一行日志里看到文件名,在另一行看不到——不是 bug,是全局 flag 被覆盖。
- 后果:日志行长度不固定,配合
tail -f或 ELK 解析时字段错位 - 关键点:flag 控制的是输出前缀行为,和 logger 实例绑定,不是 per-call 参数
- 解法:每个
log.New()或logrus.New()实例初始化时就固定好 flags / formatter,不再动态改 - 建议统一用结构化格式:logrus 推荐
logrus.JSONFormatter,zap 直接用zap.NewProductionConfig(),避免手工拼接字符串
容器中部分模块日志消失,怀疑是级别过滤但实际是 os.Stdout 缓冲问题
你以为是模块 A 设置了 logrus.SetLevel(logrus.ErrorLevel) 导致 INFO 日志被丢,但 docker logs 里连 ERROR 都看不到——大概率是 os.Stdout 缓冲未刷新,尤其在容器静默启动时。
- 现象:本地运行正常,容器里日志延迟数秒甚至完全不出现
- 原因:Go 对
os.Stdout默认启用全缓冲(非行缓冲),无换行符或未显式 flush 时内容卡在内存 - 验证:在 main 开头加
fmt.Println("test"); os.Stdout.Sync(),看 test 是否立即出现在docker logs - 修复:启动时强制行缓冲,
os.Stdout = bufio.NewWriterSize(os.Stdout, 1),并在 defer 中os.Stdout.(*bufio.Writer).Flush() - 注意:logrus/zap 默认写
os.Stdout,同样受此影响;不要依赖log.SetOutput(os.Stdout)就认为能实时输出
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










