go标准log包不支持自动按日期轮转,需手动实现:每次写日志前用time.now().format("2006-01-02")比对缓存日期,变化则同步关闭旧文件、打开新文件,并用io.multiwriter兼顾控制台与文件输出。

log.SetOutput 重定向到文件但不自动轮转
Go 标准 log 包本身不支持按日期切割,log.SetOutput 只能塞一个 io.Writer,比如 os.File。直接传个固定文件句柄,日志就一直往里追加,第二天还写同一个文件——这不是“按日期切割”,只是“写进文件”。
常见错误现象:2024-05-20.log 今天生成,明天还在写它,第三天还是它;或者程序重启后才切新文件,但日期对不上。
- 必须自己控制文件打开时机:每次写日志前判断当前日期是否变化,变化了就关闭旧文件、打开新文件
- 不能在
init()或启动时只开一次文件——那样永远切不动 - 注意
os.O_APPEND | os.O_CREATE | os.O_WRONLY是安全组合,别漏os.O_APPEND,否则每次写都会覆盖
用 io.MultiWriter 同时输出到终端和文件
开发时想看控制台输出,上线又要落盘,硬编码两个 log.Logger 很容易不同步。用 io.MultiWriter 把 os.Stdout 和当前日期文件包装成一个 io.Writer,再交给 log.SetOutput,最省事。
使用场景:本地调试带实时 stdout,生产环境静默写文件,代码零改动切换。
立即学习“go语言免费学习笔记(深入)”;
-
log.SetOutput(io.MultiWriter(os.Stdout, dailyFileWriter))—— 这样一句就够了 -
dailyFileWriter必须是线程安全的,因为log默认并发调用Write - 别把
os.Stdout和文件混在同一个os.File里打开——os.Stdout是特殊文件描述符,不能当普通文件 reopen
time.Now().Format("2006-01-02") 是日期判断的唯一可靠依据
别用 time.Now().Day() 或 .Month() 单独比对,跨月(如 31→1)、跨年(12→1)时极易出错。Go 的时间比较必须基于完整日期字符串或 time.Time 值本身。
性能影响很小,但逻辑漏洞很致命:你认为“今天不是昨天”就该切文件,但如果只比 day,31 号和 1 号 day 不同,但可能都在同一个月内,不该切。
- 缓存上一次写入时的日期字符串(如
"2024-05-20"),每次写前time.Now().Format("2006-01-02")对比 - 别用
time.Now().Unix() / 86400算“第几天”——时区、夏令时会让这个值漂移 - 格式必须严格用
"2006-01-02",这是 Go 唯一内置的日期 layout,其他写法(如"%Y-%m-%d")会 panic
文件句柄泄漏和 sync.Write 调用时机
每天切新文件,旧文件句柄如果不关,跑一周就是 7 个 open fd;Linux 默认限制 1024,撑满就 write: too many open files。
容易踩的坑:在 goroutine 里异步 close 文件,但主流程已经用旧句柄写了日志——写操作可能正在进行,close 就导致 write: broken pipe 或静默丢日志。
- 必须同步 close:判断要切文件时,在写入新日志前,先
oldFile.Close(),再os.OpenFile(...)开新文件 - 每次写日志前检查日期 + 切文件 + 写,三步串行,别拆到多个 goroutine
- 如果用了
file.Sync()强制刷盘(比如金融类日志要求不丢),记得只在关键 log 后调,频繁 sync 会拖慢吞吐
真正麻烦的是多进程场景:单机起多个 Go 程序,都往同一目录写 2024-05-20.log,得靠文件锁或统一日志服务,标准库 log 搞不定这个。










