Go 标准库 log 包仅适合轻量级单进程调试,不支持分级、滚动、多目标或结构化字段,无法满足日志收集需求;推荐换用 zap 或 zerolog 等结构化日志库。

Go 标准库 log 包适合轻量级、单进程的日志记录,但**不支持日志分级、滚动切片、多输出目标或结构化字段**——直接用它做“日志收集”会很快遇到瓶颈。
为什么不能只靠 log.Printf 做日志收集
标准 log 是同步写入、无缓冲、无级别控制的简单封装。常见问题包括:
- 所有日志都走
log.Output,无法区分INFO/ERROR并路由到不同文件或网络端点 - 没有自动按大小/时间切分日志文件的能力,
os.OpenFile+log.SetOutput手动轮转极易丢失日志或并发写冲突 - 无法注入 trace ID、服务名等上下文字段,日志难以关联请求链路
- 格式固定为
[date] [prefix] msg,不兼容 JSON 或 Fluentd 等采集器要求
用 log.SetOutput + io.MultiWriter 实现基础多目标输出
若暂时不能引入第三方库,可组合标准包实现“一份日志同时写文件和 stderr”:
file, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
multi := io.MultiWriter(file, os.Stderr)
log.SetOutput(multi)
注意:
立即学习“go语言免费学习笔记(深入)”;
-
os.Stderr是行缓冲的,file是全缓冲的,混用可能导致日志顺序错乱(尤其 panic 时) - 必须手动处理
file.Close(),否则进程退出时可能丢最后几条日志 - 仍无法过滤级别——所有日志都会流向两个目标
用 log.New 构造带前缀的专用 logger(避免全局污染)
全局 log 不利于模块隔离。推荐每个子系统用独立 logger:
dbLogger := log.New(os.Stdout, "[DB] ", log.LstdFlags|log.Lshortfile) httpLogger := log.New(os.Stdout, "[HTTP] ", log.LstdFlags|log.Lshortfile)
关键点:
- 前缀字符串末尾要带空格,否则
[DB]2024/05/01...会粘连 -
log.Lshortfile开销较大,生产环境慎用;调试阶段可加,上线建议去掉 - 不要在
log.New中传入未同步的io.Writer(如未加锁的bytes.Buffer),并发写会 panic
真正适合“收集”的方案:换用 zap 或 zerolog
标准 log 的定位是“快速打点调试”,不是日志收集系统。实际项目中应切换:
-
zap:高性能、结构化、支持 hooks(可对接 Loki / ES / Kafka) -
zerolog:零内存分配、API 简洁,JSON 输出开箱即用
例如用 zerolog 输出带 trace ID 的 JSON:
log.Logger = log.With().Str("trace_id", "abc123").Logger()
log.Info().Str("event", "user_login").Int("user_id", 1001).Send()
这段输出是标准 JSON 行,可被 Filebeat、Vector 直接解析——这才是日志收集链路的起点。
标准 log 的最大陷阱,是让人误以为“能打印就算完成日志工作”。真正的收集,从输出格式、字段语义、写入可靠性,到采集器兼容性,每一步都绕不开结构化与可扩展性设计。










