The default handler

Much like the older log package, log/slog ships with a “default logger”. You can use this logger without doing any configuration, just by calling any of the package-level logging functions: slog.Error("oh noes!") While you’d probably never want to use the default logger in a serious server application, it can be a convenience for small or throw-away utilities. But how does it work? That’s today’s topic! Overview … The default handler formats the log record’s message, time, level, and attributes as a string and passes it to the log package.


The default handler

Much like the older log package, log/slog ships with a “default logger”. You can use this logger without doing any configuration, just by calling any of the package-level logging functions: slog.Error("oh noes!") While you’d probably never want to use the default logger in a serious server application, it can be a convenience for small or throw-away utilities. But how does it work? That’s today’s topic! Overview … The default handler formats the log record’s message, time, level, and attributes as a string and passes it to the log package.


Intro to slog levels

By default, the log/slog package supports four log levels: Debug, Info, Warn, and Error, each of which has matching logger methods: Overview … The Info top-level function calls the Logger.Info method on the default Logger. In addition to Logger.Info, there are methods for Debug, Warn and Error levels. Besides these convenience methods for common levels, there is also a Logger.Log method which takes the level as an argument. Each of these methods has a corresponding top-level function that uses the default logger.


slog Records and levels

Yesterday we saw that slog uses handlers to process/record logs. But what exactly does it process? Records! Overview … A log record consists of a time, a level, a message, and a set of key-value pairs, where the keys are strings and the values may be of any type. As an example, slog.Info("hello", "count", 3) creates a record containing the time of the call, a level of Info, the message “hello”, and a single pair with key “count” and value 3.


Anatomy of a log/slog logger

Unlike the older log package, which provides a single *log.Logger type as its primary interface, log/slog has a two-tiered architecture. This is roughly the same architecture used by the database/sql package: One interface implements a handler (or “driver” for database/sql), and another interface is consumed. The package itself provides the intermediate translation. This is essentially a localized example of ports-and-adaptors or hexagonal architecture. Here’s how the GoDoc for the package explains it:


Let's talk about logging

I’ve been absent far too long. Most days I think about writing something again, then… I don’t. 🤷‍♂️ I’m going to try to get back into the habit… and this time, I thought I’d talk about one of my favorite packages in the standard library: log/slog. To kick off, I’ll give a general description of the package, then starting tomorrow (I promise! I won’t forget again!) we’ll start into the particulars.


When not to use context values

Storing values in a context is quite flexible, but comes without strict type safety, and obscures the API. So what can we do about this? First, for type safety, we’re limited to runtime assertions: userID := ctx.Value(userIDKey).(string) But this can panic if we ever get an unexpected value (or even nil). So to make it safer, we can use the two-variable assignment form, which yields a bool indicating success: userID, ok := ctx.