是,中间件里直接用 logger.Info 会丢日志:Zap 默认 WriteSyncer(如 os.Stdout)无缓冲,每条日志触发系统调用,异常退出时未 flush 的日志丢失;必须用 bufio.NewWriter + zapcore.AddSync + zapcore.Lock 显式包装,并在服务退出前调用 logger.Sync() 确保落盘。

中间件里直接用 logger.Info 会丢日志?
会。Zap 默认的 WriteSyncer(比如 os.Stdout)是同步写入,但中间件高频调用 logger.Info 时,如果没配缓冲,每条日志都触发一次系统调用,CPU 和 I/O 压力陡增;更隐蔽的问题是:进程异常退出(如 SIGTERM)时,未 flush 的日志直接丢失。
根本原因不是 Zap 本身不支持缓冲,而是你传给 zap.NewCore 的 WriteSyncer 没包缓冲层。
- 别用
os.Stdout或裸os.File直接塞进 Core —— 它们不带缓冲,也不自动 flush - 必须显式包装成带缓冲的
io.WriteSyncer:用bufio.NewWriter+zapcore.AddSync - 缓冲区大小建议设为 4096 或 8192 字节,太小起不到作用,太大可能延迟高
- 缓冲 Writer 必须在进程退出前调
Flush(),否则最后一批日志永远写不出
zapcore.Lock 和缓冲能一起用吗?
能,而且必须一起用。多 goroutine 并发写同一个文件句柄时,不加锁会 panic;而 bufio.Writer 本身不是并发安全的,所以顺序不能错:
- 先用
lumberjack.Logger(或os.File)创建底层写入器 - 再用
bufio.NewWriter包一层做缓冲 - 最后用
zapcore.Lock(zapcore.AddSync(...))包最外层,确保并发安全 - 错误示范:
zapcore.Lock(bufio.NewWriter(...))——Lock只保护 write 调用,不保护 buffer 内部状态
正确链路:lumberjack.Logger → bufio.Writer → zapcore.AddSync → zapcore.Lock
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
HTTP 中间件里怎么确保缓冲日志不丢?
关键不在中间件逻辑,而在全局 logger 生命周期管理。中间件只负责打点,真正决定日志是否落地的是 logger.Sync() 的调用时机和方式:
- 别在每个请求结束时都调
logger.Sync()—— 高频 sync 是性能杀手 - 应在服务优雅退出时统一调一次:监听
os.Interrupt或syscall.SIGTERM,然后执行logger.Sync() - 如果用了
lumberjack,它的Close()方法内部会 flush,所以也要在退出时调用 - 开发期可加定时强制 flush(如每 5 秒),但生产环境禁用——它会放大延迟毛刺
漏掉 Sync() 是线上日志截断最常见原因,尤其容器环境里进程被 kill -9 时,连 defer 都不执行。
为什么 zap.NewProduction() 不开缓冲也“好像”不丢日志?
因为 NewProduction() 默认用了 zapcore.Lock(zapcore.AddSync(os.Stderr)),而 os.Stderr 在多数 Linux 系统上是行缓冲(遇到 \n 就 flush),所以短日志看起来“实时”,但这是假象:
- 长日志(含换行符少)可能卡在内核缓冲区,几秒后才出来
- 重定向到文件时,
os.Stderr变成全缓冲,行为突变,丢日志概率飙升 - 容器 stdout/stderr 被 log driver(如 journald、fluentd)接管后,缓冲策略由外部控制,不可靠
真要可靠,就得自己控缓冲+flush,不能赌系统默认行为。

















