The global logger

June 25, 2026

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:

Each of these is simply an alias to the identically named method on the default logger. So instead of going through each, let’s talk about the default logger, then we’ll talk about the respective methods when we get to that portion of the GoDoc.

During the Overview section, we already talked about the existence of the default logger. The default loger is populated and usable by, well, default. You don’t need to do anything to use it:

slog.Info("yay!")

But you may want to change it, to set its level, or where it writes. And you can do that with SetDefault:

SetDefault

func SetDefault(l *Logger)

SetDefault makes l the default Logger, which is used by the top-level functions Info, Debug and so on. After this call, output from the log package’s default Logger (as with log.Print, etc.) will be logged using l’s Handler, at a level controlled by SetLogLoggerLevel.

Notably, calling slog.SetDefault also updates the log package’s default logger. Two birds with one stone!

But this also relates to why I suggest not using the default logger (in either package—log or log/slog)!

Because the default logger can be modified anywhere, it’s unreliable. Even if you’re careful to set the default logger on program start, it’s possible, even if unlikely, that some library you start importing next week will change it. (Possibly even for legitimate-seeming reasons. No ill-will is necessary for this to become problematic.)

For this reason, as well as the plethora other reasons that global instances introduce problems with regard to side effects, testing, and general architecture, my general advice is to avoid using the global logger.


Share this

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

Unsure? Browse the archive .

Related Content


Overriding the default handler

Today I’m going to jump around a bit in the GoDoc, to talk about a topic I mentioned last time: how to override the default logger. Overview … Setting a logger as the default with slog.SetDefault(logger) will cause the top-level functions like Info to use it. SetDefault also updates the default logger used by the log package, so that existing applications that use log.Printf and related functions will send log records to the logger’s handler without needing to be rewritten.


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.


slog Constants

We’ve made it through the overview of the slog documentation. Now it’s time to get down and dirty! First up, constants! There are four, and they all define built-in attribute keys: Constants const ( // TimeKey is the key used by the built-in handlers for the time // when the log method is called. The associated Value is a [time.Time]. TimeKey = "time" // LevelKey is the key used by the built-in handlers for the level // of the log call.

Get daily content like this in your inbox!

Subscribe