Skip to content

fix: use slog.DiscardHandler in promslog.NopLogger - #960

Open
FUSAKLA wants to merge 2 commits into
prometheus:mainfrom
FUSAKLA:fus-slog-noop
Open

fix: use slog.DiscardHandler in promslog.NopLogger#960
FUSAKLA wants to merge 2 commits into
prometheus:mainfrom
FUSAKLA:fus-slog-noop

Conversation

@FUSAKLA

@FUSAKLA FUSAKLA commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Investigating huge latencies in pushgateway after upgrade from 1.10 to 1.11 I found out that the cause is the promslog.NopLogger (lets put aside that pushgateway logging might be too excessive)

It turned out that NewNopLogger() returns New(&Config{Writer: io.Discard}) — a regular slog.TextHandler at info level.
Its Enabled() returns true, so slog builds the record, the handler formats every attribute, and only then drops the bytes at io.Discard. For larger attributes this leads to meaningless serialization just to be thrown away.

  • checkWriteRequestprocessWriteRequest runs on every push.
  • It logs logger.Info(..., "new", mf, "old", existingMF) — both *dto.MetricFamily with all their metrics.
  • With a nop logger both get fully formatted on every push and thrown away.

Fix:

Use the slog.DiscardHandler added in Go 1.24 which is really no-op.

⚠️ What would change
  • slog.DiscardHandler needs Go 1.24; this module is on 1.25.0.
    • Might be an issue if someone is using the common lib in older Go?
  • LogValuer implementations with side effects will no longer be called.
  • Code wrapping NewNopLogger() and expecting Enabled() to be true will stop seeing those calls.
Benchmark

Benchmark details — go test ./promslog/ -run XXX -bench BenchmarkNopLogger -benchmem, go1.26.5 linux/amd64, AMD Ryzen AI 7 445 (12 threads). Old is the current implementation, the other one is the patch.

func BenchmarkNopLoggerOld(b *testing.B) {
	logger := New(&Config{Writer: io.Discard}) // current NewNopLogger()
	value := struct{ Field string }{Field: "value"}
	for b.Loop() {
		logger.Info("test", "key", value)
	}
}

func BenchmarkNopLogger(b *testing.B) {
	logger := NewNopLogger() // slog.New(slog.DiscardHandler)
	value := struct{ Field string }{Field: "value"}
	for b.Loop() {
		logger.Info("test", "key", value)
	}
}
goos: linux
goarch: amd64
pkg: github.com/prometheus/common/promslog
cpu: AMD Ryzen AI 7 445 w/ Radeon 840M
BenchmarkNopLoggerOld-12      352051      2890 ns/op     440 B/op    11 allocs/op
BenchmarkNopLogger-12       22288611      48.5 ns/op      16 B/op     1 allocs/op

Signed-off-by: Martin Chodur <m.chodur@seznam.cz>
Signed-off-by: Martin Chodur <m.chodur@seznam.cz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant