The default handler

April 7, 2026

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.

2022/11/08 15:28:26 INFO hello count=3

So that’s doubly convenient—not only does it log things in a reasonable format, but it also passes them to the log package for backward compatibility. This means that if your application already takes advantage of the default log package’s logger, you don’t need to do any additional configuration to start using log/slog. Sweet!

But wait… how does the default log package’s logger work? Well, it’s pretty limited (which is, of course, largely why log/slog was added to replace it!) By default, it simply writes to STDERR. Though you can configure the default log.Logger… a little bit—mainly by redirecting where those logs go (e.g. to a file). But that’s a good segue into the default logger configuration we do care about—you can also configure the log/slog default logger, which we’ll talk about tomorrow!


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


The global logger

I’m going to do something I haven’t done before in these stdlib tours, and that is skip over a huge section of the GoDoc. That’s becase a huge section here is entirely redundant with what comes later: func Debug func Debug(msg string, args ...any) Debug calls Logger.Debug on the default logger. And we have virtually identical entries for each of the following: DebugContext Error ErrorContext Info InfoContext Log LogAttrs Warn WarnContext Each of these is simply an alias to the identically named method on the default logger.


Test logging with ReplaceAttr

Today’s topic is probably the most interesting, subtle, and complicated of the three HandlerOptions fields: ReplaceAttr. type HandlerOptions type HandlerOptions struct { … // ReplaceAttr is called to rewrite each non-group attribute before it is logged. // The attribute's value has been resolved (see [Value.Resolve]). // If ReplaceAttr returns a zero Attr, the attribute is discarded. // // The built-in attributes with keys "time", "level", "source", and "msg" // are passed to this function, except that time is omitted // if zero, and source is omitted if AddSource is false.


Handler level

Each handler has an optional Level value, which controls which logs to drop, and which to actually log: type HandlerOptions type HandlerOptions struct { … // Level reports the minimum record level that will be logged. // The handler discards records with lower levels. // If Level is nil, the handler assumes LevelInfo. // The handler calls Level.Level for each record processed; // to adjust the minimum level dynamically, use a LevelVar.

Get daily content like this in your inbox!

Subscribe