不可行。logrotate 是独立 shell 工具,无 Go 原生接口,不支持同步阻塞调用,且不会通知 Go 进程文件已轮转,导致日志写入旧文件或丢失。

Go 程序直接调用 logrotate 可行吗?
不可行。logrotate 是一个独立的 shell 工具,不提供 Go 原生接口,也无法被 Go 进程同步阻塞调用完成切分——它靠外部定时(如 cron)驱动,和你的 Go 进程生命周期无关。
常见错误现象:exec.Command("logrotate", "/etc/logrotate.d/myapp") 调用后日志没切、权限报错、或切了但 Go 还往旧文件写导致丢失。
- logrotate 不会通知你的 Go 进程“我刚切了文件”,
os.File句柄仍指向原文件描述符,写入持续发生 - 若 Go 日志未使用
os.O_APPEND或未在切分后os.OpenFile(..., os.O_WRONLY|os.O_APPEND)重开,新日志可能覆盖旧内容 - logrotate 默认用
copytruncate模式时,Go 写入不受影响,但该模式有竞态:复制完到清空前的写入会丢
Go 自带的 rotatelogs 库为什么常出问题?
因为多数人只看文档示例,忽略其底层依赖 os.File 的刷新机制与信号处理缺失。
使用场景:适合无 systemd/cron 环境、需完全由 Go 控制切分时机(如按大小/时间自动轮转)的小型服务。
立即学习“go语言免费学习笔记(深入)”;
-
rotatelogs.New返回的io.Writer必须全程透传给log.SetOutput,不能中途换回os.Stdout或拼接其他 writer - 默认不处理
SIGHUP,无法响应外部 reload 请求;需手动注册 signal handler 并调用rl.Close()+rl = rotatelogs.New(...)重建 - 时间轮转精度受
WithRotationTime和实际写入频率影响:若 1 小时切一次,但日志每 5 秒才写一条,可能拖到下一个写入点才真正切 - Windows 下
rotatelogs的符号链接行为与 Linux 不一致,LinkName参数易失效
容器中用 logrotate 的正确姿势
必须让 logrotate 和 Go 进程共享文件系统视图,并解决“切分后 Go 继续写原文件”这个核心矛盾。
关键不是“能不能用”,而是“怎么让 Go 感知并配合”。常用组合是:logrotate + SIGHUP + Go 侧文件重开逻辑。
- logrotate 配置里必须加
create 644 root root和sharedscripts,确保新文件权限正确、且只执行一次 postrotate - postrotate 脚本里发
kill -SIGHUP $(cat /var/run/myapp.pid)(注意:容器中 pid 文件需挂载到宿主机可读路径,或改用进程名查杀) - Go 主程序需监听
syscall.SIGHUP,收到后关闭当前*os.File,再用相同路径os.OpenFile(..., os.O_WRONLY|os.O_APPEND|os.O_CREATE)重开 - 避免在 init 容器里跑 logrotate:它和主应用不在同一 PID namespace,
kill失效;应作为 sidecar 或直接集成进主镜像的 cron
为什么 lumberjack 在容器里容易丢日志?
因为它的 Rotate 是同步阻塞操作,而容器退出时 SIGTERM 到来,若此时正卡在 rename 或 chown,Close 可能被中断,缓冲区未 flush。
性能影响:每次轮转会阻塞所有日志写入,高并发下延迟明显;兼容性上,lumberjack.Logger 不支持 context,无法配合超时控制。
- 务必设置
lumberjack.Logger.MaxBackups = 0或较小值,否则磁盘满会导致Write返回 error,而标准log包会静默吞掉 - 不要用
lumberjack直接包装os.Stdout:它内部假设写的是普通文件,对 /dev/stdout 的Seek调用会失败 - 在 Kubernetes 中,若用 emptyDir 存日志,需确认
sizeLimit足够容纳最大单个文件 + 最多备份数,否则切分时rename报no space left on device
最易被忽略的一点:无论用哪种方案,Go 进程都必须以非 daemon 方式运行(前台),否则信号收不到;Dockerfile 里别写 cmd ["myapp", "&"] 这种后台启动。










