Free — no signup required

Implementing Semantic Logging

2 min read

Choosing the Right Library

Most modern logging libraries support structured output natively. The key capability to look for is the ability to attach key-value fields to a log call alongside a static message string.

The pattern is consistent across every major ecosystem, differing only in call syntax: Python's structlog replaces an f-string interpolating order ID, time, and currency into a sentence with a call passing a static event name ("order_processed") plus typed keyword fields. Go's log/slog (standard library since Go 1.21) or zap replaces a Printf-style format string the same way — a static event name followed by alternating key/value arguments. Node.js's pino takes the fields as an object argument and the static event name as a second, separate argument. In every case, the shape is the same: a fixed string identifying what kind of event this is, and a set of typed fields carrying the specific data.

The Golden Rule of Semantic Logging

The message string (the first argument) must be a static, constant string — think of it as an event type identifier. All variable data goes into fields.

# WRONG — variable data in the message string
log.info(f"User {user_id} logged in from {ip_address}")

# RIGHT — static event name, variable data in fields
log.info("user_login", user_id=user_id, ip_address=ip_address)

Why does this matter? Because your logging backend groups and counts events by their message string. If the message contains a user ID, every user gets a unique event type, and you can never count "how many logins happened" without a regex. With a static event name, count(event="user_login") works instantly.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free