Gin 默认日志不能直接替换为 Logrus,因为 logrus.Logger 不实现 io.Writer 接口,强行赋值会 panic 或丢日志;必须用适配器包装使其满足 io.Writer 合约,并手动映射状态码到 Logrus 级别(200–399→Info、400–499→Warn、500+→Error),同时分离 access log 与 error log 输出目标以避免竞态。

为什么 Gin 默认日志不能直接替换为 Logrus?
Gin 的 gin.DefaultWriter 和 gin.DefaultErrorWriter 是底层 io.Writer 接口实例,Logrus 的 logrus.Logger 本身不实现该接口;强行赋值会导致运行时 panic 或日志丢失。必须用适配器包装 Logrus 实例,使其满足 io.Writer 合约。
常见错误现象:cannot use logrus.New() (type *logrus.Logger) as type io.Writer;或看似生效,但 ctx.Error()、重定向日志等路径完全静默。
- 不要直接把
logrus.New()赋给gin.DefaultWriter - 必须通过
io.MultiWriter或自定义Write([]byte)方法桥接 - Logrus 的
Level和 Gin 的 debug/warn/error 日志语义不一一对应,需手动映射
如何让 Gin 请求日志(access log)走 Logrus 并按 level 分离?
Gin 的 gin.LoggerWithConfig 中间件输出的是访问日志,它只写入 gin.ResponseWriter 包装的 writer,不经过 Logrus。要让它进 Logrus,得自己实现一个中间件,捕获状态码和耗时后调用 logrus.WithFields() 手动打点。
关键点在于:别依赖 gin.Logger(),改用 gin.LoggerWithConfig(gin.LoggerConfig{Output: ...}) 配合自定义 writer,且该 writer 必须把不同状态码映射到对应 Logrus level(如 4xx → Warn,5xx → Error)。
- 200–399 →
logrus.Info() - 400–499 →
logrus.Warn()(不是 Error,避免误报) - 500+ →
logrus.Error() - 务必在
c.Next()后读取c.Writer.Status(),不能在之前 - 示例片段:
log := logrus.WithFields(logrus.Fields{"path": c.Request.URL.Path, "method": c.Request.Method}) if c.Writer.Status() >= 500 { log.Error("request failed") } else if c.Writer.Status() >= 400 { log.Warn("bad request") } else { log.Info("request completed") }
如何让 Gin 内部错误(如 panic recovery、binding error)也进 Logrus?
Gin 的 gin.Recovery() 和默认 binding 错误处理都写入 gin.DefaultErrorWriter。要接管这部分,需重写 gin.RecoveryWithWriter,并在其中调用 logrus.Error();同时对 c.ShouldBind() 类错误,主动捕获 error 并用 logrus.Warn() 记录(binding 失败通常不是系统级故障)。
- 禁用默认 recovery:
router.Use(gin.RecoveryWithWriter(io.Discard)) - 自定义 recovery 中间件中,用
logrus.WithField("stack", string(stack)).Error("panic recovered") - binding 场景下,避免把
err.Error()直接当用户提示返回,应区分内部日志和响应体 - 注意:Logrus 的
ExitFunc不应设为os.Exit,否则 panic recovery 后服务直接退出
Logrus 输出到文件 + 控制台时,Gin 日志为何重复或错乱?
当用 io.MultiWriter(os.Stdout, file) 给 Gin 设置 Output,再另起 goroutine 往同一文件写 Logrus,容易因无锁写入导致日志行断裂(比如一行里混着 access log 和 error log)。根本原因是多个 writer 并发写同一 fd,而 Logrus 的 FileHook 或 rotatelogs 本身已带同步机制,不该再被 MultiWriter 包裹。
- 正确做法:只让 Gin 的 access log 走控制台(
os.Stdout),让所有结构化日志(含 Gin 错误、业务 log)统一走 Logrus 的rotatelogsHook - Logrus 添加 hook 示例:
hook, _ := rotatelogs.New("/var/log/app/access.%Y%m%d.log") logrus.AddHook(&logrus_hook.Hook{Writer: hook}) - 确保
gin.DefaultWriter和gin.DefaultErrorWriter指向不同目标,或至少不共用同一个文件句柄 - Windows 下注意路径分隔符,
/var/log/...在 Windows 会静默失败


















