Go log包默认只输出到stderr,需用os.OpenFile创建*os.File(实现io.Writer)传给log.SetOutput;并发写安全但格式易乱;需手动flush或用bufio.NewWriter;长期运行需日志轮转等高级功能。

Go 标准库的 log 包默认只输出到 stderr,不支持直接写文件——必须自己包装 os.File 作为 io.Writer 传进去。
log.SetOutput 需要传 io.Writer,不是文件路径
很多人卡在第一步:直接把字符串路径(比如 "app.log")塞给 log.SetOutput,结果 panic:cannot use "app.log" (type string) as type io.Writer。这是因为 SetOutput 接收的是接口,不是路径。
- 先用
os.OpenFile打开或创建文件,返回*os.File(它实现了io.Writer) - 注意打开模式:追加日志用
os.O_APPEND | os.O_CREATE | os.O_WRONLY,覆盖用os.O_TRUNC - 别忘了检查
err,文件目录不存在时会失败,os.MkdirAll要自己兜底 - 示例:
file, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } log.SetOutput(file)
多 goroutine 写同一个文件是安全的,但格式容易乱
*os.File.Write 是线程安全的,Go 运行时内部加了锁,所以多个 log.Print 并发调用不会导致 panic 或数据损坏。但问题出在「换行」和「时间戳」上:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 每条日志是原子写入,但
log.Printf("a"); log.Printf("b")可能被调度成a\nb\n或ab\n\n(如果底层 buffer 没 flush) - 标准
log的Flags(如log.Ltime | log.Lshortfile)在并发下仍可靠,但自定义前缀(比如手动拼接时间)容易错位 - 如果需要结构化日志(JSON)、轮转、压缩,别硬刚标准库——直接上
zap或zerolog,它们原生支持文件写入和并发安全格式控制
log.Println 写文件后看不到实时内容?检查是否 flush
日志“写进去了但文件里没更新”,大概率是缓冲区没刷出。标准 log 默认不自动 flush,尤其用 os.OpenFile 时:
立即学习“go语言免费学习笔记(深入)”;
-
log本身没有Flush()方法,它的输出目标(*os.File)才有 - 可以定期调用
file.Sync()强制落盘(适合关键错误日志),但频繁调用影响性能 - 更稳妥的做法:用
bufio.NewWriter包一层,自己控制Flush()时机;或者改用带缓冲策略的日志库(如lumberjack+log组合) - 调试时临时加
file.Sync()看效果,上线前评估 I/O 压力
真正麻烦的不是“怎么写进去”,而是“怎么不让日志把磁盘打满”“怎么查昨天的 error”“怎么避免 panic 时最后一行日志丢失”——这些得靠轮转、异步写入、panic hook 配合,不是 SetOutput 一行代码能解决的。

















