Go 语言日志框架对比:Zap / Logrus / Zerolog 性能测试
要在空项目中用 Go 基准测试对比 Zap、Logrus、Zerolog,需将三者统一输出到 io.Discard,并在初始化后、循环前调用 b.ResetTimer,再运行多轮采样,从而获得可信性能数据并据此按场景选择日志框架。
学完这篇,你能自己跑出一份可信的 Go 日志框架性能对比数据,并知道在什么场景下该选 Zap、Logrus 还是 Zerolog。
第一步:搭一个干净的基准测试项目
不要在生产项目里测,那里有中间件、钩子和网络输出,数据会失真。新建一个空目录:
mkdir logbench && cd logbench
go mod init logbench
go get go.uber.org/zap
go get github.com/sirupsen/logrus
go get github.com/rs/zerolog
然后创建 bench_test.go,三个框架写在同一个文件里,保证编译优化、机器负载等条件完全一致。
第二步:统一输出目标——全部丢进 io.Discard
这是最关键的一步。如果三个框架分别写文件、写 stdout,你测的其实是磁盘和终端的速度,不是框架的速度。
package bench
import (
"errors"
"io"
"testing"
"github.com/rs/zerolog"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var msg = "user login failed"
var errVal = errors.New("invalid token")
func BenchmarkZap(b *testing.B) {
core := zapcore.NewCore(
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
zapcore.AddSync(io.Discard),
zapcore.InfoLevel,
)
logger := zap.New(core)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info(msg, zap.String("user", "alice"), zap.Error(errVal))
}
}
func BenchmarkZerolog(b *testing.B) {
logger := zerolog.New(io.Discard)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.Info().Str("user", "alice").Err(errVal).Msg(msg)
}
}
func BenchmarkLogrus(b *testing.B) {
logger := logrus.New()
logger.SetOutput(io.Discard)
logger.SetFormatter(&logrus.JSONFormatter{})
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
logger.WithFields(logrus.Fields{
"user": "alice",
"error": errVal,
}).Info(msg)
}
}
注意:
b.ResetTimer()必须放在初始化之后、循环之前。否则 logger 的构建开销会被算进每次迭代,Zap 这种初始化较重的库会被"冤枉"。
第三步:跑起来,并且多个样本
go test -bench=. -benchmem -count=5 -run=^$ | tee result.txt
-run=^$ 确保不跑普通单元测试;-count=5 跑 5 轮,因为单次结果抖动可能超过 10%。
有条件的话装个 benchstat 做统计对比,比肉眼靠谱:
go install golang.org/x/perf/cmd/benchstat@latest
benchstat result.txt
注意:测试时关掉 IDE 的实时索引、Docker 容器、同步网盘。笔记本还要插电——省电模式会把 CPU 降频,跑出来的 ns/op 直接翻倍。
第四步:看结果,关注量级而不是小数点
在一台普通的 x86 服务器 / 开发机上,典型结果大致是这样的量级(不同机器绝对值会有差异,重点是数量级):
| 框架 | ns/op | allocs/op |
|---|---|---|
| Zap (JSON) | 约 30–60 | 0–1 |
| Zerolog (JSON) | 约 40–80 | 0–1 |
| Logrus (JSON) | 约 1000–1600 | 15–25 |
结论很清楚:Zap 和 Zerolog 属于同一梯队,都比 Logrus 快一个数量级以上,差距主要来自 Logrus 每次 WithFields 都会创建 logrus.Fields map 并触发大量堆分配。日记十万条日志的场景,这个差距就是 GC 的压力。
第五步:加测三个"真实场景"变量
只测裸调用会误导选型,再补三个维度:
- 开启调用者信息。Zap 用
zap.AddCaller(),Zerolog 用logger.With().Caller().Logger()。开启后两者都会明显变慢(栈回溯很贵),如果你需要定位行号,务必重新测一遍。 - Text 格式 vs JSON 格式。Logrus 的 TextFormatter、Zap 的 ConsoleEncoder 走的是另一条代码路径,性能表现不一样。
slog。Go 1.21 之后标准库自带结构化日志,加进来当参照系,如果它对你就够用,可能连第三方库都不用引。
注意:不要在循环里用
fmt.Sprintf拼字符串再传给 logger。这样测出来的是 fmt 的分配开销,会把三个框架的差异抹平。
第六步:按结果选型
- 追求吞吐、结构化日志、要采样和多样输出:选 Zap,生态成熟,
SugaredLogger兼顾性能和易用。 - 极度在意 GC、热路径日志量大:选 Zerolog,链式 API 设计目标就是零分配。
- 代码库已经在用 Logrus:不必为了性能全量重写,先把 QPS 最高的那几条热路径替换掉,剩下的保持不动。
- 新项目、需求简单:先看标准库
slog,不够用再上第三方。
小结
- 对比测试的前提是三个框架用同一份代码、同一个
io.Discard输出、同一台机器。 b.ResetTimer()位置、-benchmem、-count=5是拿到可信数据的三个必要条件。- 看数量级不看小数:Zap / Zerolog 比 Logrus 快一个数量级以上,前者差距来自 map 分配。
- 开启 caller、切换 formatter 后必须重测,结论可能变化。
- 选型以业务热路径为准,不必为基准数据全量重构。
转载请注明出处,版权归原作者所有。
正式会员






