Ein fehlender Log-Eintrag kann die Fehlersuche in einer PHP-Anwendung unnötig verlängern, während ein unstrukturierter Log schnell zur Datenhalde wird. Ich zeige, wie das native PHP-Logging mit error_log(), php.ini, Dateien und Systemdiensten funktioniert und wann sich eine Bibliothek wie Monolog lohnt. Dazu kommen praxistaugliche Beispiele, typische Fehler und Regeln für sichere Logs in produktiven Umgebungen.
Mit wenigen Einstellungen wird aus PHP-Logging ein verlässliches Diagnosewerkzeug
- Produktion: Fehler protokollieren, aber niemals direkt im Browser anzeigen.
-
error_log(): Für einzelne Meldungen genügt oft die eingebaute PHP-Funktion. - Ziel: Logs gehören in eine Datei, an stderr oder in einen zentralen Syslog-Dienst.
- Struktur: Zeitstempel, Schweregrad, Kontext und eine Request-ID machen Einträge auswertbar.
- Sicherheit: Passwörter, Tokens und vollständige Zahlungsdaten dürfen niemals im Log landen.

Was PHP-Logging praktisch leisten muss
Ein Log beschreibt, was in einer Anwendung passiert ist, wann es passiert ist und unter welchen Bedingungen. Das reicht von einem erfolgreichen Zahlungsvorgang bis zu einer nicht erreichbaren Datenbank. Ein guter Eintrag hilft mir nicht nur zu erkennen, dass etwas schiefgegangen ist, sondern auch, wo und warum.
Die Suchintention hinter „php log“ ist deshalb vor allem informativ und beratend. Entwickler möchten meist eine konkrete Meldung schreiben, PHP-Fehler in einer Datei finden oder verstehen, warum trotz aktivierter Protokollierung kein Eintrag erscheint. Diese drei Fälle brauchen unterschiedliche Einstellungen.
PHP unterscheidet zwischen Fehleranzeige und Fehlerprotokollierung. display_errors betrifft die Ausgabe an den Besucher, während log_errors festlegt, ob Fehler intern gespeichert werden. Im Livebetrieb sollten Fehlermeldungen nicht auf der Webseite erscheinen, weil sie Dateipfade, SQL-Details oder sensible Konfigurationswerte verraten können.
Für die technische Grundlage sind die Einstellungen error_reporting, display_errors, log_errors und error_log entscheidend. Das offizielle PHP Manual führt diese Optionen als Teil der PHP-Laufzeitkonfiguration.
Die wichtigsten Bordmittel für Meldungen und Fehler
Einzelne Ereignisse mit error_log schreiben
Für einen einfachen Hinweis brauche ich keine zusätzliche Bibliothek. Die Funktion error_log() schreibt eine Nachricht in den konfigurierten System- oder PHP-Log. Eine kurze Meldung kann bereits genügen, sollte aber möglichst einen konkreten Vorgang beschreiben.
Der zweite Parameter 0 ist der Standard und verwendet den von PHP konfigurierten Logger. Ich setze bei solchen Meldungen lieber einen verständlichen Text ein, statt einfach den Inhalt eines ganzen Arrays zu serialisieren. Das hält die Ausgabe lesbar und verhindert, dass ungewollt vertrauliche Daten auftauchen.
Eine Datei lässt sich mit dem Nachrichtentyp 3 direkt anhängen:
Bei diesem Modus fügt PHP keinen Zeilenumbruch automatisch hinzu. Genau das wird häufig übersehen. Für eine kleine lokale Anwendung funktioniert die Methode gut, für mehrere Server oder Container ist ein zentraler Ausgabekanal meistens die bessere Wahl.
Fehler in Entwicklung und Produktion konfigurieren
In einer Entwicklungsumgebung möchte ich möglichst viel sehen. In Produktion soll die Anwendung dagegen stabil und diskret bleiben. Eine typische Trennung sieht so aus:
; Entwicklung
error_reporting = E_ALL
display_errors = On
log_errors = On
; Produktion
error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /var/log/php/application.logE_ALL ist beim Entwickeln sinnvoll, weil auch Warnungen und veraltete Funktionen früh sichtbar werden. In produktiven Systemen ist es nicht automatisch falsch, ebenfalls alles zu protokollieren. Entscheidend sind ein brauchbarer Filter, eine Aufbewahrungsregel und ein Monitoring, das aus vielen unkritischen Meldungen keinen Alarmsturm macht.
Wenn der Fehler bereits vor dem Start des eigentlichen Skripts entsteht, kann eine Einstellung im Code zu spät kommen. Deshalb gehören die zentralen Optionen in die passende php.ini, in die PHP-FPM-Konfiguration oder in die Umgebung des jeweiligen Containers.
Exceptions zentral auffangen
Unbehandelte Exceptions sollten nicht an verschiedenen Stellen unterschiedlich behandelt werden. Ein zentraler Handler kann die Exception protokollieren und dem Nutzer gleichzeitig eine neutrale Fehlerseite liefern.
getMessage(),
$exception->getFile(),
$exception->getLine()
));
http_response_code(500);
echo 'Ein interner Fehler ist aufgetreten.';
});Ich protokolliere dabei den Stacktrace nur dort, wo er geschützt ist. Eine vollständige Exception-Ausgabe im Browser gehört nicht in die Produktion. Außerdem sollte ein Handler nicht selbst einen zweiten Fehler auslösen, etwa durch einen nicht beschreibbaren Log-Pfad.
Wohin die Meldungen geschrieben werden
Der Code kann korrekt sein und trotzdem scheinbar keinen Log erzeugen. Der häufigste Grund ist nicht error_log(), sondern ein falscher Blick auf das Ziel. Je nach Betriebsart landen Einträge in einer Datei, im Webserver-Log, bei PHP-FPM, im Systemjournal oder auf stderr.
| Ziel | Geeignet für | Typische Stolperfalle |
|---|---|---|
| Eigene Datei | Kleine Anwendungen und lokale Entwicklung | Fehlende Schreibrechte oder ungebremstes Wachstum |
| Webserver-Log | Klassische Apache- oder Nginx-Installationen | PHP- und Zugriffslogs werden verwechselt |
| PHP-FPM oder stderr | Container, Plattformbetrieb und Prozessmanager | Der Log liegt außerhalb des Projektverzeichnisses |
| Syslog oder Journald | Server mit zentraler Administration | Filter oder Berechtigungen unterdrücken die Meldung |
Bei einer Linux-Installation prüfe ich zuerst die effektive PHP-Konfiguration, nicht nur irgendeine Datei im Projekt. Mit php --ini sehe ich die CLI-Konfiguration. Für Webanfragen kann jedoch eine andere PHP-SAPI aktiv sein, etwa PHP-FPM.
Eine solche Diagnose darf nur kurzzeitig und geschützt erreichbar sein, weil phpinfo() viele interne Details offenlegt. Alternativ kann eine kleine Testseite mit ini_get('log_errors') und ini_get('error_log') die relevante Konfiguration gezielter anzeigen.
In Containern schreibe ich Logs bevorzugt nach stderr oder stdout. Docker, Kubernetes und viele Hostingplattformen sammeln diese Streams bereits ein. Eine lokale Datei im Container kann dagegen beim Neustart verschwinden und ist dann für die Analyse wertlos.
Wann error_log genügt und wann Monolog besser passt
Für ein kleines Skript, einen Cronjob oder eine einzelne Warnung ist die native Funktion völlig ausreichend. Sobald mehrere Module, Log-Level, Ausgabekanäle oder strukturierte Zusatzdaten ins Spiel kommen, wird handgebautes Logging schnell unübersichtlich.
Monolog bietet eine einheitliche Schnittstelle nach PSR-3. Das ist ein verbreiteter Standard für Logger-Methoden wie debug(), info(), warning() und error(). Die Bibliothek kann unter anderem Dateien, Syslog, Sockets und externe Dienste bedienen. Die offizielle Monolog-Dokumentation beschreibt dieses Zusammenspiel aus Logger, Handlern, Formatierern und Prozessoren.
composer require monolog/monologpushHandler(
new StreamHandler(__DIR__ . '/var/shop.log', Level::Info)
);
$logger->info('Bestellung angelegt', [
'order_id' => 4815,
'customer_id' => 73,
]);Der Vorteil liegt nicht nur in schöneren Methoden. Ein Handler kann beispielsweise alle Meldungen in eine Datei schreiben, während ein zweiter nur bei kritischen Fehlern eine Benachrichtigung auslöst. Dadurch bleibt der Anwendungscode von der späteren Infrastruktur weitgehend unabhängig.
Für produktive Systeme bevorzuge ich strukturierte Logs im JSON-Format. Sie lassen sich von Logplattformen zuverlässig durchsuchen und nach Feldern filtern. Ein Eintrag mit order_id, request_id und duration_ms ist in der Praxis deutlich wertvoller als ein langer Freitext mit denselben Informationen.
| Level | Beispiel | Reaktion |
|---|---|---|
| debug | SQL-Parameter in einer Testumgebung | Nur bei gezielter Fehlersuche aktivieren |
| info | Import erfolgreich abgeschlossen | Für Ablauf und Kennzahlen nutzen |
| warning | Externer Dienst antwortet ungewöhnlich langsam | Beobachten und Schwellenwerte prüfen |
| error | Bestellung konnte nicht gespeichert werden | Untersuchen, eventuell alarmieren |
| critical | Datenbank dauerhaft nicht erreichbar | Sofortige technische Reaktion |
Ein häufiger Fehler ist, jede Meldung als error zu markieren. Dann verliert der Schweregrad seine Bedeutung. Ich verwende warning für auffällige, aber noch abgefangene Zustände und reserviere error für Vorgänge, die tatsächlich fehlgeschlagen sind.
Welche Informationen ein guter Log-Eintrag braucht
Ein brauchbarer Eintrag beantwortet mindestens vier Fragen: Was ist passiert, wann ist es passiert, welcher Vorgang war betroffen und wie kann ich ihn wiederfinden? Für Webanwendungen kommen meist HTTP-Methode, Route, Statuscode, Nutzer- oder Mandanten-ID und eine Request-ID hinzu.
'payment_failed',
'request_id' => $requestId,
'order_id' => 4815,
'provider' => 'stripe',
'duration_ms' => 842,
'level' => 'error',
'time' => date(DATE_ATOM),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));Die Request-ID verbindet Einträge aus PHP, Webserver, Datenbank und externen Diensten. Gerade bei asynchronen Jobs oder mehreren Containern spart dieses kleine Feld oft mehr Zeit als jede zusätzliche Debug-Ausgabe.
Ich vermeide vollständige Nutzlasten, sofern sie für die Diagnose nicht zwingend nötig sind. Besonders kritisch sind Passwörter, Session-Cookies, API-Schlüssel, Kreditkartendaten und personenbezogene Inhalte. Auch E-Mail-Adressen oder IP-Adressen können je nach Zweck und Aufbewahrung datenschutzrechtlich relevant sein.
Ein Log ist kein Ersatz für Metriken und Traces. Logs erklären einzelne Ereignisse, Metriken zeigen Mengen und Trends, während Traces den Weg einer Anfrage durch mehrere Dienste verfolgen. Für eine kleine PHP-Anwendung reichen Logs zunächst aus, bei verteilten Systemen sollte ich diese drei Perspektiven nicht miteinander verwechseln.
Rotation, Rechte und Fehlersuche ohne blinde Flecken
Eine Logdatei wächst, solange Fehler auftreten oder die Anwendung läuft. Ohne Rotation kann sie den Datenträger füllen und dadurch ausgerechnet den Dienst beeinträchtigen, den sie überwachen soll. Für produktive Server gehören deshalb Rotation, Kompression und eine definierte Aufbewahrungsdauer zur Grundkonfiguration.
Die konkrete Dauer hängt vom Zweck und von internen Regeln ab. Für technische Diagnose reichen häufig 14 bis 30 Tage, während sicherheitsrelevante oder geschäftliche Nachweise andere Vorgaben haben können. Eine pauschale Aufbewahrung über Jahre ist selten eine gute Idee.
Auch die Dateirechte müssen zum Prozess passen. Der Benutzer von Apache, Nginx oder PHP-FPM braucht Schreibzugriff auf das Ziel, aber die Logdatei sollte nicht öffentlich aus dem Webverzeichnis abrufbar sein. Ein Pfad wie /var/www/html/app.log ist daher riskanter als ein geschützter Speicherort außerhalb des Document Roots.
Lesen Sie auch: Git-Best-Practices für klare Commits und sichere Releases
Wenn keine Meldung erscheint
- Prüfen, ob tatsächlich die erwartete PHP-SAPI verwendet wird.
-
log_errorsunderror_login dieser Umgebung kontrollieren. - Schreibrechte des PHP-Prozesses auf Datei und Verzeichnis prüfen.
- Webserver-, PHP-FPM-, Container- und Systemlogs getrennt durchsuchen.
- Mit einer eindeutigen Testmeldung verifizieren, ob der Codepfad erreicht wird.
Ein Klassiker ist die Verwechslung von CLI und Webserver. Ein Befehl im Terminal kann eine andere php.ini laden als eine Anfrage über PHP-FPM. Ebenso kann ein relativer Pfad je nach Startverzeichnis an einer unerwarteten Stelle landen.
Das Unterdrücken von Fehlern mit dem Operator @ halte ich nur in sehr wenigen, bewusst geprüften Fällen für vertretbar. Es nimmt der Fehlersuche wichtige Signale und kann dazu führen, dass Probleme still weiterlaufen. Besser ist es, erwartbare Fehler sauber abzufangen und mit einem passenden Level zu protokollieren.
Ein belastbares Logging-Konzept für PHP
Für eine kleine Anwendung genügt meist log_errors = On, eine geschützte Zieldatei und ein sparsamer Einsatz von error_log(). Mit wachsender Anwendung lohnt sich eine PSR-3-kompatible Bibliothek, weil Level, Kontext, JSON-Format und mehrere Ausgabekanäle dann zentral konfiguriert werden können.
Mein pragmatischer Mindeststandard besteht aus keinen Fehlermeldungen im Browser, eindeutigen Ereignisnamen, einer Request-ID, geprüften Dateirechten und einer automatischen Rotation. Wer zusätzlich regelmäßig nach Fehlerraten, Antwortzeiten und wiederkehrenden Ereignissen schaut, macht aus einzelnen Logzeilen ein echtes Betriebswerkzeug.
Die beste Protokollierung ist nicht die lauteste. Sie ist so konkret, dass ein anderer Entwickler den Vorfall nachvollziehen kann, so sparsam, dass wichtige Meldungen nicht untergehen, und so sicher, dass der Log selbst kein neues Risiko darstellt.