log.SetOutput可写文件,但需用os.OpenFile配O_CREATE|O_APPEND|O_WRONLY模式避免清空日志;log.Print*自动换行,自定义写法需显式加\n;应复用单个log.Logger实例或加锁;轮转需lumberjack等第三方库。

log.SetOutput 写文件最直接,但别漏了 os.File 的打开模式
Go 标准库的 log 包本身不区分输出目标,靠 log.SetOutput 接收一个 io.Writer —— 你给它一个打开的 *os.File,日志就进文件了。关键不是“能不能”,而是“怎么开这个文件”。
- 用
os.OpenFile,别用os.Create:后者等价于os.OpenFile(..., os.O_CREATE|os.O_TRUNC|os.O_WRONLY),每次启动程序都会清空旧日志 - 推荐模式:
os.O_CREATE | os.O_APPEND | os.O_WRONLY,确保追加写、自动建文件、不误删历史 - 记得检查返回错误:
if err != nil { log.Fatal(err) },文件路径不存在或权限不足时会静默失败(如果没处理错误)
示例:
f, err := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
log.SetOutput(f)
log.Println 写进去的是字符串,换行符得自己管
log.Println 和 log.Printf 默认会在末尾加换行符,但如果你用 log.Output 或直接写 f.Write,就不会自动换行 —— 这点容易导致多条日志挤在一行,排查时一眼看不出边界。
- 标准
log.Print*系列函数安全,自带\n - 自定义封装时(比如加 traceID),如果调用
fmt.Fprintf(f, ...),必须显式加\n - 注意 Windows 换行:
\r\n不是必须的,Go 日志默认只写\n,跨平台查看没问题
并发写文件要加锁,log.Logger 本身不是线程安全的
log.Logger 实例的 Print* 方法内部有锁,但前提是**你用的是同一个实例**。常见错误是:每个 goroutine 都 new 一个 log.Logger,然后各自 SetOutput 到同一个 *os.File —— 这会导致日志内容错乱、部分丢失。
- 正确做法:全局一个
*log.Logger实例,所有地方复用它 - 如果要用多个 logger(比如按模块分日志),每个 logger 可以共享同一个
io.Writer,但不要各自调用SetOutput改写底层 file - 更稳妥:用
sync.Mutex包一层io.Writer,或者直接用lumberjack.Logger这类成熟轮子(它内置锁和切割逻辑)
文件切片和清理不能靠标准 log 包,得自己补或换库
log 包不提供日志轮转(rotating)、按大小/时间切文件、自动清理旧日志的功能。上线后不处理,app.log 会越长越大,直到磁盘爆掉。
立即学习“go语言免费学习笔记(深入)”;
- 简单项目可定时用
os.Rename+os.Create手动切,但要注意竞态:切的时候可能有其他 goroutine 正在写 - 生产环境强烈建议用
gopkg.in/natefinch/lumberjack.v2:它实现了线程安全的轮转,支持MaxSize、MaxBackups、MaxAge - 用 lumberjack 示例:
l := &lumberjack.Logger{ Filename: "app.log", MaxSize: 10, // MB MaxBackups: 3, MaxAge: 7, // days } log.SetOutput(l)轮转逻辑藏在 Write 方法里,对上层完全透明,但背后做了文件重命名、句柄切换、锁保护 —— 这些细节,不自己实现就很难踩得稳。


















