Logging Basics
Fast recall
Quick revision
Core answer
Whether you understand Logging Basics beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The modern pattern (Go 1.21+ log/slog):
import "log/slog"
// Setup: JSON to stdout
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
// Structured logs — key-value, not string formatting
slog.Info("user created",
"user_id", 123,
"email", "al@x.com",
)
slog.Error("db connection failed",
"op", "loadUser",
"err", err,
)
// Output:
// {"time":"...","level":"INFO","msg":"user created","user_id":123,"email":"al@x.com"}The conventions interviewers check:
- 1JSON to stdout — the platform's collector (CloudWatch, Loki, the K8s log) handles storage, search, retention
- 2Structured fields (key-value), not formatted prose — searchable, filterable, machine-parseable
- 3Levels with discipline: info (the normal flow), warn (recoverable), error (failed operation — needs attention)
- 4Context (request ID, user, trace) on every line — the correlation that makes log-tracing possible
- 5NO file logging, NO log rotation — that's the platform's job (12-factor)
The middleware integration (the HTTP pattern):
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
reqID := r.Header.Get("X-Request-ID")
logger := logger.With("req_id", reqID, "method", r.Method, "path", r.URL.Path)
next.ServeHTTP(w, r) // the handler logs with the enriched logger
logger.Info("request",
"duration_ms", time.Since(start).Milliseconds(),
"status", statusFrom(w))
})
}The libraries: log/slog (the stdlib, 1.21+ — the default choice), zap (Uber's — the performance leader), zerolog (the allocation-free), logrus (the pre-slog standard — being superseded). The library matters less than the pattern: structured, leveled, context-enriched, stdout.
Logging Basics
Direct Answer
Log to stdout/stderr as structured JSON (log/slog, zap, zerolog) — the collector aggregates. Levels: debug/info/warn/error. Request-scoped context (request ID) on every line. NOT files — the platform handles that.
Deep Dive & Architecture
The slog design (the stdlib's answer to the library wars): a Handler interface (where logs go + how they're formatted) + structured fields (slog.Any, slog.String — the type-safe key-value pairs) + a default logger (slog.SetDefault — the package-level drop-in). The JSON handler is the production default; the text handler the development default — the SAME code, different handlers.
The context propagation (the request-scoped logger pattern): the middleware creates the enriched logger (logger.With("req_id", id)) and stores it in the request's context; handlers retrieve and log with it. The pattern: logger.FromContext(ctx) — the context carries the logger; every log line from that request carries the same correlation. This is the Go implementation of 'structured logging with request ID' — the observable service.
The anti-patterns the question screens: log.Fatal in a handler (crashes the process on a recoverable error — Fatal is for STARTUP); the printf-into-log-strings (fmt.Sprintf in the message — the fields are the structure); the log-to-file approach (fights the platform, loses the K8s log collection, the rotation problem Go didn't need).
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Logging Basics in a real project?
- How would you compare Logging Basics with an alternative?
- What is a common mistake when using Logging Basics?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.