go 1.21+ 推荐 slog,需通过 handleroptions.level 设置输出阈值(如 levelerror),debug 等调用仍执行但可能被丢弃;自定义 handler 需手动检查 r.level;避免混用 slog/zap;log rotation 要显式设权限、处理符号链接和属主。

log/slog 怎么设置不同级别输出
Go 1.21+ 默认推荐用 slog,它本身不内置分级开关,而是靠 slog.Handler 实现过滤。直接调 slog.Debug() 或 slog.Error() 不会自动拦截——没配对的 handler,Debug 日志照样打出来。
实操建议:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelError}))控制最低输出级别 -
Level是阈值:设slog.LevelWarn就只输出 Warn 及更严重(Error、Critical)的日志 - 别依赖函数名判断是否生效——
slog.Debug()调用仍会执行,只是 handler 可能丢弃它;有副作用的逻辑(比如拼接耗时字符串)得手动包一层if level >= slog.LevelDebug - 自定义 handler 时,务必在
Handle()方法开头检查r.Level ,否则分级失效
文件轮转为什么没触发——fsnotify 和定时器都不可靠
标准库 log/slog 和 log 都不带轮转功能。想轮转就得自己写或借第三方,但多数人踩坑在“以为打开文件就完事了”,其实轮转本质是:关旧文件 + 开新文件 + 清理历史,三步缺一不可。
常见错误现象:
- 用
os.OpenFile(..., os.O_APPEND)持续写,但磁盘满了或手动删了日志文件,程序还在往已删除的 fd 写——没报错,但内容丢了 - 基于时间轮转(比如每天一个文件),用
time.Ticker定时切换,结果 Ticker 没对齐 0 点,或者进程重启后计时器重置,导致同一天生成多个文件 - 并发写日志时,多个 goroutine 同时触发轮转,出现两个 goroutine 都去 close 同个 file,第二个 panic 报
use of closed network connection
实操建议:
- 轮转动作必须加锁(
sync.Mutex或sync.Once),且关闭旧文件和打开新文件要原子化 - 优先按大小轮转(如单文件超 100MB),比按时间更可控;检查文件大小用
os.Stat().Size(),别依赖写入字节数累加(可能有并发误差) - 清理旧日志用
filepath.Glob("app-*.log")+sort+os.Remove(),别硬编码数字后缀
zap 和 zerolog 对比:什么时候该换库
如果你已经用着 slog,但发现日志量大时 CPU 占用明显升高,或者需要 JSON 格式+字段结构化+采样,那不是配置问题,是底层模型限制——slog 的 handler 是同步串行的,而 zap 和 zerolog 用无锁环形缓冲区 + 异步写入。
使用场景差异:
- 调试开发阶段:用
slog.With("trace_id", tid).Info("req done")足够,简单干净 - 高吞吐服务(QPS > 1k):切
zap,注意zap.NewProduction()默认禁用 caller,要开就得加zap.AddCaller(),否则排查时找不到哪行打的日志 - 嵌入式或内存敏感场景:选
zerolog,它把结构体字段全编译期确定,运行时零分配,但不支持 level 动态调整(zerolog.SetGlobalLevel()是全局变量,热更新需重启) - 别混用:一个进程里同时 init
slog和zap,它们的io.Writer可能互相干扰,尤其共用同一个文件句柄时
log rotation 的路径权限和符号链接陷阱
线上常遇到:程序明明写了轮转逻辑,但新日志还是追加到旧文件,或者轮转后文件权限变成 600 导致运维脚本读不了。根本原因不是代码,是 os.OpenFile 的 flag 和 umask 没对齐。
实操要点:
- 创建文件必须显式传
0644权限:os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644),否则依赖进程 umask,K8s 容器里 umask 常是 0022,结果文件变 644;但有些系统默认 0077,就变成 600 - 如果日志路径是符号链接(比如
/var/log/myapp/current → /var/log/myapp/2024-06-01.log),轮转时不要直接os.Rename()目标文件,而要os.Readlink()解出真实路径再操作,否则链接断开 - 轮转前先
os.Chown()新文件属主(尤其容器里 uid 不固定),否则可能被当成 root 写,普通用户进程后续无法覆盖
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










