Strukturiertes Logging mit Pino — Wie wir Observability aufbauen
Warum wir Pino einsetzen, wie wir Logs zwischen Bot und API korrelieren, und welche Sentry-Integration sich in der Praxis bewährt hat.
Wenn ein Discord-Bot in mehreren Guilds gleichzeitig läuft und parallel ein Dashboard mit Dutzenden gleichzeitigen WebSocket-Verbindungen bedient, reicht `console.log` nicht mehr aus. Wir haben unseren Stack 2026 auf strukturiertes JSON-Logging mit Pino umgestellt — dieser Artikel zeigt, wie unsere Pipeline aussieht und welche Entscheidungen wir bewusst getroffen haben.
Warum strukturiertes Logging?
Freitext-Logs sind für Maschinen schwer zu parsen und für Menschen in der vierten Zeile unleserlich. Strukturierte Logs hingegen erzwingen ein Schema — das ermöglicht zuverlässiges Filtern, Aggregieren und Alerts.
Konkret bedeutet das: jedes Log-Statement ist ein JSON-Objekt mit festen Schlüsseln wie `level`, `time`, `service`, `requestId` und einem frei definierbaren `msg`. Damit kann eine Log-Management-Plattform (in unserem Fall die selbst gehostete Loki-Instanz) Anfragen wie „alle Errors aus bot-main mit requestId X“ in unter einer Sekunde beantworten.
Pino-Setup: Eine Instanz pro Service
Wir instanziieren Pino pro Service (`bot-main`, `bot-music`, `api`, `web`) und reichern die Logger mit Service-Metadaten über `child()` an. So tragen alle Log-Lines automatisch den richtigen Service-Tag, ohne dass wir ihn manuell bei jedem Log-Call setzen müssen.
import pino from "pino";
const baseLogger = pino({
level: process.env.LOG_LEVEL ?? "info",
base: { service: "bot-main" },
timestamp: pino.stdTimeFunctions.isoTime,
redact: {
paths: ["*.token", "*.authorization", "*.password"],
censor: "[REDACTED]",
},
});
export const logger = baseLogger.child({ component: "discord-bot" });Was wir von Anfang an redacted haben
Discord-Tokens, OAuth-Authorization-Header und Passwörter landen regelmäßig in Logs — oft durch ein versehentliches `logger.info({ req })`. Die `redact`-Option von Pino filtert diese Felder serverseitig, bevor sie die Anwendung verlassen. Das ist eine Verteidigungslinie, kein Ersatz für sorgfältigen Code.
Request-Korrelation über Service-Grenzen hinweg
Der schwierige Teil: ein Klick im Dashboard löst einen API-Call aus, der wiederum einen Discord-Event triggert. Ohne Korrelation sehen wir in Loki drei isolierte Log-Streams, die wir manuell zusammenfügen müssen.
Unsere Lösung: ein `correlationId` (UUID v4), der am Edge des Systems generiert und über drei Mechanismen weitergegeben wird:
- HTTP-Header `x-correlation-id` (Frontend → API → ggf. Bot-Callback)
- Async-LocalStorage-Kontext in Node.js für interne Aufrufe
- Discord-Embed-Footer mit `[corr:abc123]`, falls ein Log aus einem externen Trigger (User-Slash-Command) kommt
Jeder Logger liest diesen Kontext in einem `pino-http`-Middleware-Hook und heftet ihn als `correlationId` an die Log-Line an.
Ohne Korrelation debuggt man Korrelation. Das ist der größte Hebel, den strukturiertes Logging uns gebracht hat.
Sentry-Integration: Errors vs. Logs
Wir nutzen Sentry ausschließlich für unbehandelte Exceptions und Performance-Traces. Alles andere (Info-Logs, Business-Events) geht direkt nach Loki. Diese Trennung ist wichtig:
- Sentry ist teuer (per-Event-Billing) — nicht für Routine-Logs gedacht.
- Loki ist günstig und für Hochvolumen-Logs optimiert.
- Sentry erlaubt Symbolication und Source-Maps — Logs brauchen das nicht.
Unsere `logger.error()`-Helper schicken Exceptions zusätzlich zu Sentry, ohne den Log-Flow zu duplizieren. So bleibt die Quelle der Wahrheit im Log-Stream, während Sentry als Alarm- und Triage-Schicht dient.
Was wir nicht tun
Wir loggen keine PII (Discord-User-Inhalte, private Nachrichten) und keine Token-Rohdaten. Wir loggen auch keine API-Responses in voller Länge — nur Status, Größe in Bytes, und ein Hash der Pfad-Parameter, falls Debugging nötig ist.
Erkenntnisse nach sechs Monaten
Seit der Umstellung haben wir drei Vorfälle deutlich schneller gelöst, weil wir endlich sehen konnten, in welcher Reihenfolge welche Service-Calls passierten. Wir haben außerdem zwei Bugs gefunden, die mit `console.log` nie aufgefallen wären — einer davon betraf einen Race-Condition in der Cache-Invalidierung, der nur sichtbar wurde, als wir Logs zeitlich korrelieren konnten.
Wenn du mit einem kleinen Projekt startest, ist Pino übertrieben. Sobald du aber zwei Services hast, die miteinander reden, lohnt sich der Aufwand. Fang mit dem `redact`-Setup an — das ist die eine Sache, die du nicht nachträglich reparieren willst.
Themen
Geschrieben von
DerStr1k3r
Fullstack Developer · Germany
Starte jetzt mit xUCP Systems V2 und bringe deinen Discord-Server auf das nächste Level. Vollständig kostenlos, keine Kreditkarte nötig.
Jetzt starten