Go标准库log包不支持日志轮转,需用lumberjack等第三方库;os.OpenFile+SetOutput仅重定向输出,无法解决大小/时间轮转、并发安全及重启续号问题。
Go 标准库的 log 包本身不支持日志轮转,直接写文件会无限追加、撑爆磁盘。必须借助第三方库或手动封装,否则生产环境无法接受。
os.OpenFile + log.SetOutput
很多人尝试用 os.OpenFile 打开一个文件,再传给 log.SetOutput,以为能“接管”日志输出——但这只是把日志重定向到文件,完全没解决轮转问题。轮转需要判断文件大小/时间、重命名旧文件、创建新文件、甚至压缩归档,标准库不做这些事。
app.log.2025-05-20.001
app.log.003,重启后又从 .001 开始,覆盖旧日志lumberjack 封装 log.SetOutput
lumberjack 是最成熟、轻量、被大量生产项目验证的日志轮转库(如 Kubernetes、Docker 的部分组件也在用)。它实现的是 io.WriteCloser 接口,可直接塞进 log.SetOutput,无需改日志调用方式。
安装:
go get gopkg.in/natefinch/lumberjack.v2
基础用法示例:
package main
import (
"log"
"gopkg.in/natefinch/lumberjack.v2"
)
func main() {
logger := log.New(&lumberjack.Logger{
Filename: "/var/log/myapp/app.log",
MaxSize: 100, // MB
MaxBackups: 7,
MaxAge: 28, // 天
Compress: true,
}, "", log.LstdFlags)
logger.Println("服务启动")
}
MaxSize 单位是 MB,不是字节;超过即触发轮转MaxBackups 控制保留几个历史文件(.001 ~ .007),超出的自动删除MaxAge 是按文件最后修改时间计算,和当前系统时间比对,不是日志内容里的时间戳.gz 文件生效,主日志文件(app.log)始终是明文如果因合规或审计要求必须自己实现(比如要加密归档、对接 S3、打特定 trace ID 前缀),绕不开这几个关键点:
os.Stat 检查大小,再 os.Rename,中间不能有其他写入,否则可能丢失日志。建议用 sync.Mutex + 双缓冲或 channel 控制写入队列time.Now().Format("20060102"),同一秒内多次轮转会覆盖。应加上序号或纳秒级时间戳,如 app.log.20250520.123456789
filepath.Glob 遍历全目录:当磁盘 IO 压力大时,Glob 可能阻塞主线程。应维护一个内存中的文件名列表,轮转时更新os.Chmod 和 os.Chown 要在 OpenFile 后立即调用,否则新建文件可能继承错误权限(尤其容器里 uid/gid 不匹配时)轮转真正的难点不在“怎么切”,而在“切得稳、不丢、不卡、不冲突”。哪怕只用 lumberjack,也要实测压测场景下它的锁竞争和 GC 表现——有些版本在高并发小日志(每毫秒百条)下,Write 方法会成为瓶颈。