Go标准库log不支持多输出,需用io.MultiWriter(轻量)或zap等结构化日志库(支持分级分流);注意文件权限、goroutine安全、关闭时机及写入拓扑设计。

Go 标准库 log 本身不支持多输出目标,必须手动组合 io.MultiWriter 或用结构化日志库(如 zap、zerolog)实现;直接调用 log.SetOutput 只能设一个写入器,覆盖前值。
用 io.MultiWriter 同时写文件和控制台
这是最轻量、无第三方依赖的方案,适合简单场景。核心是把多个 io.Writer 合成一个,传给 log.SetOutput。
-
os.Stdout和os.Stderr都是io.Writer,可直接传入io.MultiWriter - 写文件需先打开:用
os.OpenFile并设置os.O_CREATE | os.O_APPEND | os.O_WRONLY,避免每次覆盖 - 注意:所有写入共用同一锁,高并发下可能成为瓶颈;若需异步或分级输出(如 error 写文件 + info 打屏),
MultiWriter不适用
示例:
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
defer file.Close()
multi := io.MultiWriter(os.Stdout, file)
log.SetOutput(multi)
log.Println("这行会同时出现在终端和文件里")
用 zap 实现带级别分流的多输出
当需要按日志级别分发(比如 error 写文件 + info 发 Kafka + debug 输出到 stderr),io.MultiWriter 失效,得靠结构化日志库的 Core 或 WriteSyncer 组合。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
-
zap的zapcore.NewTee是专为多输出设计的函数,接收多个zapcore.Core - 每个
Core可绑定不同WriteSyncer(文件、网络、内存缓冲)和不同LevelEnabler(控制哪些级别通过) - 别直接用
zap.NewProduction()或zap.NewDevelopment(),它们返回单输出实例,无法扩展
示例(error 写文件,其余打屏):
file, _ := os.OpenFile("error.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
console := zapcore.Lock(os.Stdout)
encoder := zap.NewProductionEncoderConfig()
core1 := zapcore.NewCore(zapcore.NewJSONEncoder(encoder), zapcore.AddSync(file), zapcore.ErrorLevel)
core2 := zapcore.NewCore(zapcore.NewConsoleEncoder(encoder), console, zapcore.InfoLevel)
logger := zap.New(zapcore.NewTee(core1, core2))
logger.Error("只进 error.log")
logger.Info("只进 stdout")
常见踩坑点:文件权限、goroutine 安全与关闭时机
多输出看似只是“加几个 Writer”,但实际部署时容易因细节崩掉。
- Linux 下忘记
chmod日志目录,导致open /var/log/app/app.log: permission denied—— 程序静默失败,日志全丢 - 用
os.Stdout重定向到文件后,再用io.MultiWriter包一层,结果日志重复写两次(一次被 shell 重定向,一次被程序写) - 用
defer file.Close()放在 main 函数末尾,但程序 panic 或 signal 退出时,defer不执行,日志缓冲未 flush,最后几条丢失 -
zap的WriteSyncer默认不是 goroutine-safe 的,若多个 goroutine 并发调logger.Info,需确保底层io.Writer自身线程安全(如用zapcore.Lock包装)
多输出不是配置开关,而是写入路径的拓扑设计;真正难的从来不是“怎么写两份”,而是“哪一级该写哪一份”“写失败了要不要降级”“重启时旧文件怎么轮转”。这些逻辑一旦耦合进业务主流程,就很难抽离。

















