# Packages
# README
slogexamples
slogexamples
is a collection of examples showing how to extend slog. They are a follow up to this blog post on the Anchorage Digital blog, showing some of the techniques for extending slog
mentioned there.
All of these examples stay as close as possible to the 0 allocations goal of slog
. Feel free to copy paste them, modify them, and mix them together to build your own closed or open source augmented loggers.
The docs have the usage examples and API docs rendered nicely. Navigate into each package directory to see the code and the usage examples.
Hooking into io.Writer
testoutputter shows how to intercept the logger's calls to the underlying io.Writer and do something useful. It sends all logs to t.Log()
, which ensures that test output is readable when using parallel tests, subtests or when one test of many fails.
One limitation of most attempts to use t.Log()
with slog is that the correct call site can't be printed. See this issue for more details. The only way to correctly redirect logs to t.Log()
is to use a wrapper around slog that calls t.Log()
outside slog's code. testoutputter2 provides an exampleof this, using a wrapper around slog to do this. There are some obvious downsides of this approach, so I would personally prefer wrong line numbers over the testoutputter2
solution.
Wrapping slog
ctxslog is an example of a slog wrapper. It forces the caller to pass the context in every logger call. This is a more restricted way to use slog, but it's slightly more convenient to use in codebases where the context is expected to be passed everywhere and tracing or cancelation is very important.
ctxslog2 is an even more restrictive wrapper that forces the logger itself to be passed through the context. It hides all direct access to the logger and requires the user to call functions of the package rather than logger methods. This looks and feels like using a global logger instance, but the logger is actually in the context, which is better than a global logger because it can be faked in tests.
Custom slog.Handler
otelhandler is an example of a handler that acts as a middleware and adds additional attributes to log entries. In particular, it adds TraceID
and SpanID
to logs emitted within the context of an open telemetry trace. This allows for correlating logs and traces sent to different systems. See the original blog post for screenshots of what this looks like in Google Cloud.
There's a lot more to writing custom handlers than what's shown here. This guide, from the author of slog, is very helpful. Also, see the testerrorer2
example, explained below, for an easy way to implement a handler.
Hooking into slog.HandlerOptions.ReplaceAttr
testerrorer hooks into the slog.TextHandler
's ReplaceAttr
callback. This function is called on every attribute before it's formatted for rendering and testerrorer
uses the opportunity to check if anything is logged at error level and fail the test if so.
One limitation is that it looks at all attributes with the type slog.Level
that are logged rather than just the level of the log. This is to make sure that if the level attribute is renamed by another ReplaceAttr
function, that doesn't break the functionality. The down side is that a call like logger.Info("example", "l", slog.LevelError)
will cause the test to error incorrectly. The implementation can be trivially modified to look for the level attributed based on its name instead if the name is considered more stable.
testerrorer2 doesn't have the same limitation because it's implemented as a handler. The benefit of this approach is that the handler can access all the information about the log entry, not just a single attribute at a time. The down side is that implementing a handler is normally much more complex. This example uses the slogevent package to show an easy way to skip the boring parts and just implement the custom logic of the handler. It trades off performance for a simple API.