gin默认日志不支持结构化、多目标写入和轮转,需通过中间件接管输出路径并为每个请求创建独立logrus.entry注入上下文字段;直接改defaultwriter无法复用hook、统一级别或保留logrus特性;多目标输出用io.multiwriter配合统一formatter;轮转必须用rotatelogs或lumberjack,参数应从配置读取。

gin 默认日志只输出到控制台,且不支持结构化字段、多目标写入或轮转。想用 logrus 替代或增强它,核心不是“引入包”,而是**接管日志输出路径 + 适配请求上下文**。直接调用 logrus.Info() 写业务日志没问题,但 HTTP 请求日志(method、status、latency 等)必须通过中间件捕获。
为什么不能只改 gin.DefaultWriter?
单纯设置 gin.DefaultWriter = logrus.StandardLogger().Out 只能让 gin 自带的 Logger() 中间件把日志塞进 logrus 的底层 writer,但:
• 它仍用 gin 原生格式(无 Fields、无结构化)
• 无法复用 logrus.Hook(比如发到 ES 或加 trace_id)
• 无法统一控制日志级别(gin.Logger() 固定 info 级别)
• 错误堆栈、caller 行号等 logrus 特性全失效
logrus.New() 实例必须绑定到中间件生命周期
每个请求需要独立的 logrus.Entry,才能注入 c.ClientIP()、c.Request.URL.Path 等动态字段。常见错误是全局复用一个 *logrus.Logger 实例然后直接 .Info() —— 这样所有请求日志共享同一 context,字段会串。
- 在中间件函数内调用
logrus.WithFields(),传入本次请求的c - 不要在
init()里提前logrus.SetOutput(),否则无法按请求隔离字段 - 若需文件输出,用
os.OpenFile(..., os.O_APPEND|os.O_CREATE|os.O_WRONLY),别用os.Stdout直接赋值给log.Out(会覆盖控制台)
如何让日志同时写文件和控制台?
用 io.MultiWriter 是最轻量方案,但要注意:它只是把字节流复制到多个 writer,不解决格式冲突。如果 logrus.TextFormatter 和 logrus.JSONFormatter 混用,输出会乱。
- 统一用
logrus.TextFormatter{FullTimestamp: true},避免时间戳格式不一致 - 文件 writer 用
os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - 控制台 writer 用
os.Stdout,不要用logrus.StandardLogger().Out(可能已被重定向) - 组合:
logrus.SetOutput(io.MultiWriter(file, os.Stdout))
轮转日志必须用 rotatelogs 或 lumberjack
os.OpenFile 本身不支持按大小/时间切分,硬编码轮转逻辑极易出错(并发写、残留句柄、权限失败)。生产环境必须用成熟轮转库:
-
github.com/lestrrat-go/file-rotatelogs:支持时间轮转(WithRotationTime(24*time.Hour))、软链接(WithLinkName("latest.log"))、保留策略(WithMaxAge(7*24*time.Hour)) -
gopkg.in/natefinch/lumberjack.v2:更简单,按大小轮转(MaxSize、MaxBackups),但无软链接 - 二者都不能直接传给
logrus.SetOutput(),要 wrap 成io.Writer(rotatelogs返回的就是io.Writer;lumberjack需&lumberjack.Logger{...})
logrus,而是让每条请求日志携带准确的上下文字段,并确保 writer 在高并发下不丢日志、不阻塞请求。轮转配置一旦写死就很难动态调整,建议把 rotation_time、max_age 等参数从配置文件读取,而不是硬编码在中间件里。











