go标准log包不支持日志级别控制且默认输出到stderr;需显式调用setoutput重定向,配合setflags/setprefix设置格式,初始化须在main开头完成;建议早期引入zap/slog或封装线程安全wrapper。

log 包本身不支持日志级别控制,直接用 log.Printf 或 log.Println 写出来的日志无法按 Info/Warn/Error 过滤或开关——这不是配置问题,是能力缺失。
log 包的输出目标必须显式重定向,否则永远只写 stderr
默认行为是把所有日志发到标准错误流(stderr),即使你跑在容器里、用 systemd 管理,也看不到日志进文件或系统日志服务。
-
log.SetOutput(os.Stdout)可切到 stdout,适合 Docker 日志驱动捕获 -
log.SetOutput(file)需配合os.OpenFile,注意权限和defer file.Close()不能放错位置 - 想同时写文件 + 控制台?用
io.MultiWriter(os.Stdout, file),别自己做 goroutine 转发 - 写入
log/syslog:先syslog.NewLogger(syslog.LOG_INFO, 0),再log.SetOutput()接过去;但注意syslog.Writer不是线程安全的,高并发下可能 panic
常见错误:
- 打开文件后没检查
err,程序启动就 crash 却以为日志“没生效” -
defer file.Close()放在log.SetOutput(file)之后,导致后续日志写失败但无提示 - 把
syslog.New()返回的*syslog.Writer直接传给log.SetOutput(),而它需要的是io.Writer接口,得用syslog.NewLogger()才兼容
log.SetFlags 和 log.SetPrefix 必须在首条日志前调用
log 包的状态是全局且惰性初始化的。一旦 log.Println 执行过,log 内部的默认 logger 就建好了,再调 SetFlags 就无效。
正确顺序:
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)log.SetPrefix("[API] ")log.Println("server started")-
常用 flag 组合:
- 调试用:
log.LstdFlags | log.Lshortfile(含日期、时间、文件行号) - 生产精简:
log.Ldate | log.Ltime(去掉行号,减少磁盘 IO) - 时间精度要求高:
log.Lmicroseconds,但注意它会覆盖log.Ltime的格式
- 调试用:
log.SetPrefix不影响Fatal/Panic的退出行为,只是加个字符串前缀
想用级别控制?别硬改 log,换库或封装要趁早
标准 log 包没有 Debug、Warn 函数,也没法动态开关某一级别。强行模拟只会让代码变脆:
- 错误做法:
if os.Getenv("DEBUG") == "1" { log.Printf("[DEBUG] %s", msg) }—— 每处都判,漏一处就埋雷 - 更糟做法:用
build tag条件编译,但上线后想临时开 Debug 就得重新编译发布
真正可行的路径只有两条:
- 项目初期就引入
zap或slog:前者性能强、字段结构化明确;后者是 Go 官方推荐,但slog默认不输出,必须显式slog.SetDefault(slog.New(...)),否则slog.Info调用静默丢弃 - 自己封装一层轻量 wrapper:定义
type Logger struct{ *log.Logger; level int },在Info方法里判断l.level >= InfoLevel再写,但要注意log.Logger本身不是线程安全的写法,得确保底层io.Writer是安全的(比如文件句柄本身是线程安全的)
最常被忽略的一点:无论选哪个方案,日志初始化必须在 main 函数最开头完成,且不能依赖任何可能触发日志的初始化逻辑(比如 init() 函数里调了 log)。否则第一行日志就可能丢失上下文或格式。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











