标准库 log 仅适用于本地 cli、单元测试或短时 job;生产环境必须用 zap/zerolog,因 log 会引发性能瓶颈和运维盲点;logrus 已归档不建议新项目使用;zap 是事实标准,因其零分配、结构化、易集成等优势。

log 适合原型验证或单机小工具;生产服务、高并发 API、需结构化日志的场景,必须用 zap 或 zerolog —— 不是“更好用”,而是 log 在这些场景下会成为性能瓶颈和运维盲点。
什么时候还能用标准库 log?
仅限以下情况:
- 本地 CLI 工具(如 go run main.go 一次性脚本)
- 单元测试中的临时日志输出(t.Log() 更合适)
- 容器内只运行几秒的 Job 类任务,且不依赖日志排查问题
- 你明确接受:无日志级别控制、无法异步写入、每条日志都触发一次系统调用、GC 压力随日志量线性增长
logrus 现在还能不能上新项目?
不建议。关键事实:
- 官方仓库自 2023 年起已归档(archived),不再接受 PR、不修复 CVE
- 虽然仍能 go get,但其 Hook 机制与现代日志采集链路(如 filebeat → Kafka → Loki)耦合重、扩展难
- 日志字段序列化依赖反射,高并发下易成 CPU 热点(对比 zap 的预分配 encoder)
- 如果已有老项目在用,可继续维护;但新项目请直接跳过
为什么 zap 是当前生产环境事实标准?
它解决的是真实部署痛点,不是纸面参数:
- zap.NewProduction() 默认启用 JSON 编码 + 时间戳 + 调用栈裁剪 + sync.Pool 缓冲复用,开箱即接入 promtail/fluentd
- logger.With() 返回新 logger,支持 request-scoped 上下文注入(比如自动带 req_id、user_id),避免到处传参
- logger.Debug() 在非 Debug 模式下**零分配、零字符串拼接**,而 logrus.Info("user=", u.ID, "action=", a) 每次都触发 fmt.Sprint
- 支持 WriteSyncer 接口,可无缝对接 lumberjack.Logger 实现轮转,或自定义写入 /dev/stdout + systemd-journald 标签
package main
<p>import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)</p><p>func main() {
w := zapcore.AddSync(&lumberjack.Logger{
Filename: "/var/log/myapp/app.log",
MaxSize: 100, // MB
MaxBackups: 7,
MaxAge: 28, // days
})
core := zapcore.NewCore(
zapcore.NewJSONEncoder(zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
MessageKey: "msg",
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
}),
w,
zapcore.InfoLevel,
)
logger := zap.New(core)
defer logger.Sync()</p><pre class='brush:php;toolbar:false;'>logger.Info("service started", zap.String("version", "v1.2.0"))}
容易被忽略的部署细节
Linux 下日志路径和权限常导致静默失败:
- 不要硬写 /var/log/xxx.log,除非你的 systemd service 文件里明确配置了 ReadWritePaths=/var/log/xxx
- 容器中优先用 os.Stdout,靠 docker logs 或 kubectl logs 统一收集;轮转交给宿主机日志代理
- zap 的 logger.Sync() 必须调用(尤其进程退出前),否则最后几条日志可能丢失 —— 这个坑在压测时才暴露
- 若用 zerolog,注意它默认不输出时间戳,需手动加 .Timestamp(),否则 loki 查询会丢时间维度










