Go标准库log包轻量可靠但无级别和轮转,调试用log.Println,留存日志必用log.Printf;需显式SetFlags启用时间戳和行号;写文件须用os.OpenFile并正确defer关闭;并发安全但SetOutput全局覆盖,多目标输出用io.MultiWriter。

Go标准库log包默认输出到os.Stderr,不带级别、不自动轮转,但足够轻量可靠——只要别把它当结构化日志用,就基本不会翻车。
log.Println vs log.Printf:什么时候该用哪个
日常调试打点,log.Println省事;但只要日志要留存、要搜索、要进日志系统,必须用log.Printf。
-
log.Println("user:", u)会把结构体u打印成&{...}内存地址,不是字段值 -
log.Printf("user id=%d name=%s", u.ID, u.Name)精准控制输出,且不自动换行——漏写\n会导致多条日志挤在一行 - 错误日志统一用
%v:log.Printf("failed to parse config: %v", err),别手动调err.Error()
加时间戳和文件行号:SetFlags别乱组合
默认不带任何元信息,必须显式启用。最常用也最稳妥的是:
-
log.SetFlags(log.LstdFlags | log.Lshortfile)—— 同时包含日期+时间(秒级)+ 文件名:行号 - 别重复设
log.Ldate和log.Ltime,它们已被log.LstdFlags包含,否则时间会打印两遍 - 需要微秒级?加
log.Lmicroseconds,但高并发下有轻微性能抖动,非必要不加 - CLI工具想干净输出?用
log.SetFlags(0),但得自己确保log.SetOutput(os.Stdout)并手动加\n
写入文件:OpenFile模式和defer时机最关键
日志写不进文件,90%是os.Open误用或defer f.Close()位置错了。
立即学习“go语言免费学习笔记(深入)”;
- 必须用
os.OpenFile,不能用os.Open——后者只读,写入会静默失败 - 推荐打开模式:
os.O_WRONLY | os.O_CREATE | os.O_APPEND,权限用0644而非0666 -
defer f.Close()必须放在main()函数里,而不是init()或封装函数中,否则文件句柄提前关闭,后续日志触发bad file descriptor - 务必检查
err:if err != nil就log.Fatalf,否则日志丢失无提示
并发安全与多目标输出:SetOutput不是万能的
log包本身线程安全,但SetOutput是全局覆盖——改一次,所有log.Xxx都走新目标。
- 想同时写文件+控制台?用
io.MultiWriter:log.SetOutput(io.MultiWriter(f, os.Stdout)) - 需要不同前缀或不同格式的日志(比如
[INFO]和[ERROR])?用log.New创建多个独立实例,别硬塞进一个全局log - 没有内置日志级别,别试图用
log.Fatal替代error级别——它会直接os.Exit(1),且不走SetOutput设置的目标
最常被忽略的点:日志内容是否可检索、是否带上下文、是否在高并发下稳定输出——log包只负责“写”,不负责“组织”。一旦项目变大,就得靠slog或zap补上结构化和分级能力,而不是给log.Printf套一堆if。


















