Logrus JSON日志需严格遵循配置顺序:先SetOutput再SetFormatter,文件句柄须在main中打开并保持存活,时间格式与字段需显式设置,生产环境仍推荐Logrus而非slog。

Logrus 的 JSON 输出不是“设了格式就自动生效”的开关,而是依赖明确的调用顺序、文件句柄生命周期和 Go 版本兼容性——错一步,日志就静默丢失或 panic。
logrus.SetFormatter 必须在 SetOutput 之后调用
很多用户先 SetFormatter(&logrus.JSONFormatter{}) 再 SetOutput(f),结果日志空输出或 panic(尤其 v1.9+)。这是因为部分版本内部会缓存 formatter 状态,若输出目标未就绪,JSON 序列化逻辑可能跳过或出错。
- ✅ 正确顺序:先
SetOutput,再SetFormatter - ⚠️ 常见错误现象:控制台/文件无任何日志,或 panic 提示
nil pointer dereference(底层 entry.writer 为 nil) - ? 如果用
logrus.New()实例而非全局 logger,也要保证实例配置顺序一致
文件输出必须避免 init() 中 defer 关闭
bad file descriptor 错误几乎都源于在 init() 函数里打开文件并 defer f.Close()。Go 的 defer 在函数返回时立即执行,init() 执行完,文件就关了,后续所有 logrus.Info() 都写向已关闭的 fd。
- ✅ 正确做法:把
os.OpenFile和defer f.Close()放进main(),确保句柄存活到程序退出 - ⚠️ 权限建议用
0644(非0666),避免日志文件被组/其他用户意外修改 - ⚠️ 模式推荐
os.O_CREATE | os.O_APPEND | os.O_WRONLY,不加os.O_TRUNC,防止重启清空历史
JSON 时间格式和字段控制需显式配置
logrus.JSONFormatter{} 默认用浮点秒时间戳(如 "ts":1747941498.123),多数日志平台(ELK、Loki)更倾向 ISO8601 字符串("timestamp":"2026-05-22T19:18:18Z"),且默认不包含调用位置。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- ✅ 设置时间键名与格式:
TimestampFormat: "2006-01-02T15:04:05Z",TimeKey: "timestamp" - ✅ 添加文件/行号:
logrus.SetReportCaller(true),但注意它只对TextFormatter生效;JSON 场景需配合自定义CallerPrettyfier或改用slog/zap - ⚠️ 字段名大小写敏感:结构体字段要导出 + 加
json:"field_name"tag,否则 JSON 序列化为空
Go 1.22+ 可直接用 slog.JSONHandler,但 logrus 仍是更稳妥的选择
如果你刚升级到 Go 1.22,slog.JSONHandler 确实开箱可用,但 slog 缺少 Logrus 成熟的 Hook 机制(比如按级别分文件、写 Kafka)、默认不支持 WithFields 链式调用,且 slog.With() 返回新 logger 的语义容易引发泄漏。
- ✅ 生产环境仍推荐
logrus:Hook 可插拔、社区案例多、错误反馈明确(比如bad file descriptor直接暴露句柄问题) - ⚠️ 不要混用:别在同一个项目里既用
logrus又用slog做主日志,调试成本陡增 - ⚠️ 注意大小写:
github.com/sirupsen/logrus(sirupsen 小写),旧导入路径Sirupsen会导致构建失败
最易被忽略的其实是日志初始化时机——它必须早于任何 logrus.Info() 调用,且不能依赖包级变量隐式初始化。把日志 setup 逻辑收进一个 setupLogger() 函数,在 main() 开头显式调用,比靠 init() 更可控、更可测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










