skip to content

Service Telemetry

Making a running Go service explain itself: slog records and the handler that shapes and redacts them, runtime and expvar numbers, and a trace context you carry through every hop yourself.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

68 · 4 sections

In Go's log/slog, how do the variadic key/value arguments work, and what is !BADKEY?

level: juniorimportance: must knowfreq 76%
basics
~20 s

log/slog reads its variadic arguments as alternating pairs: a string key followed by its value, or a ready-made slog.Attr such as slog.String("pkg", p). A key left with no value is logged under the placeholder key !BADKEY.

open as a page

In Go's log/slog, what does the Level field of slog.HandlerOptions control, and what happens if you leave it nil?

level: juniorimportance: must knowfreq 60%
basics
~10 s

HandlerOptions.Level sets the minimum severity a slog handler emits: records at that level or higher are written, lower ones are dropped. Leave it nil and the handler defaults to slog.LevelInfo, so Debug records disappear.

open as a page

What does slog.SetDefault change, and where do slog.Info records go before it is called?

level: juniorimportance: must knowfreq 72%
basics
~20 s

slog.SetDefault installs a *slog.Logger as the process-wide default, so the package-level slog.Info, slog.Warn and slog.Error use its handler. Before that call, those functions use a built-in handler that prints plain, unstructured lines to standard error.

open as a page

In Go's log/slog, how do slog.Group and Logger.WithGroup differ in what they nest?

level: middleimportance: should knowfreq 56%
basics
~10 s

slog.Group builds one attribute whose value is a nested set of attributes, for a single record. Logger.WithGroup returns a derived logger that qualifies everything added afterwards under that group name, on every record.

open as a page

Why does slog.Level space its constants four apart, and how do you add a custom level between them?

level: middleimportance: should knowfreq 40%
basics
~20 s

slog.Level is an int — Debug -4, Info 0, Warn 4, Error 8 — and the gaps exist so you can define levels in between. Declare a constant such as slog.LevelInfo+2 and emit records with Logger.Log.

open as a page

In Go's log/slog, why does a `slog.Debug` call still cost work when the logger's level is Info?

level: juniorimportance: must knowfreq 52%
basics
~20 s

Go evaluates every argument before the call runs, so work you do to build a log line happens even at a disabled level. slog only checks the level inside the call, after the arguments are boxed into interfaces.

open as a page

Which four methods does slog.Handler require, and what is each one for?

level: juniorimportance: must knowfreq 50%
basics
~20 s

slog.Handler has four methods: Enabled reports whether a level should be logged, Handle formats and writes one Record, WithAttrs returns a new handler carrying extra attributes, and WithGroup returns one that nests later keys under a name.

open as a page

What does Go's log package write by default: which stream, what prefix, and what severity?

level: juniorimportance: must knowfreq 48%
basics
~20 s

Go's log package writes to standard error through the one logger returned by log.Default(), prefixing each line with the date and time (log.LstdFlags). It has no severity levels: log.Print, log.Printf and log.Println all produce identical, unlabelled lines.

open as a page

In Go's log/slog, what does implementing the LogValuer interface on a type change about how it is logged?

level: juniorimportance: must knowfreq 38%
basics
~20 s

A type that implements LogValuer supplies a LogValue method returning a slog.Value, and slog logs that substitute instead of the value itself. Every handler sees the substitute, so a secret can be replaced with a fixed placeholder in one place.

open as a page

Should a containerised Go service's slog handler write to os.Stdout or os.Stderr?

level: juniorimportance: must knowfreq 56%
basics
~20 s

Both streams are collected by the container runtime, so the real rule is to pick one and send every record there. Most teams choose os.Stdout for the application's log stream and leave os.Stderr to the runtime's own panic output.

open as a page

How do you time the wrapped http.Handler inside a Go middleware function?

level: juniorimportance: must knowfreq 68%
basics
~10 s

Read start := time.Now() before calling next.ServeHTTP, then record time.Since(start) inside a deferred closure. Deferring means the duration is still recorded when the handler panics or returns early.

open as a page

Why is r.URL.Path a poor metric label in a Go HTTP service, and what does http.Request.Pattern give you instead?

level: juniorimportance: must knowfreq 50%
basics
~20 s

r.URL.Path holds the concrete requested path, so /items/1 and /items/2 become separate label values and the number of counters grows with traffic. http.Request.Pattern holds the ServeMux pattern that matched, a fixed string that changes only when you add a route.

open as a page

Why does runtime.ReadMemStats stop the world when runtime/metrics.Read does not?

level: middleimportance: must knowfreq 50%
basics
~20 s

runtime.ReadMemStats stops the world for the whole call so that every field of the MemStats struct is one mutually consistent snapshot. runtime/metrics.Read instead aggregates the runtime's per-P statistics under an internal lock, so it adds no stop-the-world pause.

open as a page

What does runtime/debug.ReadBuildInfo() tell a running Go binary about itself?

level: juniorimportance: should knowfreq 42%
basics
~20 s

ReadBuildInfo returns the build record the linker embedded in the binary: the Go toolchain version, the main module's path and version, the dependency modules linked in, and build settings such as flags and the VCS revision. It reports false when no record is present.

open as a page

What does a blank import of Go's expvar package register, and what does /debug/vars serve?

level: juniorimportance: should knowfreq 40%
basics
~10 s

Importing expvar runs its init, which registers a handler for /debug/vars on http.DefaultServeMux and publishes two variables of its own, cmdline and memstats. The endpoint returns every published variable as one JSON object.

open as a page

How do you write a slog.Handler that adds a run id from the context to every record?

level: middleimportance: must knowfreq 60%
basics
~20 s

Wrap another slog.Handler. In Handle(ctx, record), look the run id up with ctx.Value, attach it with Record.AddAttrs, then call the wrapped handler's Handle. Callers must use the Context-taking log methods for the id to arrive.

open as a page

Why build an outbound call with http.NewRequestWithContext instead of http.NewRequest?

level: middleimportance: must knowfreq 72%
basics
~10 s

http.NewRequest gives the request context.Background(), so the outbound call ignores the caller's deadline and cancellation and carries none of the per-request state your code keeps in a context.Context. NewRequestWithContext attaches the caller's context instead.

open as a page

In Go's log/slog, what does Logger.InfoContext give you that Logger.Info does not?

level: juniorimportance: should knowfreq 55%
basics
~10 s

Logger.InfoContext passes your context.Context down to the handler's Handle method, so the handler can read request-scoped data such as a run id out of it. Logger.Info passes context.Background() instead, so that data never arrives.

open as a page

How do you attach an X-Trace-Id header to an outbound *http.Request, and how does Header.Set differ from Header.Add?

level: juniorimportance: should knowfreq 60%
basics
~20 s

Build the request, then call req.Header.Set("X-Trace-Id", id) before sending it. Set replaces every value already stored under that key; Add appends another value and keeps the earlier ones. Both store the key in canonical form.

open as a page

Why should a request's tracing span be carried in context.Context rather than stored in a struct field?

level: juniorimportance: should knowfreq 48%
basics
~20 s

A span belongs to one request, but a service struct is shared by every concurrent request, so a field is raced on and overwritten. context.Context is per request and flows down to exactly that request's callees.

open as a page