Logging en observability in Go: complete gids

Leer logging en observability in Go. Van slog en structured logs tot metrics, tracing en OpenTelemetry met praktische voorbeelden.

14 augustus 20266 min leestijdDoor We Develop Communication

Een productie-applicatie zonder goede logging en observability in Go is als een auto zonder dashboard: je rijdt, maar je hebt geen idee wat er onder de motorkap gebeurt. Zodra er iets misgaat, een trage endpoint, een mysterieuze 500, een geheugenlek, sta je machteloos.

In deze gids leer je hoe je professionele logging opzet met het nieuwe slog pakket, hoe je metrics en traces toevoegt, en hoe je met OpenTelemetry een compleet observability-verhaal bouwt. Inclusief praktische voorbeelden die je direct in je eigen Go-project kunt toepassen.

De drie pijlers van observability

Observability rust op drie pijlers die samen een compleet beeld geven van je systeem:

  • Logs, discrete gebeurtenissen met context, zoals "gebruiker 42 heeft ingelogd" of "database connectie verloren".
  • Metrics, numerieke waarden over tijd, zoals CPU-gebruik, request counts of response times.
  • Traces, het pad van een request door meerdere services heen, inclusief timing per onderdeel.

Logs vertellen je wat er is gebeurd, metrics laten zien hoe vaak en hoe lang, en traces laten zien waar in je systeem de tijd is gegaan. Een volwassen observability-setup combineert alle drie.

Waarom structured logging onmisbaar is

Traditionele logregels zoals log.Println("user logged in:", userID) zijn prima voor lokaal debuggen, maar in productie worden ze snel een obstakel. Je kunt er niet op filteren, niet op aggregeren en niet goed op alerten.

Structured logging lost dit op door logs uit te schrijven als JSON met key-value paren:

{"time":"2026-08-14T10:32:11Z","level":"INFO","msg":"user logged in","user_id":42,"ip":"10.0.0.5"}

Deze format is ideaal voor tools zoals Grafana Loki, Elasticsearch of Datadog: je kunt eenvoudig zoeken op user_id=42, grafieken bouwen op basis van level of alerts instellen als een bepaald veld optreedt.

slog: structured logging in de Go standard library

Sinds Go 1.21 is log/slog onderdeel van de standard library. Daarmee heb je structured logging zonder externe dependencies, een belangrijke stap vooruit voor de Go community.

Een simpel voorbeeld

package main

import (
    "log/slog"
    "os"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    slog.SetDefault(logger)

    slog.Info("server gestart",
        "port", 8080,
        "env", "production",
    )
}

Dit levert een nette JSON-regel op die elk logging-platform kan parsen. Geen regex-magie, geen fragiele string-parsing.

Log levels gebruiken

slog ondersteunt vier standaard levels: Debug, Info, Warn en Error. Je stelt het minimum level in via HandlerOptions:

opts := &slog.HandlerOptions{
    Level: slog.LevelDebug,
}
logger := slog.New(slog.NewJSONHandler(os.Stdout, opts))

In productie laat je meestal Debug uit om ruis te beperken, en zet je een feature-flag of env-variabele om het tijdelijk aan te zetten tijdens incidenten.

Context meegeven met groups en With

Een terugkerend patroon is dat je bij elke logregel dezelfde velden wilt hebben, bijvoorbeeld een request_id of user_id. Daarvoor gebruik je With:

reqLogger := logger.With(
    "request_id", reqID,
    "user_id", user.ID,
)

reqLogger.Info("verzoek ontvangen")
reqLogger.Info("database query uitgevoerd", "duration_ms", 23)

Elke logregel bevat nu automatisch request_id en user_id, zonder dat je ze telkens opnieuw hoeft te specificeren.

Logging middleware in HTTP servers

In een HTTP server wil je vaak elke request loggen met methode, path, status en duration. Je bouwt hiervoor een middleware. Als je deze concepten nog opfrist, bekijk dan onze gids over middleware en routing en HTTP servers bouwen in Go.

func LoggingMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            start := time.Now()
            rw := &responseWriter{ResponseWriter: w, status: 200}

            next.ServeHTTP(rw, r)

            logger.Info("http request",
                "method", r.Method,
                "path", r.URL.Path,
                "status", rw.status,
                "duration_ms", time.Since(start).Milliseconds(),
                "remote", r.RemoteAddr,
            )
        })
    }
}

Deze middleware voegt één regel per request toe, compact, doorzoekbaar en geschikt voor dashboards.

Gevoelige data beschermen

Een van de meest gemaakte fouten is het per ongeluk loggen van wachtwoorden, API-tokens of persoonsgegevens. Met slog kun je via ReplaceAttr automatisch velden maskeren:

opts := &slog.HandlerOptions{
    ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
        if a.Key == "password" || a.Key == "token" {
            return slog.String(a.Key, "[REDACTED]")
        }
        return a
    },
}

Zo voorkom je dat gevoelige velden in je log-pipeline terechtkomen, ook als een ontwikkelaar per ongeluk een hele struct logt.

Metrics met Prometheus

Logs geven je detail, maar metrics geven je overzicht. Met Prometheus verzamel je numerieke tijdreeksen zoals request counts, latency histograms en resource usage.

Een basic setup:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var requestCount = prometheus.NewCounterVec(
    prometheus.CounterOpts{
        Name: "http_requests_total",
        Help: "Totaal aantal HTTP requests",
    },
    []string{"method", "path", "status"},
)

func init() {
    prometheus.MustRegister(requestCount)
}

func main() {
    http.Handle("/metrics", promhttp.Handler())
    // ...
}

In je middleware verhoog je de counter na elke request. Prometheus schraapt /metrics periodiek en je kunt met Grafana dashboards bouwen op basis van deze data.

Welke metrics zijn relevant?

Begin met de RED-metrics voor elke service:

  • Rate, aantal requests per seconde
  • Errors, aantal fouten per seconde
  • Duration, hoe lang requests duren (p50, p95, p99)

Voor achtergrondprocessen gebruik je de USE-metrics: Utilization, Saturation en Errors. Samen dekken deze vrijwel alle alerting scenario's af.

Distributed tracing met OpenTelemetry

Zodra je applicatie uit meerdere services bestaat, worden traces cruciaal. Een trace toont hoe een request door je systeem reist, van de frontend via de API naar de database en terug.

OpenTelemetry is de de facto standaard voor tracing. De Go SDK integreert met net/http, gRPC, veel databases en message brokers.

Een span aanmaken

import "go.opentelemetry.io/otel"

func HandleOrder(ctx context.Context, orderID string) error {
    ctx, span := otel.Tracer("orders").Start(ctx, "HandleOrder")
    defer span.End()

    span.SetAttributes(attribute.String("order.id", orderID))

    if err := chargeCustomer(ctx, orderID); err != nil {
        span.RecordError(err)
        return err
    }
    return nil
}

Elke functie die je wilt traceren opent een span. Spans worden automatisch gekoppeld via de context.Context die je al doorgeeft, dezelfde context die je gebruikt bij database queries.

Traces exporteren

Je stuurt traces naar een backend zoals Jaeger, Tempo of een cloud-provider via een exporter:

exporter, _ := otlptracehttp.New(ctx,
    otlptracehttp.WithEndpoint("tempo.example.com:4318"),
)

tp := trace.NewTracerProvider(
    trace.WithBatcher(exporter),
    trace.WithResource(resource.NewWithAttributes(
        semconv.SchemaURL,
        semconv.ServiceName("orders-api"),
    )),
)
otel.SetTracerProvider(tp)

Zo heb je binnen een paar regels code een compleet tracing-systeem draaien.

Correlatie tussen logs, metrics en traces

De grootste kracht van observability ontstaat wanneer je de drie pijlers koppelt. Voeg een trace_id toe aan elke logregel:

span := trace.SpanFromContext(ctx)
logger.InfoContext(ctx, "order verwerkt",
    "order_id", orderID,
    "trace_id", span.SpanContext().TraceID().String(),
)

Nu kun je vanuit een trage trace direct doorklikken naar de bijbehorende logs, en vanuit een foutmelding terug naar de volledige request-flow. Deze workflow is onmisbaar bij het onderzoeken van productie-incidenten.

Best practices voor productie

Een paar vuistregels die zich in de praktijk bewijzen:

  1. Log één regel per gebeurtenis, geen multiline logs, dat breekt parsing.
  2. Gebruik consistente veldnamen, user_id, niet userId in de ene service en uid in de andere.
  3. Log op de rand, niet diep in je code, log bij entry en exit van een handler, niet bij elke helper-functie.
  4. Sample aggressief bij hoge volumes, 100% van alle traces kost goud; 1–10% is vaak genoeg.
  5. Alert op symptomen, niet op oorzaken, alert op "gebruikers zien fouten", niet op "CPU is hoog".
  6. Bouw runbooks, koppel alerts aan documentatie die aangeeft wat je moet doen.

Combineer deze aanpak met solide error handling en een doordachte project structuur, en je hebt een applicatie die je in productie werkelijk kunt begrijpen.

Veelgestelde vragen

Wat is het verschil tussen logging en observability?

Logging registreert gebeurtenissen in je applicatie, terwijl observability het bredere vermogen is om het gedrag van een systeem te begrijpen op basis van logs, metrics en traces. Logging is één van de drie pijlers van observability.

Welke logging library moet ik gebruiken in Go?

Sinds Go 1.21 is het standaard slog pakket de beste keuze voor de meeste projecten. Het biedt structured logging zonder extra dependencies. Voor geavanceerde features kun je zerolog of zap overwegen vanwege hun prestaties.

Wat zijn structured logs?

Structured logs zijn logregels in een machinaal leesbaar formaat zoals JSON met key-value paren in plaats van vrije tekst. Hierdoor kun je logs makkelijk filteren, doorzoeken en analyseren in tools zoals Loki, Elasticsearch of Datadog.

Wat is OpenTelemetry?

OpenTelemetry is een open-source framework voor het verzamelen van telemetriedata: logs, metrics en traces. Het biedt een uniforme API en SDK voor meerdere talen, inclusief Go, zodat je observability onafhankelijk van je backend kunt instrumenteren.

Hoe voorkom ik dat gevoelige data in logs komt?

Gebruik een log middleware die bekende velden zoals wachtwoorden, tokens en creditcards redigeert. Met slog kun je een custom Handler of ReplaceAttr functie gebruiken om gevoelige velden automatisch te maskeren voordat ze worden weggeschreven.

Veelgestelde vragen

Klaar om digitaal te groeien?

Wij helpen Nederlandse bedrijven met webtechnologie en SEO-strategieën die écht werken. Neem vrijblijvend contact op.