Logrus必须在init或main开头用SetOutput()全局配置输出,否则日志漏写;rotatelogs需协同设置切分周期、软链接和清理策略;禁用gin.Logger()避免重复日志。

Logrus 不能靠中间件“临时替换”输出,必须在程序启动早期用 logrus.SetOutput() 全局绑定;否则日志会漏写或写到 os.Stderr。
logrus.SetOutput() 必须在 init 或 main 开头调用
Logrus 的全局 logger 在首次调用 logrus.Info() 等方法时,就已绑定默认输出(os.Stderr)。之后再调用 logrus.SetOutput() 不会影响已初始化的 handler。
- 错误做法:在 Gin 中间件里 new 一个
logrus.Logger实例,再调用它的SetOutput()—— 这只是局部实例,和 Gin 默认 logger 无关 - 错误做法:在
c.Next()之后、甚至路由处理函数里才配置logrus.SetOutput() - 正确做法:在
init()或main()最开头完成全部配置,包括 writer 创建、formatter 设置、level 设定
rotatelogs.New() 配置时间轮转的关键参数
用 rotatelogs 实现按时间切日志,核心是三个参数协同:切分周期、软链接、清理策略。设错一个就容易磁盘爆满或找不到最新日志。
-
WithRotationTime(24 * time.Hour):每 24 小时新建一个文件,文件名含%Y%m%d;设成1 * time.Minute会导致每分钟生成一个文件,不配清理必崩 -
WithLinkName("logs/latest.log"):创建软链接指向当前最新日志,但要求logs/目录已存在且可写,否则 writer 初始化失败 -
WithMaxAge(7 * 24 * time.Hour)和WithRotationCount(7)二选一;混用会导致行为不可预期 —— 前者按保留天数删旧文件,后者按文件个数删
别和 gin.Logger() 同时启用
gin.Logger() 是 Gin 自带中间件,它用自己的一套 io.Writer 输出格式化字符串,和 Logrus 完全无关。两者共存会引发日志重复、格式错乱,甚至 panic(比如往已关闭的文件句柄写)。
- 如果你用了
router.Use(gin.Logger()),就等于开了第二套日志系统,必须关掉 - 正确姿势:用
gin.New()替代gin.Default(),避免自动加载Logger()和Recovery() - 若需保留 recovery 功能,单独加
gin.Recovery()即可,它不依赖日志输出
自定义中间件记录请求详情时,别直接用 logrus 包级函数
Logrus 全局 logger 适合打通用日志(如启动、配置加载),但记录 HTTP 请求这类结构化信息,建议封装成中间件并传入带上下文的 *logrus.Logger 实例。
- 用
logger.WithFields()注入method、path、status、latency等字段,比拼接字符串更利于后续解析 - 不要在中间件里反复调用
logrus.SetOutput()或重设 formatter —— 全局配置只做一次 - 注意
c.Next()前后分别打日志:前段记请求进入,后段记响应发出,并用defer或变量捕获耗时
最易被忽略的点是:rotatelogs writer 初始化失败时不会 panic,而是静默 fallback 到 os.Stderr,导致你以为日志进了文件,其实全在终端刷屏——务必检查 rotatelogs.New() 返回的 error,并确认日志目录权限和父路径存在。











