Monitoring & Observability Betrieb & Architektur

Monitoring & Observability: Warum Fehler oft sichtbar sind – aber niemand hinschaut

Carola Schulte
Carola Schulte 1. Februar 2026 14 min Lesezeit

Das System läuft. Die Tests sind grün. Der Go-Live war erfolgreich. Und dann passiert es: Der Kunde meldet, dass Bestellungen nicht durchkommen. Der Support sieht nichts in den Logs. Die Entwickler starten eine Fehlersuche, die Stunden dauert. Das Problem war die ganze Zeit sichtbar – es hat nur niemand hingeschaut, weil niemand wusste, wo.

Kurz gesagt: Monitoring zeigt Ihnen, dass etwas kaputt ist. Observability hilft Ihnen zu verstehen, warum. Beides zusammen ist eine Architekturentscheidung, keine nachträgliche Maßnahme.

Monitoring vs. Observability: Der Unterschied zählt

Monitoring beantwortet vordefinierte Fragen: Ist der Server erreichbar? Wie hoch ist die CPU-Auslastung? Wie viele Requests pro Sekunde? Sie definieren vorher, was Sie wissen wollen, und richten Dashboards und Alerts dafür ein.

Observability beantwortet Fragen, die Sie noch nicht kennen. Wenn ein Nutzer meldet „Bestellungen dauern manchmal 30 Sekunden“, können Sie mit guter Observability die Ursache finden, ohne vorher einen spezifischen Alert dafür eingerichtet zu haben. Durch strukturierte Logs, Traces und Metriken navigieren Sie sich von Symptom zur Ursache.

Aspekt Monitoring Observability
Fragt „Ist es kaputt?“ „Warum ist es kaputt?“
Ansatz Vordefinierte Checks & Schwellwerte Explorative Analyse unbekannter Probleme
Daten Metriken, Uptime, Fehlerrate Logs, Traces, Metriken – korreliert
Reicht für Einfache Systeme, bekannte Fehlermodi Verteilte Systeme, unbekannte Fehlermodi

Für die meisten Projekte gilt: Sie brauchen beides. Monitoring als erste Verteidigungslinie (Alerting, Dashboards), Observability als Werkzeug zur Fehleranalyse (Structured Logging, Tracing).

Die drei Säulen: Logs, Metriken, Traces

1. Structured Logging

Der häufigste Fehler in Logging-Architekturen: unstrukturierte Textzeilen, die kein Mensch und keine Maschine zuverlässig parsen kann.

// Schlecht: Freitext-Logging
$logger->info("Bestellung 4711 von Kunde 89 angelegt, Betrag 249.90 EUR");

// Gut: Structured Logging (JSON)
$logger->info('order.created', [
    'order_id'    => 4711,
    'customer_id' => 89,
    'amount'      => 249.90,
    'currency'    => 'EUR',
    'items_count' => 3,
    'channel'     => 'web',
    'duration_ms' => 127
]);

Warum JSON statt Freitext:

  • Maschinen können filtern: jq '.[] | select(.amount > 1000)'
  • Dashboards können aggregieren: Durchschnittliche Bestelldauer pro Channel
  • Alerts können gezielt auslösen: Wenn duration_ms > 5000 bei mehr als 10 Bestellungen pro Minute
  • Korrelation wird möglich: Alle Logs mit order_id=4711 über alle Services hinweg finden

Context Enrichment: Logs brauchbar machen

class ContextLogger
{
    private array $context = [];

    public function withContext(array $context): self
    {
        $clone = clone $this;
        $clone->context = array_merge($this->context, $context);
        return $clone;
    }

    public function info(string $event, array $data = []): void
    {
        $entry = array_merge($this->context, $data, [
            'event'     => $event,
            'timestamp' => gmdate('c'),
            'level'     => 'info',
        ]);

        error_log(json_encode($entry, JSON_UNESCAPED_UNICODE));
    }
}

// Verwendung: Kontext einmal setzen, alle Logs erben ihn
$logger = (new ContextLogger())
    ->withContext(['request_id' => bin2hex(random_bytes(8))])
    ->withContext(['user_id' => $currentUser->id])
    ->withContext(['tenant_id' => $currentTenant->id]);

$logger->info('order.created', ['order_id' => 4711]);
// Ergebnis: {"request_id":"a3f2...","user_id":89,"tenant_id":5,"event":"order.created","order_id":4711,...}
Praxistipp: Eine request_id, die durch alle Log-Einträge eines Requests mitläuft, ist der wichtigste einzelne Kontextparameter. Damit können Sie jeden Request komplett nachvollziehen – über alle Services hinweg.

2. Metriken: Die vier goldenen Signale

Google hat im SRE-Buch vier Metriken definiert, die für jedes System relevant sind. In der Praxis decken sie die meisten Probleme ab:

Signal Was es misst Beispiel-Alert
Latenz Wie lange dauern Requests? p95-Latenz > 2s seit 5 Minuten
Traffic Wie viele Requests kommen rein? Traffic-Einbruch > 50% gegenüber Vorwoche
Fehlerrate Wie viele Requests scheitern? 5xx-Rate > 1% seit 10 Minuten
Saturation Wie voll ist das System? DB-Connections > 80% des Pools
class MetricsCollector
{
    private array $timings = [];
    private array $counters = [];

    public function timing(string $metric, float $durationMs): void
    {
        $this->timings[$metric][] = $durationMs;
    }

    public function increment(string $metric, array $tags = []): void
    {
        $key = $metric . ':' . implode(',', array_map(
            fn($k, $v) => "$k=$v", array_keys($tags), $tags
        ));
        $this->counters[$key] = ($this->counters[$key] ?? 0) + 1;
    }

    public function getPercentile(string $metric, int $percentile): ?float
    {
        $values = $this->timings[$metric] ?? [];
        if (empty($values)) return null;

        sort($values);
        $index = (int) ceil(count($values) * $percentile / 100) - 1;
        return $values[$index];
    }
}

// Verwendung in Middleware
$start = microtime(true);
$response = $handler->handle($request);
$duration = (microtime(true) - $start) * 1000;

$metrics->timing('http.request.duration', $duration);
$metrics->increment('http.request.total', [
    'method' => $request->getMethod(),
    'status' => $response->getStatusCode(),
    'route'  => $request->getUri()->getPath()
]);
Hinweis: Das Beispiel zeigt das Prinzip. In produktiven Systemen gehen Metriken an einen Collector oder ein Monitoring-Backend wie Prometheus, StatsD oder den OpenTelemetry Collector – nicht in In-Memory-Arrays.
Häufiger Fehler: Nur Durchschnittswerte betrachten. Ein Durchschnitt von 200ms sagt nichts über die 5% der Requests, die 8 Sekunden dauern. Immer Perzentile messen – p50 (Median), p95 und p99.

3. Distributed Tracing

In verteilten Systemen reicht ein einzelner Log-Eintrag nicht. Eine Bestellung durchläuft API-Gateway, Order-Service, Payment-Service, Notification-Service. Tracing verbindet diese Schritte zu einer nachvollziehbaren Kette:

// Vereinfachtes Tracing-Konzept
class Tracer
{
    public function startSpan(string $name, ?string $parentId = null): Span
    {
        return new Span(
            traceId:  $parentId ? $this->currentTraceId : bin2hex(random_bytes(16)),
            spanId:   bin2hex(random_bytes(8)),
            parentId: $parentId,
            name:     $name,
            startTime: microtime(true)
        );
    }
}

// Verwendung
$rootSpan = $tracer->startSpan('http.request');

    $dbSpan = $tracer->startSpan('db.query', $rootSpan->spanId);
    $result = $db->query("SELECT ...");
    $dbSpan->finish();

    $apiSpan = $tracer->startSpan('external.payment', $rootSpan->spanId);
    $paymentResult = $paymentClient->charge($amount);
    $apiSpan->finish();

$rootSpan->finish();

// Ergebnis: Ein Trace mit drei Spans, zeitlich zueinander geordnet
// http.request [=========================] 450ms
//   db.query     [====]                     45ms
//   external.payment       [===========]   280ms

In der Praxis übernehmen Bibliotheken wie OpenTelemetry das Span-Management. Der entscheidende Architekturpunkt: Die trace_id muss über HTTP-Header (traceparent) zwischen Services weitergereicht werden.

Alerting ohne Alert-Fatigue

Das häufigste Problem in Monitoring-Setups ist nicht zu wenig Alerting, sondern zu viel. Teams, die 50 Alerts am Tag bekommen, ignorieren alle – auch die kritischen.

Severity-Stufen klar definieren

Stufe Bedeutung Reaktion Kanal
Critical System/Feature komplett ausgefallen Sofort reagieren, auch nachts SMS/Anruf, PagerDuty
Warning Degradiert, aber funktionsfähig Innerhalb der Geschäftszeiten beheben Slack/Teams-Channel
Info Auffällig, aber nicht dringend Im nächsten Sprint prüfen Dashboard, Ticket

Regeln gegen Alert-Fatigue

  • Jeder Alert muss eine Handlung auslösen. Wenn die Reaktion immer „ignorieren“ ist, gehört der Alert weg
  • Schwellwerte auf Basis realer Daten setzen, nicht auf Bauchgefühl. Zwei Wochen Baseline messen, dann Schwellwerte definieren
  • Rate statt Absolutwert: „5xx-Rate > 1%“ ist stabiler als „mehr als 10 Fehler“ (bei 1.000 Requests sind 10 Fehler normal, bei 100 nicht)
  • Zeitfenster beachten: Ein einzelner Timeout ist kein Alert. Fünf Timeouts in drei Minuten schon
  • Deduplication: Dieselbe Ursache sollte einen Alert erzeugen, nicht 200
// Alert-Konfiguration (konzeptionell)
$alerts = [
    [
        'name'      => 'high_error_rate',
        'condition' => 'rate(http_errors_total[5m]) / rate(http_requests_total[5m]) > 0.01',
        'severity'  => 'critical',
        'for'       => '5m',  // Muss 5 Minuten lang gelten
        'summary'   => 'Fehlerrate über 1% seit 5 Minuten',
        'runbook'   => '/runbooks/high-error-rate.md'
    ],
    [
        'name'      => 'slow_responses',
        'condition' => 'histogram_quantile(0.95, http_request_duration_seconds) > 2',
        'severity'  => 'warning',
        'for'       => '10m',
        'summary'   => 'p95-Latenz über 2 Sekunden seit 10 Minuten',
        'runbook'   => '/runbooks/slow-responses.md'
    ],
    [
        'name'      => 'db_connection_pool',
        'condition' => 'db_connections_active / db_connections_max > 0.8',
        'severity'  => 'warning',
        'for'       => '5m',
        'summary'   => 'DB Connection Pool über 80% ausgelastet',
        'runbook'   => '/runbooks/db-pool-exhaustion.md'
    ]
];
Runbooks: Jeder Alert sollte auf ein Runbook verweisen – ein kurzes Dokument, das beschreibt: Was bedeutet der Alert? Wie verifiziere ich das Problem? Was sind die ersten drei Schritte zur Behebung? Ohne Runbook ist ein Alert nur Lärm.

Tool-Entscheidungen: Stack-Auswahl als Architektur

Die Wahl des Monitoring-Stacks ist eine Architekturentscheidung mit langfristigen Konsequenzen. Drei gängige Ansätze:

Elastic Stack (Elasticsearch, Logstash/Beats, Kibana)

Früher als „ELK Stack“ bekannt, heute erweitert um Beats und Fleet. Der Klassiker für Log-Aggregation. Stark bei Volltextsuche und Log-Exploration.

  • Stärke: Mächtige Suche, flexible Dashboards, großes Ökosystem
  • Schwäche: Hoher Ressourcenverbrauch (Elasticsearch braucht RAM), Betriebsaufwand
  • Passt wenn: Viele verschiedene Log-Quellen, hohe Log-Volumina, Team hat ELK-Erfahrung

Prometheus + Grafana

Der Standard für Metriken. Prometheus sammelt, Grafana visualisiert.

  • Stärke: Pull-basiert (kein Agent nötig), mächtige Query-Sprache (PromQL), bewährt in Kubernetes-Umgebungen
  • Schwäche: Kein Log-Management (braucht Ergänzung wie Loki), langfristige Datenspeicherung erfordert Zusatz-Tools
  • Passt wenn: Metriken-fokussiertes Monitoring, Container-Umgebungen, Budget für Betrieb vorhanden

Managed Services (Datadog, New Relic, Elastic Cloud)

  • Stärke: Kein Betriebsaufwand, schneller Start, oft Logs+Metriken+Tracing in einem Tool
  • Schwäche: Kosten skalieren mit Datenvolumen (können schnell erheblich werden), Vendor Lock-in, Daten liegen extern
  • Passt wenn: Kleines Team ohne DevOps-Kapazität, schnelle Ergebnisse nötig, Budget vorhanden
Kostenfalle: Managed Monitoring-Services rechnen oft pro ingested GB oder pro Host ab. Ein System, das verbose loggt, kann schnell vierstellige Monatskosten erzeugen. Vorher Datenvolumen schätzen und Pricing durchrechnen.

OpenTelemetry: Der Vendor-neutrale Standard

OpenTelemetry (OTel) definiert ein einheitliches Format für Logs, Metriken und Traces. Der Vorteil: Sie instrumentieren Ihren Code einmal und können das Backend jederzeit wechseln – von Jaeger zu Datadog, von Prometheus zu New Relic.

// OpenTelemetry-Konzept: Instrumentation einmal, Backend austauschbar
// PHP: opentelemetry/sdk Paket

$tracerProvider = new TracerProvider(
    new SimpleSpanProcessor(
        new OtlpHttpExporter('http://collector:4318/v1/traces')
    )
);

$tracer = $tracerProvider->getTracer('meine-app');

$span = $tracer->spanBuilder('order.process')
    ->setAttribute('order.id', $orderId)
    ->setAttribute('customer.id', $customerId)
    ->startSpan();

try {
    $result = $this->processOrder($orderId);
    $span->setStatus(StatusCode::STATUS_OK);
} catch (\Exception $e) {
    $span->setStatus(StatusCode::STATUS_ERROR, $e->getMessage());
    $span->recordException($e);
    throw $e;
} finally {
    $span->end();
}

Architektur-Empfehlung: Wenn Sie heute ein neues System mit längerer Lebensdauer bauen, ist OpenTelemetry oft die beste vendor-neutrale Grundlage. Die Backend-Entscheidung (selbst gehostet vs. Managed) können Sie dann unabhängig davon treffen und später ändern. Für sehr kleine Monolithen kann Structured Logging + ein einfacher Health-Endpoint aber erstmal ausreichen – OTel bringt dort mehr Struktur als unmittelbaren Nutzen.

Praxis: Monitoring-Architektur für PHP-Anwendungen

Die meisten PHP-Anwendungen sind keine Microservice-Landschaften, sondern monolithische oder modular-monolithische Systeme hinter PHP-FPM. Das verändert die Monitoring-Anforderungen:

PHP-spezifische Metriken

  • PHP-FPM Pool-Status: Aktive/idle Worker, Queue-Länge, max_children erreicht
  • OPcache-Hitrate: Unter 95% deutet auf Konfigurationsprobleme hin
  • Memory pro Request: Steigt der Memory-Verbrauch über die Zeit? (Memory Leaks in Long-Running-Processes)
  • Slow Queries: MySQL/PostgreSQL Slow Query Log auswerten
// PHP-FPM Status als Metrik-Quelle
// In der PHP-FPM-Config: pm.status_path = /fpm-status

function collectFpmMetrics(): array
{
    $status = json_decode(
        file_get_contents('http://127.0.0.1:9000/fpm-status?json=&full'),
        true
    );

    return [
        'fpm.active_processes'   => $status['active processes'],
        'fpm.idle_processes'     => $status['idle processes'],
        'fpm.total_processes'    => $status['total processes'],
        'fpm.max_children_reached' => $status['max children reached'],
        'fpm.slow_requests'      => $status['slow requests'],
        'fpm.listen_queue'       => $status['listen queue'],
        'fpm.listen_queue_max'   => $status['max listen queue'],
    ];
}

Health-Check-Endpoint

// /api/health - Liefert Systemstatus für Load Balancer und Monitoring
function healthCheck(PDO $db, Redis $redis): array
{
    $checks = [];

    // Datenbank
    try {
        $start = microtime(true);
        $db->query("SELECT 1");
        $checks['database'] = [
            'status' => 'ok',
            'response_ms' => round((microtime(true) - $start) * 1000, 2)
        ];
    } catch (\Exception $e) {
        $checks['database'] = ['status' => 'error', 'message' => 'Connection failed'];
    }

    // Redis/Cache
    try {
        $start = microtime(true);
        $redis->ping();
        $checks['cache'] = [
            'status' => 'ok',
            'response_ms' => round((microtime(true) - $start) * 1000, 2)
        ];
    } catch (\Exception $e) {
        $checks['cache'] = ['status' => 'error', 'message' => 'Connection failed'];
    }

    // Gesamtstatus
    $allOk = !in_array('error', array_column($checks, 'status'));

    return [
        'status' => $allOk ? 'healthy' : 'degraded',
        'checks' => $checks,
        'timestamp' => gmdate('c'),
        'version' => getenv('APP_VERSION') ?: 'unknown'
    ];
}
Liveness, Readiness, Degraded:
  • Liveness (/health/live) – Der Prozess lebt. Antwortet er nicht, wird er neu gestartet.
  • Readiness (/health/ready) – Das System kann Traffic annehmen. Während Cache aufgewärmt wird oder Migrationen laufen: alive, aber nicht ready.
  • Degraded – Das System lebt und nimmt Traffic an, aber ein Teil hängt (z.B. Cache ausgefallen, ein externer Service nicht erreichbar). Kein Neustart nötig, aber Aufmerksamkeit schon.

Typische Monitoring-Fehler

1. Alles loggen, nichts finden

Systeme, die jede Zeile Code loggen, erzeugen Terabytes an Daten, in denen niemand etwas findet. Logging-Volumen korreliert nicht mit Logging-Qualität.

Besser: Log-Level konsequent einsetzen. DEBUG nur in Entwicklung. INFO für geschäftsrelevante Ereignisse (Bestellung angelegt, Zahlung eingegangen). WARNING für degradierte Zustände. ERROR für Fehler, die Aufmerksamkeit brauchen.

2. Dashboards bauen, aber nicht anschauen

20 Grafana-Dashboards erstellt, keines wird regelmäßig geprüft. Das ist Monitoring-Theater.

Besser: Ein einziges Haupt-Dashboard mit den vier goldenen Signalen. Dieses Dashboard wird täglich angeschaut. Alles andere sind Detail-Dashboards für die Fehleranalyse.

3. Monitoring erst nach dem Vorfall einrichten

Klassischer Ablauf: Outage → Postmortem → „Wir brauchen Monitoring“ → hastig Dashboards bauen → nächster Outage in einem anderen Bereich → wiederholen.

Besser: Monitoring ist Teil der Definition of Done. Ein Feature ohne Metriken und Alerting ist nicht fertig.

4. Nur Infrastruktur monitoren, nicht Geschäftslogik

CPU, RAM, Disk – alles grün. Aber es werden keine Bestellungen mehr angelegt. Infrastruktur-Monitoring sieht keine Business-Probleme.

Besser: Business-Metriken messen: Bestellungen pro Stunde, Zahlungseingänge, neue Registrierungen. Wenn diese Metriken einbrechen, wissen Sie sofort, dass etwas nicht stimmt – auch wenn die Server grün sind.

Entscheidungshilfe: Was brauchen Sie?

Situation Empfehlung
Einzelner Server, PHP-Monolith Structured Logging + Health-Endpoint + einfaches Dashboard (Grafana oder Managed)
Mehrere Services, moderate Last Structured Logging + Prometheus/Grafana + Alerting-Regeln
Microservices, hohe Last OpenTelemetry + Distributed Tracing + Metriken + Log-Aggregation
Regulierte Branche (Finanzen, Gesundheit) Alles oben + Audit-Logging + Retention-Policies + Compliance-Dashboards

Checkliste: Monitoring & Observability

  • Structured Logging eingeführt? JSON statt Freitext, request_id durchgehend
  • Vier goldene Signale gemessen? Latenz, Traffic, Fehlerrate, Saturation
  • Health-Endpoint vorhanden? Liveness + Readiness, prüft echte Abhängigkeiten
  • Alerting eingerichtet? Severity-Stufen definiert, Runbooks verlinkt
  • Alert-Fatigue vermieden? Jeder Alert löst eine Handlung aus, keine „Noise“-Alerts
  • Business-Metriken? Nicht nur CPU und RAM, sondern auch Bestellungen, Zahlungen, Registrierungen
  • Dashboards gepflegt? Ein Haupt-Dashboard, das täglich genutzt wird
  • Datenvolumen im Griff? Log-Level korrekt, Kosten bei Managed Services geprüft

Carola Schulte

Carola Schulte

Software-Architektin mit Fokus auf Betriebsarchitektur, Security-Audits und System-Design.

Beratung anfragen

Monitoring-Architektur planen?

Ich analysiere Ihre bestehende Systemlandschaft und entwickle eine Monitoring-Strategie, die zu Teamgröße, Budget und Komplexität passt – ohne Over-Engineering.

Kostenlose Erstanalyse anfragen