MCPcopy Index your code
hub / github.com/go-logr/logr

github.com/go-logr/logr @v1.4.3

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.4.3 ↗ · + Follow
428 symbols 1,539 edges 41 files 140 documented · 33% 728 cross-repo links
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

A minimal logging API for Go

Go Reference Go Report Card OpenSSF Scorecard

logr offers an(other) opinion on how Go programs and libraries can do logging without becoming coupled to a particular logging implementation. This is not an implementation of logging - it is an API. In fact it is two APIs with two different sets of users.

The Logger type is intended for application and library authors. It provides a relatively small API which can be used everywhere you want to emit logs. It defers the actual act of writing logs (to files, to stdout, or whatever) to the LogSink interface.

The LogSink interface is intended for logging library implementers. It is a pure interface which can be implemented by logging frameworks to provide the actual logging functionality.

This decoupling allows application and library developers to write code in terms of logr.Logger (which has very low dependency fan-out) while the implementation of logging is managed "up stack" (e.g. in or near main().) Application developers can then switch out implementations as necessary.

Many people assert that libraries should not be logging, and as such efforts like this are pointless. Those people are welcome to convince the authors of the tens-of-thousands of libraries that DO write logs that they are all wrong. In the meantime, logr takes a more practical approach.

Typical usage

Somewhere, early in an application's life, it will make a decision about which logging library (implementation) it actually wants to use. Something like:

    func main() {
        // ... other setup code ...

        // Create the "root" logger.  We have chosen the "logimpl" implementation,
        // which takes some initial parameters and returns a logr.Logger.
        logger := logimpl.New(param1, param2)

        // ... other setup code ...

Most apps will call into other libraries, create structures to govern the flow, etc. The logr.Logger object can be passed to these other libraries, stored in structs, or even used as a package-global variable, if needed. For example:

    app := createTheAppObject(logger)
    app.Run()

Outside of this early setup, no other packages need to know about the choice of implementation. They write logs in terms of the logr.Logger that they received:

    type appObject struct {
        // ... other fields ...
        logger logr.Logger
        // ... other fields ...
    }

    func (app *appObject) Run() {
        app.logger.Info("starting up", "timestamp", time.Now())

        // ... app code ...

Background

If the Go standard library had defined an interface for logging, this project probably would not be needed. Alas, here we are.

When the Go developers started developing such an interface with slog, they adopted some of the logr design but also left out some parts and changed others:

Feature logr slog
High-level API Logger (passed by value) Logger (passed by pointer)
Low-level API LogSink Handler
Stack unwinding done by LogSink done by Logger
Skipping helper functions WithCallDepth, WithCallStackHelper not supported by Logger
Generating a value for logging on demand Marshaler LogValuer
Log levels >= 0, higher meaning "less important" positive and negative, with 0 for "info" and higher meaning "more important"
Error log entries always logged, don't have a verbosity level normal log entries with level >= LevelError
Passing logger via context NewContext, FromContext no API
Adding a name to a logger WithName no API
Modify verbosity of log entries in a call chain V no API
Grouping of key/value pairs not supported WithGroup, GroupValue
Pass context for extracting additional values no API API variants like InfoCtx

The high-level slog API is explicitly meant to be one of many different APIs that can be layered on top of a shared slog.Handler. logr is one such alternative API, with interoperability provided by some conversion functions.

Inspiration

Before you consider this package, please read [this blog post by the inimitable Dave Cheney][warning-makes-no-sense]. We really appreciate what he has to say, and it largely aligns with our own experiences.

Differences from Dave's ideas

The main differences are:

  1. Dave basically proposes doing away with the notion of a logging API in favor of fmt.Printf(). We disagree, especially when you consider things like output locations, timestamps, file and line decorations, and structured logging. This package restricts the logging API to just 2 types of logs: info and error.

Info logs are things you want to tell the user which are not errors. Error logs are, well, errors. If your code receives an error from a subordinate function call and is logging that error and not returning it, use error logs.

  1. Verbosity-levels on info logs. This gives developers a chance to indicate arbitrary grades of importance for info logs, without assigning names with semantic meaning such as "warning", "trace", and "debug." Superficially this may feel very similar, but the primary difference is the lack of semantics. Because verbosity is a numerical value, it's safe to assume that an app running with higher verbosity means more (and less important) logs will be generated.

Implementations (non-exhaustive)

There are implementations for the following logging libraries:

  • a function (can bridge to non-structured libraries): funcr
  • a testing.T (for use in Go tests, with JSON-like output): testr
  • github.com/google/glog: glogr
  • k8s.io/klog (for Kubernetes): klogr
  • a testing.T (with klog-like text output): ktesting
  • go.uber.org/zap: zapr
  • log (the Go standard library logger): stdr
  • github.com/sirupsen/logrus: logrusr
  • github.com/wojas/genericr: genericr (makes it easy to implement your own backend)
  • logfmt (Heroku style logging): logfmtr
  • github.com/rs/zerolog: zerologr
  • github.com/go-kit/log: gokitlogr (also compatible with github.com/go-kit/kit/log since v0.12.0)
  • bytes.Buffer (writing to a buffer): bufrlogr (useful for ensuring values were logged, like during testing)

slog interoperability

Interoperability goes both ways, using the logr.Logger API with a slog.Handler and using the slog.Logger API with a logr.LogSink. FromSlogHandler and ToSlogHandler convert between a logr.Logger and a slog.Handler. As usual, slog.New can be used to wrap such a slog.Handler in the high-level slog API.

Using a logr.LogSink as backend for slog

Ideally, a logr sink implementation should support both logr and slog by implementing both the normal logr interface(s) and SlogSink. Because of a conflict in the parameters of the common Enabled method, it is not possible to implement both slog.Handler and logr.Sink in the same type.

If both are supported, log calls can go from the high-level APIs to the backend without the need to convert parameters. FromSlogHandler and ToSlogHandler can convert back and forth without adding additional wrappers, with one exception: when Logger.V was used to adjust the verbosity for a slog.Handler, then ToSlogHandler has to use a wrapper which adjusts the verbosity for future log calls.

Such an implementation should also support values that implement specific interfaces from both packages for logging (logr.Marshaler, slog.LogValuer, slog.GroupValue). logr does not convert those.

Not supporting slog has several drawbacks: - Recording source code locations works correctly if the handler gets called through slog.Logger, but may be wrong in other cases. That's because a logr.Sink does its own stack unwinding instead of using the program counter provided by the high-level API. - slog levels <= 0 can be mapped to logr levels by negating the level without a loss of information. But all slog levels > 0 (e.g. slog.LevelWarning as used by slog.Logger.Warn) must be mapped to 0 before calling the sink because logr does not support "more important than info" levels. - The slog group concept is supported by prefixing each key in a key/value pair with the group names, separated by a dot. For structured output like JSON it would be better to group the key/value pairs inside an object. - Special slog values and interfaces don't work as expected. - The overhead is likely to be higher.

These drawbacks are severe enough that applications using a mixture of slog and logr should switch to a different backend.

Using a slog.Handler as backend for logr

Using a plain slog.Handler without support for logr works better than the other direction: - All logr verbosity levels can be mapped 1:1 to their corresponding slog level by negating them. - Stack unwinding is done by the SlogSink and the resulting program counter is passed to the slog.Handler. - Names added via Logger.WithName are gathered and recorded in an additional attribute with logger as key and the names separated by slash as value. - Logger.Error is turned into a log record with slog.LevelError as level and an additional attribute with err as key, if an error was provided.

The main drawback is that logr.Marshaler will not be supported. Types should ideally support both logr.Marshaler and slog.Valuer. If compatibility with logr implementations without slog support is not important, then slog.Valuer is sufficient.

Context support for slog

Storing a logger in a context.Context is not supported by slog. NewContextWithSlogLogger and FromContextAsSlogLogger can be used to fill this gap. They store and retrieve a slog.Logger pointer under the same context key that is also used by NewContext and FromContext for logr.Logger value.

When NewContextWithSlogLogger is followed by FromContext, the latter will automatically convert the slog.Logger to a logr.Logger. FromContextAsSlogLogger does the same for the other direction.

With this approach, binaries which use either slog or logr are as efficient as possible with no unnecessary allocations. This is also why the API stores a slog.Logger pointer: when storing a slog.Handler, creating a slog.Logger on retrieval would need to allocate one.

The downside is that switching back and forth needs more allocations. Because logr is the API that is already in use by different packages, in particular Kubernetes, the recommendation is to use the logr.Logger API in code which uses contextual logging.

An alternative to adding values to a logger and storing that logger in the context is to store the values in the context and to configure a logging backend to extract those values when emitting log entries. This only works when log calls are passed the context, which is not supported by the logr API.

With the slog API, it is possible, but not required. https://github.com/veqryn/slog-context is a package for slog which provides additional support code for this approach. It also contains wrappers for the context functions in logr, so developers who prefer to not use the logr APIs directly can use those instead and the resulting code will still be interoperable with logr.

FAQ

Conceptual

Why structured logging?

  • Structured logs are more easily queryable: Since you've got key-value pairs, it's much easier to query your structured logs for particular values by filtering on the contents of a particular key -- think searching request logs for error codes, Kubernetes reconcilers for the name and namespace of the reconciled object, etc.

  • Structured logging makes it easier to have cross-referenceable logs: Similarly to searchability, if you maintain conventions around your keys, it becomes easy to gather all log lines related to a particular concept.

  • Structured logs allow better dimensions of filtering: if you have structure to your logs, you've got more precise control over how much information is logged -- you might choose in a particular configuration to log certain keys but not others, only log lines where a certain key matches a certain value, etc., instead of just having v-levels and names to key off of.

  • Structured logs better represent structured data: sometimes, the data that you want to log is inherently structured (think tuple-link objects.) Structured logs allow you to preserve that structure when outputting.

Why V-levels?

V-levels give operators an easy way to control the chattiness of log operations. V-levels provide a way for a given package to distinguish the relative importance or verbosity of a given log message. Then, if a particular logger or package is logging too many messages, the user of th

Extension points exported contracts — how you extend this code

LogSink (Interface)
LogSink represents a logging implementation. End-users will generally not interact with this type. [5 implementers]
logr.go
Underlier (Interface)
Underlier is implemented by the LogSink returned by NewFromLogHandler. [4 implementers]
slogsink.go
SlogSink (Interface)
SlogSink is an optional interface that a LogSink can implement to support logging through the slog.Logger or slog.Handle [4 …
slogr.go
Underlier (Interface)
Underlier exposes access to the underlying testing.T instance. Since callers only have a logr.Logger, they have to know [4 …
testr/testr.go
Underlier (Interface)
Underlier exposes access to the underlying logging function. Since callers only have a logr.Logger, they have to know wh [4 …
funcr/funcr.go
CallDepthLogSink (Interface)
CallDepthLogSink represents a LogSink that knows how to climb the call stack to identify the original call site and can [5 …
logr.go
UnderlierInterface (Interface)
UnderlierInterface exposes access to the underlying TestingT instance. Since callers only have a logr.Logger, they have [4 …
testr/testr.go
Marshaler (Interface)
Marshaler is an optional interface that logged values may choose to implement. Loggers with structured output, such as J [5 …
logr.go

Core symbols most depended-on inside this repo

Info
called by 112
logr.go
Error
called by 57
logr.go
V
called by 47
logr.go
Run
called by 44
examples/usage_example.go
newSink
called by 35
funcr/funcr.go
New
called by 31
logr.go
ToSlogHandler
called by 30
slogr.go
Discard
called by 29
discard.go

Shape

Function 200
Method 148
Struct 56
TypeAlias 14
Interface 10

Languages

Go100%

Modules by API surface

funcr/funcr_test.go65 symbols
funcr/funcr.go49 symbols
benchmark/benchmark_test.go46 symbols
logr.go29 symbols
testr/testr.go23 symbols
testimpls_slog_test.go21 symbols
benchmark/benchmark_slog_test.go21 symbols
logr_test.go15 symbols
slogsink.go13 symbols
sloghandler.go10 symbols
funcr/example_formatter_test.go10 symbols
testimpls_test.go9 symbols

For agents

$ claude mcp add logr \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact