logr

A minimal, backend-agnostic structured logging API for Go, letting libraries log without coupling to any specific logging implementation.

Library
Go
vv1.4.4
1,407 stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum. How we score it →
70 /100 Good
Development Activity 72
Maintenance 48
Community 60
Maturity 60
Momentum 40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation. How we score it →
84 /100 Excellent
Architecture 85
Code Quality 88
Innovation 72
Learning Curve 90

logr defines a small, stable logging interface that Go libraries and applications can depend on without committing to a concrete logging implementation. Rather than shipping its own logging engine, logr splits the problem into two APIs: the Logger type, a value-passed struct that application and library authors call directly (Info, Error, WithValues, WithName, V), and the LogSink interface, which backend authors implement to route those calls to an actual logging framework such as zap, klog, logrus, or the standard library’s slog.

This decoupling means a library can accept and use a logr.Logger internally with almost no dependency fan-out, while the application assembling that library chooses (and can later swap) which concrete logger backs it, near main(). The package is best known as the logging abstraction underpinning Kubernetes and its controller-runtime ecosystem, where dozens of controllers and operators log through logr.Logger while cluster operators plug in whichever backend fits their observability stack.

Beyond the core interface, the module ships funcr (a formatter that turns key/value pairs into text or JSON via an arbitrary write function) and testr (a sink that routes log output through testing.T for use inside Go tests), plus first-class two-way conversion helpers (FromSlogHandler, ToSlogHandler) for interoperating with Go’s standard-library slog package.

What You Get

  • A concrete, pass-by-value Logger type with Info, Error, V (verbosity), WithValues, and WithName methods for structured key/value logging
  • The LogSink interface contract that backend implementers fulfill, plus optional extension interfaces (CallDepthLogSink, CallStackHelperLogSink) for accurate call-site attribution
  • Bundled funcr package for building text or JSON-formatted sinks from any write function, with support for logr.Marshaler, fmt.Stringer, and error
  • Bundled testr package for routing log output through testing.T/testing.B, ideal for asserting on logs in unit tests
  • Bidirectional slog interoperability via FromSlogHandler/ToSlogHandler and context helpers (NewContextWithSlogLogger/FromContextAsSlogLogger)
  • Context propagation helpers (NewContext/FromContext) for carrying a Logger through context.Context
  • A documented, stable public API protected by CI-enforced apidiff checks against breaking changes

Common Use Cases

  • Library authors who want to emit logs without forcing every consumer onto a specific logging framework
  • Kubernetes controllers and operators built on controller-runtime, which accept logr.Logger throughout their reconciliation code
  • Applications that want to swap logging backends (zap, klog, logrus, zerolog, slog) without touching call sites scattered across the codebase
  • Projects migrating toward Go’s standard log/slog package that need a bridge for code still written against logr.Logger
  • Test suites that want log output captured and asserted against testing.T via testr

Under The Hood

Architecture

logr.go defines two cooperating layers: Logger, a small pass-by-value struct wrapping a LogSink and a verbosity level, and LogSink, the interface a backend must implement (Init, Enabled, Info, Error, WithValues, WithName). Every Logger method (Info, Error, V, WithValues, WithName, WithCallDepth) is a thin, nil-safe forwarder onto the sink, with optional capability interfaces (CallDepthLogSink, CallStackHelperLogSink, SlogSink) detected via type assertion so backends can opt into richer behavior (accurate call-site depth, slog compatibility) without the core interface growing. funcr.go and testr.go are self-contained LogSink implementations layered on top of this contract, demonstrating the intended extension pattern for real backends (zapr, klogr, stdr, etc., which live in sibling go-logr repos). Because Logger is a value type passed by copy, the design deliberately keeps state minimal (a sink pointer/interface plus an int) so it can be threaded through call graphs and structs cheaply.

Tech Stack

The module targets Go 1.18+ with zero non-stdlib runtime dependencies in the core package (go.mod lists only the module path and Go version). funcr and the slog bridge (sloghandler.go, context_slog.go) use only encoding/json, log/slog, reflect, and other standard-library packages. Sibling implementations (zapr, klogr, stdr, zerologr) live in separate go-logr repositories and are not dependencies of this module — logr itself stays deliberately dependency-free so it can be imported by anything without dragging in a concrete logging stack.

Code Quality

The repo has extensive test coverage: logr_test.go, context_test.go, context_slog_test.go, slogr_test.go, plus per-package tests for funcr, testr (including a fuzz test, testr_fuzz_test.go), and testing. CI (.github/workflows/tests.yaml, lint.yaml) runs the test suite and a strict golangci-lint configuration (errcheck, govet, staticcheck, revive, gocritic, dupl, and more, zero-tolerance issue limits) on every change. An apidiff.yaml workflow guards the public API against accidental breaking changes across releases, and an OpenSSF Scorecard workflow tracks supply-chain security posture. Error handling is explicit throughout — sink methods and helpers check nil sinks defensively rather than panicking.

What Makes It Unique

logr’s distinguishing choice is scope: it is intentionally just an interface, not a logging implementation, with an unusually small and stable surface area enforced by automated API-diff checks. Its two-tier Logger/LogSink split lets it stay decoupled from any backend while still supporting advanced needs (call-stack skipping for helper functions, per-callsite verbosity, marshaler-based lazy value formatting) through optional capability interfaces rather than a bloated core API. The package’s own documentation explicitly maps its design decisions against Go’s standard-library slog (adopted years later and partly inspired by logr), and ships first-class two-way conversion so codebases can migrate incrementally rather than in one break.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers