Go log包默认输出到stderr,写文件需用SetOutput重定向至os.File;推荐OpenFile标志os.O_APPEND|os.O_CREATE|os.O_WRONLY;须检查error、close文件;多goroutine下应共用单个Logger或加锁writer;shell重定向不可靠;简单按天轮转需加锁封装,复杂需求用lumberjack。
Go 标准库的 log 包默认输出到 os.Stderr,不直接写文件;要写入日志文件,必须显式设置输出目标,且需注意文件打开模式、并发安全和资源释放。
log.SetOutput() 重定向到文件最直接的方式是把 *os.File 传给 log.SetOutput()。但要注意:os.OpenFile() 的标志位决定是否追加、是否创建、是否截断。
os.O_APPEND | os.O_CREATE | os.O_WRONLY:推荐组合,追加写、自动建文件、只写不读os.O_TRUNC(会清空已有日志)os.OpenFile() 返回的 error,路径不存在或权限不足时会失败file.Close(),否则可能丢失最后几条日志(缓冲未刷出)file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
log.SetOutput(file)
log.Println("服务启动") // 写入 app.log
log.Logger 本身是并发安全的,但底层 io.Writer(比如 *os.File)的写入操作在高并发下仍可能因系统调用调度或缓冲区竞争导致行粘连

log 默认每条日志末尾加 \n,所以只要不手动改 log.SetFlags(0) 或用 log.Print() 混用,一般不会粘连log.Logger 实例共用同一个 *os.File,且各自调用 Write() —— 这绕过了 log 的锁机制log.Logger 实例,或使用带锁封装的 writer(如 io.MultiWriter + 自定义同步 writer)log.Printf() 配合 os.Stdout 重定向来写文件在 shell 启动时用 ./app > app.log 看似可行,但存在严重缺陷:
log.Println() 输出会被重定向,但 log.Fatalln() 和 log.Panicln() 仍会写到 stderr(除非也重定向 2>&1)io.Writer
如果不需要完整日志库(如 zap 或 lumberjack),可轻量封装:每次写前检查当前日期是否变化,变化则关闭旧文件、打开新文件。
sync.Mutex),否则多 goroutine 下可能同时触发切换,导致文件句柄泄漏或写入错位Write() 方法里做耗时操作(如 stat、rename),否则拖慢所有日志调用Write() 开头比对 time.Now().Format("2006-01-02"),避免高频调用 time.Now()
真正复杂的轮转(按大小、保留份数、压缩、异步刷盘)还是交给 github.com/natefinch/lumberjack 更稳妥——它内部已处理了竞态、原子重命名和 close race。