直接用 log.setoutput 换文件会丢日志,因为 log.logger 同步写入、无缓冲、无重试,切换时旧文件句柄可能已被关闭,导致 write: bad file descriptor 或静默丢弃。

为什么直接用 log.SetOutput 换文件会丢日志
因为 log.Logger 是同步写入、无缓冲、无重试的。在并发写日志过程中,若某 goroutine 正执行 Write,你却在另一处调用 log.SetOutput(newFile) 并 oldFile.Close(),旧句柄可能已被关,导致 write: bad file descriptor 或静默丢弃后续字节。
这不是竞态“偶尔出错”,而是确定性失败——只要写入和切换发生在同一毫秒级窗口,就必现。根本问题在于:标准库不提供原子输出切换语义。
- 永远不要在 HTTP handler、定时任务或日志写入热点路径里调用
log.SetOutput - 不要自己维护
*os.File句柄并手动Close/OpenFile交替 - 真正安全的做法是让所有日志写入始终流向一个稳定、线程安全、自带切换逻辑的
io.WriteCloser
lumberjack.Logger 的配置陷阱与真实行为
lumberjack 不是日志框架,而是一个带滚动能力的 io.WriteCloser,它被设计为直接塞进 log.SetOutput。但它的参数含义常被误读:
-
Filename必须是绝对路径,或确保进程工作目录固定(如 systemd service 中WorkingDirectory未设时,相对路径会失效) -
MaxSize单位是 MB,且判断时机是「写入前检查当前文件大小」,不是实时统计;设为0表示禁用大小滚动,仅靠MaxAge触发 -
MaxAge和MaxBackups是「或」关系:任一条件满足即清理最老文件,不是“既要满 7 天又要超 5 个才删” -
Compress: true会阻塞Write调用——压缩过程串行执行,高并发写日志时可能卡住整个日志链路 -
LocalTime: true必须显式开启,否则归档文件名和ModTime都按 UTC,排查时容易对不上本地时间
按小时/天滚动必须绕过 Filename 静态字段
lumberjack 原生只支持单文件 + 数字后缀(app.log.1, app.log.2),不支持 app-2026-06-13.log 或 app-2026-06-13-14.log 这类时间戳命名。要实现纯时间驱动滚动,得自己封装 io.Writer:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 用
sync.Mutex保护文件句柄切换,避免多 goroutine 同时创建同名文件 - 每次
Write前计算时间键(如time.Now().Format("2006-01-02")),与当前文件名比对 - 切换时必须用
os.Rename(同分区下原子),不能先os.Remove再os.Create,否则存在“无文件可写”的空窗期 - 打开新文件务必用
os.O_CREATE | os.O_WRONLY | os.O_APPEND,漏掉os.O_APPEND会导致覆盖已有内容 - 别在
Write方法里做filepath.Glob或os.RemoveAll—— 这些 IO 操作会拖慢所有日志调用
清理旧日志不能只靠 lumberjack 的 MaxAge
lumberjack 的 MaxAge 参数仅用于轮转决策(比如“该不该切新文件”),它**不会主动删除超龄归档文件**。如果你看到 app.log.1、app.log.2 等文件长期堆积,就是这个原因。
生产环境必须补一层独立清理逻辑:
- 启一个单独 goroutine,用
time.Ticker每小时扫描一次日志目录 - 用
filepath.Glob("logs/app.log.*")匹配归档文件,避免误删其他类型文件 - 对每个匹配文件调用
os.Stat,校验IsRegular()并比对ModTime()是否早于time.Now().AddDate(0,0,-30) - 删除前加
runtime.Gosched()或分批(如每删 50 个让出调度),防止阻塞主线程 - 容器环境下特别注意:挂载点可能是 bind mount,
os.RemoveAll在宿主机上执行可能不生效,应优先用os.Remove单文件清理
最易被忽略的一点:lumberjack 归档后的文件(如 app.log.1.gz)修改时间是压缩完成时刻,不是原始日志写入时刻——清理逻辑若只看 ModTime,可能误判保留时长。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










