⭐ 推荐:社区规则条款 V1.0

Go 语言日志框架对比:Zap / Logrus / Zerolog 性能测试

dp32323
dp32323 正式会员正式会员
发布于 2026-10-14 20:19 ·2 浏览 ·0 回复
内容摘要

要在空项目中用 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/opallocs/op
Zap (JSON)约 30–600–1
Zerolog (JSON)约 40–800–1
Logrus (JSON)约 1000–160015–25

结论很清楚:Zap 和 Zerolog 属于同一梯队,都比 Logrus 快一个数量级以上,差距主要来自 Logrus 每次 WithFields 都会创建 logrus.Fields map 并触发大量堆分配。日记十万条日志的场景,这个差距就是 GC 的压力。

第五步:加测三个"真实场景"变量

只测裸调用会误导选型,再补三个维度:

  1. 开启调用者信息。Zap 用 zap.AddCaller(),Zerolog 用 logger.With().Caller().Logger()。开启后两者都会明显变慢(栈回溯很贵),如果你需要定位行号,务必重新测一遍。
  2. Text 格式 vs JSON 格式。Logrus 的 TextFormatter、Zap 的 ConsoleEncoder 走的是另一条代码路径,性能表现不一样。
  3. slog。Go 1.21 之后标准库自带结构化日志,加进来当参照系,如果它对你就够用,可能连第三方库都不用引。

注意:不要在循环里用 fmt.Sprintf 拼字符串再传给 logger。这样测出来的是 fmt 的分配开销,会把三个框架的差异抹平。

第六步:按结果选型

  • 追求吞吐、结构化日志、要采样和多样输出:选 Zap,生态成熟,SugaredLogger 兼顾性能和易用。
  • 极度在意 GC、热路径日志量大:选 Zerolog,链式 API 设计目标就是零分配。
  • 代码库已经在用 Logrus:不必为了性能全量重写,先把 QPS 最高的那几条热路径替换掉,剩下的保持不动。
  • 新项目、需求简单:先看标准库 slog,不够用再上第三方。

小结

  1. 对比测试的前提是三个框架用同一份代码、同一个 io.Discard 输出、同一台机器。
  2. b.ResetTimer() 位置、-benchmem、-count=5 是拿到可信数据的三个必要条件。
  3. 看数量级不看小数:Zap / Zerolog 比 Logrus 快一个数量级以上,前者差距来自 map 分配。
  4. 开启 caller、切换 formatter 后必须重测,结论可能变化。
  5. 选型以业务热路径为准,不必为基准数据全量重构。
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-830.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~