Best Practices für Logging in Produktionssystemen
Strukturiertes Logging, Log-Level und Correlation-IDs — die Muster, die aus lauter Log-Ausgabe eine durchsuchbare, verwertbare Observability-Schicht machen.

Die meisten Produktions-Logs fallen in zwei Kategorien: zu viel Rauschen, um nützlich zu sein, oder zu wenig Information, um überhaupt etwas zu debuggen. Gutes Logging ist eine Designentscheidung, kein nachträglicher Einfall. Es erfordert, für jede Log-Anweisung das richtige Format, das richtige Level und den richtigen Kontext zu wählen.
Strukturierte Logs statt Klartext
Klartext-Logs sind in einem Terminal für Menschen lesbar und überall sonst nutzlos. Log-Aggregationstools wie Datadog, Elastic und CloudWatch brauchen strukturierte Daten zum Filtern, Durchsuchen und für Alerts.
// ❌ Plain text — impossible to parse or filter at scale
console.log(`User ${userId} placed order ${orderId} for $${total}`);
// "User cust_123 placed order ord_456 for $99.50"
// ✅ Structured JSON — every field is searchable
logger.info("Order placed", {
userId: "cust_123",
orderId: "ord_456",
total: 99.5,
currency: "USD",
itemCount: 3,
});
// {"level":"info","msg":"Order placed","userId":"cust_123","orderId":"ord_456","total":99.5,"currency":"USD","itemCount":3,"timestamp":"2020-02-19T14:30:00Z"}Mit strukturierten Logs kannst du "zeig mir alle Bestellungen über 100 $ von Nutzer cust_123" abfragen, ohne Regex-Klimmzüge.
Log-Level, die etwas bedeuten
Die meisten Teams verwenden Log-Level uneinheitlich. Definiere klare Semantik und setze sie im Code-Review durch.
| Level | Wann verwenden | Beispiel |
|---|---|---|
error | Etwas ist fehlgeschlagen und braucht menschliche Aufmerksamkeit | Zahlungsabbuchung fehlgeschlagen, Datenbankverbindung verloren |
warn | Etwas Unerwartetes, aber behandelt | Rate Limit nähert sich, veraltete API aufgerufen |
info | Bedeutsame Business-Ereignisse | Bestellung aufgegeben, Nutzer registriert, Deployment gestartet |
debug | Interne Details zur Fehlersuche | Cache Hit/Miss, Query-Timing, Zwischenzustand |
// ❌ Wrong levels — error used for non-errors, info used for noise
logger.error("User not found"); // This is expected behavior, not an error
logger.info(`Cache key: ${key}`); // Debug-level detail flooding production
// ✅ Correct levels — each level has clear semantics
logger.warn("User not found", { userId, endpoint: "/api/profile" });
logger.debug("Cache lookup", { key, hit: false, latencyMs: 2 });Setze in Produktion das Mindestlevel auf info. Aktiviere debug vorübergehend, wenn du ein bestimmtes Problem untersuchst. Lass debug niemals dauerhaft laufen — es erzeugt zu viel Volumen und kostet echtes Geld beim Log-Speicher.
Correlation-IDs für Request-Tracing
Wenn ein einzelner Nutzer-Request mehrere Services berührt, verbinden Correlation-IDs die gesamte Kette.
import { randomUUID } from "crypto";
function requestIdMiddleware(req: Request, res: Response, next: NextFunction) {
const requestId = req.headers["x-request-id"] ?? randomUUID();
// Attach to response for client debugging
res.setHeader("x-request-id", requestId);
// Attach to request context for downstream logging
req.requestId = requestId;
next();
}// Every log in the request lifecycle includes the same correlationId
function createRequestLogger(requestId: string) {
return {
info: (msg: string, data?: Record<string, unknown>) =>
logger.info(msg, { ...data, requestId }),
warn: (msg: string, data?: Record<string, unknown>) =>
logger.warn(msg, { ...data, requestId }),
error: (msg: string, data?: Record<string, unknown>) =>
logger.error(msg, { ...data, requestId }),
};
}
// In a route handler:
app.post("/api/orders", async (req, res) => {
const log = createRequestLogger(req.requestId);
log.info("Order creation started", { userId: req.userId });
const order = await createOrder(req.body, log);
log.info("Order created", { orderId: order.id });
await chargePayment(order, log);
log.info("Payment charged", { orderId: order.id });
res.json(order);
});Wenn etwas fehlschlägt, suche nach requestId, um die vollständige Request-Timeline über alle Services hinweg zu sehen.
Was man loggen sollte (und was nicht)
Immer loggen
- Request-Start und -Ende (mit Dauer)
- Business-Ereignisse (Bestellung aufgegeben, Zahlung verarbeitet, Nutzeraktion)
- Fehler und Exceptions mit vollständigen Stack Traces
- Externe API-Aufrufe (mit Antwortzeit und Status)
Niemals loggen
- Passwörter, Tokens, API-Keys oder Secrets
- Vollständige Kreditkartennummern oder Sozialversicherungsnummern
- Request-Bodies mit sensiblen Nutzerdaten
- Health-Check-Requests (sie überfluten den echten Traffic)
// ❌ Logging sensitive data
logger.info("User login", { email, password: req.body.password });
// ✅ Redact sensitive fields
logger.info("User login", { email, passwordProvided: !!req.body.password });Fehler-Logging mit Kontext
Ein Fehler-Log ohne Kontext ist fast nutzlos. Nimm alles auf, was zum Reproduzieren des Problems nötig ist.
// ❌ Bare error message — who was affected? What were the inputs?
logger.error("Payment failed");
// ✅ Full context — actionable without opening a debugger
try {
await paymentGateway.charge(amount, paymentMethodId);
} catch (error) {
logger.error("Payment charge failed", {
userId,
orderId,
amount,
paymentMethodId,
gateway: "stripe",
errorCode: error.code,
errorMessage: error.message,
stack: error.stack,
});
throw error;
}Das Ziel ist, dass jemand, der das Log um 3 Uhr nachts liest, versteht, was passiert ist, wer betroffen war und was zu untersuchen ist — ohne neues Logging deployen zu müssen.
Performance-Logging
Protokolliere langsame Operationen proaktiv. Warte nicht darauf, dass Nutzer Latenz melden.
function withTiming<T>(
name: string,
fn: () => Promise<T>,
log: Logger,
thresholdMs = 1000,
): Promise<T> {
const start = performance.now();
return fn().finally(() => {
const durationMs = Math.round(performance.now() - start);
if (durationMs > thresholdMs) {
log.warn("Slow operation detected", { operation: name, durationMs });
} else {
log.debug("Operation completed", { operation: name, durationMs });
}
});
}
// Usage
const users = await withTiming("getActiveUsers", () => getActiveUsers(), log);Dieses Muster deckt Performance-Regressionen auf, bevor sie zu Incidents werden.
Die wichtigsten Punkte
- Nutze strukturierte JSON-Logs — Klartext ist im großen Maßstab nicht abfragbar
- Definiere Log-Level konsistent — error bedeutet "braucht menschliche Aufmerksamkeit", nicht "etwas Unerwartetes ist passiert"
- Correlation-IDs verbinden einen gesamten Request-Lebenszyklus über Services hinweg
- Logge niemals sensible Daten — Passwörter, Tokens und PII sind Sicherheitsrisiken in Log-Speichern
- Nimm vollständigen Kontext in Fehler-Logs auf — mach Debugging um 3 Uhr nachts ohne Codeänderungen möglich
- Protokolliere langsame Operationen proaktiv — Performance-Schwellenwerte erkennen Regressionen frühzeitig


