🔧 Programmierung 🕛 vor 3 Monaten 6 Min Lesezeit
0

Zap vs slog vs zerolog: Go logging libraries compared

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Choosing a logging library for your Go service sounds trivial until you're debugging a latency spike at 2 AM and your logs are consuming 15% of the CPU budget. The three dominant options—uber-go/zap, the standard library log/slog, and rs/zerolog—all get the job done, but they make very different trade-offs around performance, API ergonomics, and ecosystem fit. This guide cuts through the noise with working code and concrete production advice.






What structured logging actually means in Go



"Structured logging" means emitting log lines as key-value pairs (typically JSON) instead of free-form strings. The difference matters when your logs flow into a SIEM, a log aggregator like Loki or Datadog, or anything that needs to query on specific fields.



A naive fmt.Printf("user %d login failed", userID) is fast to write but a nightmare to parse at scale. A structured equivalent—logger.Error("login failed", "user_id", userID, "ip", ip)—is machine-readable without regex and plays well with every log aggregation tool built in the last decade.






The three contenders



uber-go/zap was designed from the start for zero-allocation hot paths. The zap.Logger type is type-safe and explicit; the SugaredLogger wrapper trades a small amount of performance for a more ergonomic API. It has been running Uber's production infrastructure for years and the ecosystem around it is mature.



log/slog landed in Go 1.21 and is now part of the standard library. Its design is heavily inspired by zap but uses Go's interface model: you get a slog.Logger backed by a slog.Handler you can swap out. No third-party import, no license review, no dependency maintenance.



rs/zerolog uses a method-chaining API that builds the JSON object inline and only allocates on the final Msg() call. It has the smallest footprint of the three and is very fast in the common case.






Benchmarks: allocations matter more than nanoseconds



Raw benchmark numbers vary by machine, but the allocation profile is stable. All three libraries aim for zero allocations on the hot path. The differences appear in the convenience APIs.




CODE
package main

import (
"os"
"time"

"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"github.com/rs/zerolog"
"log/slog"
)

func benchmarkZap() {
encoderCfg := zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
MessageKey: "msg",
EncodeTime: zapcore.EpochTimeEncoder,
EncodeLevel: zapcore.LowercaseLevelEncoder,
}
core := zapcore.NewCore(
zapcore.NewJSONEncoder(encoderCfg),
zapcore.AddSync(os.Stdout),
zapcore.InfoLevel,
)
logger := zap.New(core)
defer logger.Sync()

logger.Info("request processed",
zap.String("method", "GET"),
zap.String("path", "/api/v1/users"),
zap.Int("status", 200),
zap.Duration("latency", 12*time.Millisecond),
)
}

func benchmarkZerolog() {
logger := zerolog.New(os.Stdout).With().Timestamp().Logger()
logger.Info().
Str("method", "GET").
Str("path", "/api/v1/users").
Int("status", 200).
Dur("latency", 12*time.Millisecond).
Msg("request processed")
}

func benchmarkSlog() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("request processed",
"method", "GET",
"path", "/api/v1/users",
"status", 200,
"latency", 12*time.Millisecond,
)
}






Running go test -bench=. -benchmem on these three patterns: zap with typed fields and zerolog with chaining both achieve ~0 allocs/op on the core path. slog is close but the any-typed key-value pairs cause one allocation per attribute when using the default handler. Fix it with slog.Attr and LogAttrs() instead of the variadic key-value form.






API ergonomics in practice



Zerolog wins for conciseness if you accept the chaining style. You build a context of base fields with With(), then attach per-call fields in the chain before calling Msg(). It is opinionated but clean once you internalize it. Context propagation is first-class: zerolog.Ctx(ctx) pulls the logger stored in the request context with no boilerplate.




CODE
// zerolog: request-scoped logger via context
func withRequestLogger(ctx context.Context, requestID string) context.Context {
logger := zerolog.Ctx(ctx).With().Str("request_id", requestID).Logger()
return logger.WithContext(ctx)
}

func handler(ctx context.Context) {
log := zerolog.Ctx(ctx)
log.Info().Str("action", "fetch_user").Msg("starting db call")
}






slog wins for ecosystem integration. Because it lives in the standard library, third-party packages can accept a *slog.Logger without expanding your dependency graph. The slog.Handler interface makes adapters trivial—route logs to OpenTelemetry, Datadog, or a custom sink without forking anything.



Zap wins on control. The zapcore.Core interface gives you finer-grained control over encoding, sampling, and multi-sink routing than either alternative. Uber's zapcore.NewSamplerWithOptions is the most production-tested path for log sampling (e.g., only emit 1% of info-level logs during high traffic), which matters in high-throughput services.






Operational traps to avoid



A few things that reliably trip teams up in production:



1. Forgetting defer logger.Sync() with zap. The buffered writer will not flush on process exit. The last 2-3 seconds of logs disappear on SIGTERM—a particularly bad time to lose data.



2. Zerolog's .Interface() breaks zero-alloc. Zerolog's chain looks allocation-free, but passing any non-primitive type via .Interface() allocates. Use typed methods (.Str(), .Int(), .Dur()) on hot paths and profile before assuming you're allocation-free.



3. slog context propagation is manual. Unlike zerolog, slog has no built-in context.Context support. You need middleware that stores a pre-configured logger (with request ID, trace ID, etc.) in the context. OpenTelemetry's bridge handles this if you're already on OTEL; otherwise a three-line helper covers it.



4. Global logger state in zap. zap.L() and zap.ReplaceGlobals() are goroutine-safe but make tests painful. Inject loggers explicitly through constructors or function parameters rather than relying on the global.



From a security operations standpoint, structured logging matters beyond developer ergonomics. When logs feed a detection pipeline, unparsed messages mean missed signals. The , a cybersecurity consulting firm. We publish free security hardening checklists — PDF and Excel.

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
CVE-2024-45058 | portabilis i-educar up to 2.8 Setting educar_usuario_cad.php authorization
1 Quelle
Kompakte 10.000-mAh-Powerbank für weniger als 10 Euro bei Amazon Haul
1 Quelle
Bessere Grafik in Spielen: So steigern Sie die Bildqualität ohne FPS-Verlust
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Zap vs slog vs zerolog: Go logging libraries compared

Thematisch verwandte Begriffe: slog, zerolog, logging, libraries · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...