Lesson 26 of 30 intermediate 4 min read

Before this:The standard library

Logging with slog

Key takeaways log/slog is Go’s standard structured logging package. Instead of formatting values into a sentence, you attach them as key/value attributesslog.Info("locked", "freq_hz", f, "snr", s). It supports levels (Debug, Info, Warn, Error) and can emit human-readable text or JSON for log aggregators. A running service logs this way so its output can be filtered and searched, not just read.

A quick program can print. A service that runs for days needs logs it can query. This lesson covers log/slog, part of the standard library since Go 1.21.

Structured, not formatted

The old habit is to build a sentence:

log.Printf("locked on %.4f MHz at %.1f dB SNR", freq/1e6, snr)  // one opaque string

slog records the same facts as separate, typed fields:

slog.Info("signal locked",
    "freq_hz", freq,
    "snr", snr,
    "protocol", "P25",
)

Each key/value is preserved, so a log system can filter protocol=P25 or graph snr over time. That is the whole point of structured logging: the data stays data.

Levels

slog has four built-in levels, and a logger can be set to drop anything below a threshold — so you leave Debug lines in the code and simply raise the level in production.

Level Use it for
slog.Debug Detailed tracing, off in production
slog.Info Normal events — startup, a lock acquired
slog.Warn Something unexpected but recoverable
slog.Error A failure that needs attention
slog.Debug("fft frame", "bins", 4096)     // hidden unless level <= Debug
slog.Error("decode failed", "err", err)   // always shown

Text or JSON output

slog separates what you log from how it’s formatted. Choose a handler:

// Human-readable text (development):
h := slog.NewTextHandler(os.Stderr, nil)

// Machine-readable JSON (production / aggregators):
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})

slog.SetDefault(slog.New(h))

The JSON handler emits one object per line — exactly what a log collector wants. You can also attach fields once with logger.With("protocol", "P25") so every subsequent line carries them, ideal for a per-channel logger in a scanner. You’ll see the same structured output feed dashboards in Logging & health checks.

Quick check: what makes slog output "structured"?

Recap

  • log/slog logs key/value attributes, keeping data queryable instead of formatted into a string.
  • It has four levels — Debug, Info, Warn, Error — with a settable threshold.
  • Swap handlers for text (dev) or JSON (production/aggregators).
  • logger.With(...) attaches persistent fields to every line.

Next up: how a real Go repository is laid out — cmd, internal, and packages.

Frequently asked questions

What makes slog "structured" logging?

Instead of baking values into a sentence, slog records them as key/value pairs: slog.Info(“locked”, “freq_hz”, 460025000, “snr”, 19.7). Each field stays a separate, typed attribute, so a log system can filter, search, and aggregate on freq_hz or snr directly — something you can’t reliably do with a formatted string.

Why not just use fmt.Println or the old log package?

For a quick script, Println is fine. But a long-running service needs log levels to control verbosity, machine-readable output for log aggregators, and consistent fields across every line. slog gives all three from the standard library, which is why it’s the default choice for services.