log.SetOutput 必须在 main 第一行调用,否则早期日志仍输出到 os.Stderr;多目标用 io.MultiWriter;*os.File 需独占避免竞态;log.SetFlags 全量替换标志位,需显式加 log.Lshortfile 才显示文件行号;log.Printf 漏换行会导致日志粘连;分模块前缀必须用 log.New 创建独立 logger。

log.SetOutput 必须在 main 开头调用
日志输出目标一旦设置就全局生效,但 log.SetOutput 调用太晚会导致部分日志仍写到 os.Stderr——比如在 init 函数或某个 handler 里才设置,之前 log.Println 已经输出了,这部分就找不回来了。
- 务必放在
main()第一行,或至少在任何log.*调用之前 - 若需多目标(如同时写文件+控制台),用
io.MultiWriter(os.Stdout, file),别自己写 goroutine 拼接 - 传给
log.SetOutput的*os.File必须是独占的:多个 logger 共享同一个*os.File会触发 write 竞态,出现日志错位或 panic - 调试时临时切回终端:
log.SetOutput(os.Stdout);生产环境切文件前,记得用os.OpenFile(..., os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)并检查err
log.SetFlags 不是“追加”,而是完全覆盖
log.SetFlags 不是开关式累加,而是直接替换整个标志位。默认值是 log.LstdFlags(含日期和时间),但它**不含文件名和行号**——很多人试了没效果,其实是忘了显式加上 log.Lshortfile。
- 要时间戳 + 文件名 + 行号:必须写全
log.SetFlags(log.LstdFlags | log.Lshortfile) - 只想要微秒级时间?
log.Lmicroseconds不能单独用,得搭配log.Ltime,否则时间字段不显示 - 想彻底清空前缀(比如自己拼 JSON):
log.SetFlags(0),而不是log.SetFlags(log.Lshortfile)再删 -
log.Llongfile输出绝对路径,开发机路径可能泄露,CI/CD 环境慎用
log.Printf 和 log.Println 的行为差异直接影响可读性
log.Println 自动加空格和换行,适合随手打点;log.Printf 完全由格式串控制,适合结构化输出——但漏写 \n 会导致多条日志挤成一行,线上排查时极难定位。
- 打印 error 类型,用
%v而不是%s:log.Printf("failed: %v", err),避免自定义 error 返回空字符串 - 结构体、map 用
%+v才能看到字段值,log.Println(user)只输出{0xc000123456} - JSON 片段不要依赖
log.Println自动换行:log.Printf(`{"level":"info","msg":"%s"}\n`, "hello"),显式加\n更可靠 -
log.Fatal和log.Panic也受SetFlags影响,不是只作用于Println
用 log.New 创建独立 logger 才能支持模块前缀
log.SetPrefix 是全局单点设置,无法按模块切换前缀(比如 [DB] / [HTTP])。真要分模块,必须用 log.New 实例化多个 logger,每个带自己的 prefix、flags 和 output。
立即学习“go语言免费学习笔记(深入)”;
-
log.New(file, "[DB] ", log.LstdFlags|log.Lshortfile)返回独立实例,不影响全局log.Printf - 每个 logger 应配独立
io.Writer:不要把同一个*os.File传给多个log.New - 并发安全靠的是 logger 实例本身,不是 writer——所以
bufio.Writer加sync.Mutex封装后才能复用 - 如果只是临时加服务名前缀,
log.SetPrefix("[auth] ")最轻量;但一旦涉及不同级别、不同输出目标,就得上log.New



















