gin原生支持按天切日志,关键在于自定义io.writer实现write方法,通过锁保护、日期比对与文件切换完成轮转;单纯os.openfile无法自动切换文件,需手动维护日期状态和文件句柄。

不需要引入 logrus 或 zap,Gin 原生就能按天切日志,关键在于替换 gin.DefaultWriter 为一个能自动轮转的 io.Writer 实现。
为什么直接用 os.OpenFile 不行
单纯用 os.OpenFile 创建文件并赋给 gin.DefaultWriter,日志只会追加到同一个文件里,不会自动按日期新建。系统跑几天后,gin.log 就会越来越大,手动切割既不及时也不可靠。
- Linux 下 nohup 启动时,若没重定向 stdout/stderr,
gin.DefaultWriter = os.Stdout会写进nohup.out,同样无分割 -
io.MultiWriter只是把多个io.Writer合并输出,不解决轮转逻辑 - 轮转必须在写入前判断日期是否变更,并打开新文件 —— 这得自己实现
Write([]byte)方法
用自定义 io.Writer 实现按天轮转
核心是实现 io.Writer 接口,内部维护当前日期和文件句柄,在每次 Write 时检查是否需要切换文件。
- 每次写入前用
time.Now().Format("2006-01-02")获取当天日期字符串 - 如果和缓存的日期不一致,先
Close()旧文件,再os.OpenFile打开新文件(带os.O_APPEND | os.O_CREATE | os.O_WRONLY) - 注意:
Write方法要加锁(sync.Mutex),避免并发写导致文件句柄错乱 - 不要在
Write里做耗时操作(比如每次读文件大小、检查磁盘空间),否则拖慢 HTTP 响应
示例片段:
type DailyWriter struct {
mu sync.Mutex
file *os.File
date string
dirPath string
}
func (w *DailyWriter) Write(p []byte) (n int, err error) {
today := time.Now().Format("2006-01-02")
w.mu.Lock()
defer w.mu.Unlock()
if w.date != today {
if w.file != nil {
w.file.Close()
}
filename := filepath.Join(w.dirPath, "gin-"+today+".log")
w.file, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
return 0, err
}
w.date = today
}
return w.file.Write(p)
}
gin.DefaultWriter 和中间件日志的区别
设置 gin.DefaultWriter 只影响 gin.Logger() 中间件的输出;你自己代码里用 log.Print 或第三方 logger(如 logrus)不会走这条路。
-
gin.DefaultWriter是全局变量,修改后所有gin.Logger()实例都生效 - 如果你禁用了默认 Logger(比如用
gin.New()+ 手动Use),那必须显式传入自定义 Writer:gin.LoggerWithWriter(customWriter) - 想同时输出到终端和文件?用
io.MultiWriter(customWriter, os.Stdout),但要注意终端输出不带颜色(gin.DisableConsoleColor()) - 别把轮转 Writer 用在
log.SetOutput()上——那是 Go 标准库日志,和 Gin 无关
生产环境绕不开的细节
真正上线时,光“按天切”远远不够,几个容易漏掉的点:
- 进程重启时,要确保旧文件被正确关闭,否则 Linux 的文件句柄可能泄漏(尤其用 systemd 管理服务时)
- 日志目录不存在时,
os.OpenFile会失败,需提前os.MkdirAll - 如果用
nohup ./app &启动,且没重定向 stdout,os.Stdout仍是 nohup.out —— 此时gin.DefaultWriter = io.MultiWriter(..., os.Stdout)会把轮转日志和 nohup.out 混在一起,失去意义 - 多实例部署(比如用 supervisor 启多个进程)时,不能共用同一轮转 Writer 实例,否则写入冲突
轮转逻辑本身很简单,但和进程生命周期、部署方式、权限模型一结合,就很容易出 silent failure —— 日志看似在写,其实早就不落盘了。











