Es ist 22:15 Uhr. Das Telefon klingelt. In der Produktion läuft etwas schief. Ich öffne die Logs – und finde: Something went wrong. Kein Stacktrace, kein Kontext, keine Zeitangabe. Nur dieser eine Satz, der einem das Blut aus dem Gesicht zieht. Das ist der Moment, in dem ich mir wünsche, ich hätte früher ordentliches Logging eingeführt.

Das typische Problem in Legacy-Projekten

In gewachsenen PHP-Anwendungen sieht Logging meistens so aus: error_log("Error: " . $e->getMessage()), verstreut über hunderte Dateien, ohne Log-Level, ohne Kontext, ohne einheitliches Format. Manchmal auch gar nicht – und man erfährt von Fehlern erst durch Nutzer-Beschwerden. Das ist kein Logging, das ist Stochern im Nebel.

Warum strukturiertes Logging einen Unterschied macht

Mit strukturiertem Logging kann ich Fehler reproduzieren, weil ich den vollständigen Kontext habe: Welcher Nutzer, welche Eingabe, welcher Systemzustand. Ich kann Monitoring-Systeme anschließen. Ich kann nach Log-Level filtern und sehe auf einen Blick, was kritisch ist und was nur Rauschen. Das ist kein Luxus – das ist professionelles Handwerk.

Monolog einrichten

Monolog ist der De-facto-Standard für PHP-Logging und lässt sich unkompliziert via Composer einbinden:

composer require monolog/monolog

Eine einfache, aber bereits sinnvolle Konfiguration mit File-Handler und JSON-Formatter:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;

$handler = new StreamHandler('/var/log/app/application.log', Logger::DEBUG);
$handler->setFormatter(new JsonFormatter());

$logger = new Logger('app');
$logger->pushHandler($handler);

// Verwendung
$logger->info('Benutzer eingeloggt', ['user_id' => $userId, 'ip' => $_SERVER['REMOTE_ADDR']]);
$logger->error('Datenbankfehler', ['query' => $sql, 'exception' => $e->getMessage()]);

Jetzt bekomme ich pro Eintrag ein JSON-Objekt mit Timestamp, Level, Nachricht und allem Kontext, den ich mitgegeben habe. Das ist grep-bar, parsebar und macht Monitoring-Tools glücklich.

Log-Levels richtig einsetzen

Das Wichtigste: nicht alles ist ein Error. Ich halte mich an diese Faustregel:

  • DEBUG – nur lokal, nie in der Produktion aktiviert
  • INFO – wichtige Business-Events: Benutzer registriert, Bestellung abgeschlossen
  • WARNING – ungeplantes, aber behandeltes Verhalten: Fallback auf Standardwert, Rate-Limit erreicht
  • ERROR – echte Probleme: Datenbankverbindung fehlgeschlagen, externe API nicht erreichbar

Schrittweise Integration statt Big Bang

Ich fange nie mit der gesamten Anwendung an. Ich suche mir den einen kritischen Bereich heraus, der am häufigsten Probleme macht – meistens der Zahlungsprozess oder die externe API-Kommunikation. Dort ersetze ich die error_log()-Aufrufe, richte Monolog ein und beobachte. Erst wenn das läuft und ich sehe, dass die Logs nützlich sind, weite ich es aus.

Gutes Logging ist nicht glamourös. Es ist der Unterschied zwischen einem Nachtdienst und einem ruhigen Schlaf. Man merkt den Wert erst, wenn man ihn braucht – aber dann wirklich.

Fazit

Der Einstieg ist klein: Monolog installieren, einen Handler konfigurieren, die schlimmsten error_log()-Stellen ersetzen. Das dauert einen halben Tag. Der Nutzen zeigt sich beim nächsten Produktionsfehler – der hoffentlich um 10 Uhr morgens passiert und nicht um 22 Uhr abends. Aber falls doch: dann hat man wenigstens Kontext.