PHP-Entwickler in Wiesbaden

PHP-Entwicklung für Unternehmen in Wiesbaden mit Schwerpunkt Logging: PSR-3 und Monolog, display_errors und log_errors in Produktion, keine Passwörter im Log, IP-Adressen und Speicherdauer nach DSGVO, Alarmierung. Ein Einzelentwickler, remote und schriftlich.

PHP-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

#Worum es auf dieser Seite geht

Wenn eine PHP-Anwendung im Betrieb Fehler macht, bleiben zwei Dinge zurück: eine Beschwerde und eine Logdatei. Ob aus der Beschwerde in kurzer Zeit eine Ursache wird, entscheidet die Logdatei. Auf dieser Seite beschreibe ich, wie ich Logging und Fehlerbehandlung in eigenen PHP-Anwendungen einrichte: was PHP selbst protokolliert, wie PSR-3 und Monolog das ordnen, was nicht ins Log gehört und wie lange personenbezogene Daten dort liegen dürfen.

Ich bin ein einzelner Entwickler und arbeite remote und schriftlich. Es geht um Anwendungen mit Laravel, Symfony oder ohne Framework, nicht um WordPress-Seiten. Die Beispiele laufen mit PHP 8.4 und Monolog 3.12.1.

#Was PHP selbst einstellt: anzeigen oder protokollieren

Die Dokumentation von PHP nennt als Standardwert für display_errors den Wert “1” und für log_errors den Wert “0”. Die mitgelieferte php.ini-production setzt display_errors auf Off. Zur Anzeige im Browser steht dort, sie sei eine Hilfe für die Entwicklung und gehöre nie auf ein Produktivsystem. Für Websites im Netz rät die Dokumentation ausdrücklich, Fehler zu protokollieren statt anzuzeigen.

EinstellungWas sie steuertWas ich in Produktion setze
display_errorsFehlermeldungen in der ausgelieferten SeiteOff
display_startup_errorsFehler beim Start von PHPOff, nur zur gezielten Fehlersuche an
log_errorsSchreiben ins FehlerlogOn
error_logZieldatei, oder der Sonderwert syslogDatei außerhalb des Webverzeichnisses, vom Webserver-Benutzer beschreibbar
error_reportingWelche Fehlerklassen gemeldet werdenE_ALL, seit PHP 8.0 der Standard
; php.ini oder PHP-FPM-Pool, nicht im Anwendungscode
display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/anwendung-fehler.log
error_reporting = E_ALL

Die Werte gehören in die Konfiguration des Servers. Die Dokumentation weist darauf hin, dass ein zur Laufzeit per ini_set() gesetztes display_errors bei fatalen Fehlern nichts bewirkt, weil die Anweisung dann nie ausgeführt wird. Eine Fehlermeldung im Browser kann außerdem Dateipfade und Teile der Anwendung offenlegen, die ein Besucher nicht sehen soll.

#PSR-3: eine Schnittstelle, acht Stufen

Das Logging einer Anwendung sollte nicht an einer Bibliothek hängen. Der Standard PSR-3 der PHP-FIG beschreibt dafür das LoggerInterface mit acht Methoden, eine je Stufe nach RFC 5424: debug, info, notice, warning, error, critical, alert, emergency. Eine neunte Methode, log(), nimmt die Stufe als Argument.

Zwei Regeln aus dem Standard bestimmen, wie ich Meldungen schreibe. Die Meldung darf Platzhalter in geschweiften Klammern enthalten, etwa {order}, und der Name muss einem Schlüssel im Kontext-Array entsprechen. Und eine Ausnahme gehört im Kontext unter den Schlüssel exception, damit der Logger den Stacktrace herauslesen kann.

<?php
declare(strict_types=1);

use Psr\Log\LoggerInterface;

function chargeOrder(LoggerInterface $logger, int $orderId): void
{
    $logger->info('Zahlung gestartet für Bestellung {order}', ['order' => $orderId]);

    try {
        // Aufruf des Zahlungsanbieters
    } catch (RuntimeException $e) {
        $logger->error('Zahlung fehlgeschlagen für Bestellung {order}', [
            'order' => $orderId,
            'exception' => $e,
        ]);
    }
}

Die Meldung bleibt ein fester Satz, die veränderlichen Werte stehen im Kontext. Das macht die Log-Zeilen durchsuchbar: Wer nach Zahlung fehlgeschlagen sucht, findet alle Fälle, egal für welche Bestellung. Die Funktion kennt nur das Interface. Ob dahinter Monolog oder ein anderer PSR-3-Logger steckt, ist eine Konfigurationsfrage.

#Monolog einrichten

Monolog ist ein PSR-3-Logger, installiert mit composer require monolog/monolog. Die Version 3.12.1 erschien laut Changelog am 29. September 2026 und verlangt nach der composer.json mindestens PHP 8.1. Ein Logger hat einen Kanal als Namen und einen Stapel von Handlern, die jeden Eintrag nacheinander bekommen. Jeder Handler hat einen Formatter.

Die Stufen beschreibt die Dokumentation so: WARNING für Außergewöhnliches, das kein Fehler ist, etwa die Nutzung veralteter Schnittstellen. ERROR für Laufzeitfehler, die kein sofortiges Handeln verlangen, aber protokolliert und überwacht werden sollen. CRITICAL für eine ausgefallene Komponente oder eine unerwartete Ausnahme. ALERT für den Fall, dass sofort gehandelt werden muss, etwa bei einer komplett ausgefallenen Website. Meine Zuordnung dazu: Eine unbehandelte Ausnahme ist critical, ein nicht erreichbarer Zahlungsanbieter error, eine abgelehnte Zahlung warning.

<?php
declare(strict_types=1);

use Monolog\Formatter\JsonFormatter;
use Monolog\Formatter\RedactingFormatter;
use Monolog\Handler\FingersCrossedHandler;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Level;
use Monolog\Logger;
use Monolog\Processor\PsrLogMessageProcessor;

$datei = new RotatingFileHandler(__DIR__ . '/var/log/app.log', 14, Level::Info);
$datei->setFormatter(new RedactingFormatter(new JsonFormatter()));

$logger = new Logger(
    'bestellungen',
    [new FingersCrossedHandler($datei, Level::Error)],
    [new PsrLogMessageProcessor()]
);

Der FingersCrossedHandler sammelt nach der Dokumentation alle Einträge, bis einer die eingestellte Stufe erreicht. Dann gibt er alle weiter, auch die mit niedriger Stufe. Im Alltag bleibt das Log klein, und im Fehlerfall steht der Weg bis zum Fehler darin. Die gesammelten Einträge können Personendaten enthalten, sie unterliegen daher denselben Regeln wie alles im Log, siehe unten. Der RotatingFileHandler legt eine Datei je Tag an und löscht Dateien, die älter sind als $maxFiles; die 14 im Beispiel ist ein Platzhalter. Die Dokumentation empfiehlt für größere Setups logrotate.

Für unbehandelte Ausnahmen registriere ich einen Handler, den PHP aufruft, wenn eine Ausnahme nirgends gefangen wird. Danach beendet PHP die Ausführung, die Seite muss also selbst eine neutrale Antwort liefern.

set_exception_handler(function (Throwable $e) use ($logger): void {
    $logger->critical('Unbehandelte Ausnahme', ['exception' => $e]);
    http_response_code(500);
    echo 'Es ist ein Fehler aufgetreten.';
});

Sinnvoll sind außerdem getrennte Kanäle. Mit $logger->withName('sicherheit') entsteht ein zweiter Logger, dessen Einträge sich im selben Log mit grep auf den Kanalnamen filtern lassen.

#Was nicht ins Log gehört

Passwörter, Tokens, der Authorization-Header, Cookies, komplette Anfragekörper und Zahlungsdaten haben im Log nichts verloren. Für Kundendaten gilt: lieber die interne ID als Name und Anschrift. Der Grundsatz der Datenminimierung in Art. 5 Abs. 1 Buchst. c DSGVO verlangt, dass personenbezogene Daten dem Zweck angemessen und auf das notwendige Maß beschränkt sind. Ein Fehlerlog braucht zum Nachvollziehen eines Fehlers meistens nur die ID.

Monolog bringt seit Version 3.11.0 vom 2. September 2026 den RedactingFormatter mit. Er umhüllt einen anderen Formatter und maskiert Werte von Kontext-Schlüsseln wie password, token, authorization und cookie. Er entfernt außerdem bekannte Geheimnisse aus der fertigen Ausgabe und kann zusätzlich reguläre Ausdrücke anwenden. Das Muster TOKEN_PATTERN für lange, tokenähnliche Zeichenfolgen ist nach der Quelle bewusst optional, weil es auch Hashes oder UUIDs treffen kann.

In einem Testlauf mit dem Code oben erschien 'password' => 'geheim123' im Kontext als "password":"[REDACTED]". Das ist ein Sicherheitsnetz, kein Freibrief: Maskiert wird, was der Name verrät. Ein Passwort, das jemand in einen Freitext schreibt, erkennt kein Formatter. Deshalb halte ich Meldungen frei von Daten und lege Werte nur gezielt in den Kontext.

#IP-Adressen und Speicherdauer

Erwägungsgrund 30 der DSGVO nennt IP-Adressen ausdrücklich als Online-Kennungen, die in Kombination mit anderen Informationen dazu genutzt werden können, Profile zu erstellen und Personen zu identifizieren. Ein Log mit IP-Adresse ist deshalb eine Verarbeitung personenbezogener Daten. Drei Artikel bestimmen den Rahmen:

  • Art. 6 Abs. 1 Buchst. f: Die Verarbeitung ist rechtmäßig, wenn sie zur Wahrung berechtigter Interessen erforderlich ist und die Interessen der betroffenen Person nicht überwiegen. Ob das für Ihr Log trägt, bewerten Ihre Datenschutzberatung oder Ihr Datenschutzbeauftragter.
  • Art. 5 Abs. 1 Buchst. b, c und e: Zweckbindung, Datenminimierung und Speicherbegrenzung. Personen dürfen nur so lange identifizierbar bleiben, wie es für den Zweck erforderlich ist. Die Verordnung nennt dafür keine feste Zahl von Tagen.
  • Art. 32: Der Verantwortliche trifft geeignete technische Maßnahmen, die unter anderem Pseudonymisierung einschließen können.

Die Frist legen also Sie mit Ihrer Beratung fest. Ich setze sie technisch um. Dafür gibt es vier Stellschrauben:

  1. Gar nicht protokollieren. Der WebProcessor von Monolog ergänzt jeden Eintrag um URI, Methode und Client-IP. Ich schalte ihn nur ein, wenn es dafür einen benannten Zweck gibt.
  2. Kürzen. Meine Gewohnheit ist, das letzte Oktett einer IPv4-Adresse und bei IPv6 die hinteren Bytes auf null zu setzen. Ob das Ihrer Datenschutzberatung genügt, ist eine rechtliche Frage.
  3. Rotation auf die Frist abstimmen. maxFiles im Beispiel ist die technische Umsetzung der Tage, die Sie festlegen.
  4. Backups beachten. Liegt das Logverzeichnis im Backup, überleben die Einträge die Rotation. Ich nehme es entweder aus dem Backup aus oder stimme die Aufbewahrung ab.
function anonymizeIp(string $ip): string
{
    $bin = inet_pton($ip);
    if ($bin === false) {
        return 'ungueltig';
    }
    $behalten = strlen($bin) === 4 ? 3 : 6; // IPv4: 3 Bytes, IPv6: 6 Bytes
    return inet_ntop(substr($bin, 0, $behalten) . str_repeat("\0", strlen($bin) - $behalten));
}

$logger->pushProcessor(function (\Monolog\LogRecord $record): \Monolog\LogRecord {
    $record->extra['ip'] = anonymizeIp($_SERVER['REMOTE_ADDR'] ?? '');
    return $record;
});

Aus 203.0.113.77 wird 203.0.113.0, aus 2001:db8:abcd:12:1:2:3:4 wird 2001:db8:abcd::. Hinter einem Proxy oder CDN enthält REMOTE_ADDR die Adresse des Proxys. Welchem Header Sie trauen dürfen, hängt von Ihrer Infrastruktur ab, das kläre ich vorher. Schickt die Anwendung Logs an einen externen Dienst, ist dieser Dienst in der Regel Auftragsverarbeiter, und Art. 28 Abs. 1 DSGVO verlangt, dass Sie nur mit Dienstleistern arbeiten, die hinreichende Garantien für geeignete Maßnahmen bieten.

#Alarmierung: wer erfährt wann von einem Fehler

Ein Log, das niemand liest, hilft erst beim nächsten Streit. Die Dokumentation von Monolog ordnet die Stufen so ein: ERROR soll protokolliert und überwacht werden, ALERT soll Alarme auslösen. Daraus folgt meine Aufteilung. critical und höher gehen sofort an eine Mailbox oder einen Chat, error zählt der Monatsbericht, warning und darunter bleiben im Log.

Monolog liefert dafür Handler, etwa für Mail über NativeMailerHandler oder SymfonyMailerHandler und für Slack über Webhooks. Wichtig ist der DeduplicationHandler. Nach der Dokumentation sammelt er Einträge bis zum Ende der Anfrage und gibt sie nur weiter, wenn sie sich in einem Zeitraum (standardmäßig 60 Sekunden) nicht wiederholen. Ist die Datenbank nicht erreichbar, scheitert jede Anfrage, und ohne diesen Handler käme für jede eine Nachricht. Steht der Server, schreibt auch kein Logger mehr. Deshalb braucht es zusätzlich eine Verfügbarkeitsprüfung von außen. Wer die Meldungen empfängt und in welchem Rhythmus sie gelesen werden, halte ich schriftlich fest.

Neues Rathaus in Wiesbaden

Foto: Berthold Werner, CC BY-SA 3.0, Wikimedia Commons.

#Wenn in Wiesbaden etwas schiefgeht: das Log als Grundlage für die Meldung

Für Unternehmen in Hessen ist der HBDI zuständig. Er beschreibt auf seiner Seite zu Datenschutzverletzungen, was bei einer Meldung zählt. Der Verantwortliche muss jede Verletzung dokumentieren, und die Meldung soll unverzüglich und möglichst binnen 72 Stunden nach Bekanntwerden erfolgen. Eine erste Meldung darf unvollständig sein, fehlende Angaben sind laut HBDI kein Grund für eine späte Erstmeldung. Für die Bearbeitung nennt der HBDI als ersten Punkt die Sachverhaltsschilderung: Ein bloßes Stichwort wie “Phishing” genügt nicht, die Art und der Verlauf des Vorfalls sollen möglichst genau beschrieben werden.

Genau dafür ist ein brauchbares Log da. Zeitpunkte, betroffene Kennungen und der Ablauf eines Zugriffs lassen sich aus Einträgen mit Kanal, Stufe und ID ablesen. Aus Einträgen ohne Kontext lassen sie sich nicht rekonstruieren. Das ist der Grund, warum ich Logging nicht erst nach einem Vorfall einrichte. Die rechtliche Bewertung eines Vorfalls und der Text der Meldung liegen bei Ihnen und Ihrer Beratung.

#Checkliste für Ihre Anwendung

PrüfpunktGut, wenn
display_errors in ProduktionOff in der Server-Konfiguration, nicht im Code
log_errors und error_logan, Zieldatei außerhalb des Webverzeichnisses
Logger im CodePSR-3-Interface, nicht eine feste Klasse
Meldungenfester Satz, Werte im Kontext
Ausnahmenim Kontext unter exception, nicht als Text zusammengeklebt
Passwörter, Tokens, Cookiesnie im Log, Maskierung als zweites Netz
IP-Adressenur mit Zweck, gekürzt, Entscheidung dokumentiert
Speicherdauervon Ihnen festgelegt, in Rotation und Backup umgesetzt
AlarmEmpfänger und Stufe schriftlich festgelegt, Wiederholungen gebündelt
Prüfung von außenVerfügbarkeitsprüfung unabhängig vom Server

#So läuft ein Projekt mit mir

Gestaltung, Mockups und Wireframes sind für diese Arbeit nicht nötig. Es geht um Code und Konfiguration.

  1. Schriftliche Anfrage. Sie schicken eine kurze Beschreibung der Anwendung, die PHP-Version und das Framework.
  2. Bestandsaufnahme. Ich lese die PHP-Konfiguration, die vorhandene Logik zum Protokollieren und Fehlerfangen und halte offene Punkte schriftlich fest. Nutzt die Anwendung schon einen Logger, baue ich darauf auf, statt einen zweiten daneben zu setzen.
  3. Festlegung. Welche Stufen alarmieren, welche Daten ins Log dürfen und wie lange sie bleiben, steht in einem kurzen Dokument. Die Frist und die Rechtsgrundlage tragen Sie mit Ihrer Beratung ein.
  4. Umsetzung auf einer Testumgebung. Ich provoziere jeden Fehlerpfad und prüfe, was im Log steht und was fehlen muss.
  5. Übergabe. Sie erhalten Konfiguration, Code und eine Anleitung, wie ein Eintrag gelesen wird. Danach betreuen Sie die Anwendung selbst oder beauftragen die Wartung in Wiesbaden. Ungenutzte Wartungsstunden verfallen am Monatsende.

Dafür brauche ich Lesezugriff auf den Code, die PHP-Konfiguration des Servers und ein Beispiel für eine Fehlermeldung, die Sie kennen. Zugangsdaten schicken Sie nicht per E-Mail, den Übergabeweg kläre ich vorher schriftlich mit Ihnen.

#Häufige Fragen

#Darf display_errors in Produktion an sein?

Nein. Die Dokumentation von PHP sagt, dass die Anzeige für die Entwicklung gedacht ist und nie auf Systemen laufen soll, die mit dem Internet verbunden sind. Für Produktion rät sie, Fehler zu protokollieren statt anzuzeigen. Ich setze die Werte in der Server-Konfiguration.

#Welche Log-Stufe nehme ich wofür?

Monolog beschreibt acht Stufen nach RFC 5424. Meine Zuordnung: critical für unbehandelte Ausnahmen, error für Fehler, die geprüft werden sollen, warning für erwartbare Störungen wie eine abgelehnte Zahlung. info bleibt für Ereignisse, die Sie später nachvollziehen wollen.

#Darf ich IP-Adressen im Log speichern?

Die DSGVO nennt IP-Adressen in Erwägungsgrund 30 als Online-Kennungen. Sie brauchen also einen Zweck und eine Rechtsgrundlage, häufig wird Art. 6 Abs. 1 Buchst. f geprüft. Technisch kürze ich Adressen oder protokolliere sie gar nicht. Die rechtliche Bewertung gehört zu Ihrer Datenschutzberatung.

#Wie lange darf ein Log aufbewahrt werden?

Art. 5 Abs. 1 Buchst. e DSGVO verlangt, dass Personen nur so lange identifizierbar bleiben, wie es für den Zweck erforderlich ist. Eine feste Zahl von Tagen nennt die Verordnung nicht. Die Frist legen Sie fest, ich setze sie in der Rotation und im Backup um.

#Reicht das Fehlerlog von PHP ohne Monolog?

Für ein kleines Skript ja: log_errors und error_log genügen. Bei einer Anwendung mit mehreren Teilen fehlen Stufen, Kontext, Kanäle und Maskierung. Dann setze ich auf PSR-3 und Monolog oder den Logger, den die Anwendung schon nutzt.

#Hilft mir das Log, wenn ich dem HBDI einen Vorfall melden muss?

Ja, bei der Sachverhaltsschilderung. Der HBDI erwartet eine möglichst genaue Beschreibung von Art und Verlauf, ein Stichwort genügt nicht. Ein Log mit Zeitpunkt, Kanal und Kennung liefert dafür die Fakten. Die Meldung selbst verantworten Sie.

#Schreiben Sie mir

Wenn Sie nach einem Fehler in Ihrer PHP-Anwendung zu wenig im Log finden oder zu viel, schicken Sie mir über die Kontaktseite ein paar Zeilen: die Adresse der Anwendung oder eine kurze Beschreibung, Ihr Ziel, eine Frist, falls es eine gibt, und den Stack mit PHP-Version und Framework. Meine Antwort kommt schriftlich: eine erste Einschätzung und die Fragen, die noch offen sind. Diese erste Einschätzung ist kostenlos.

Was ich sonst an PHP-Arbeit übernehme, steht auf der Seite PHP-Entwickler. Wie Ihr Shop Daten aufbewahrt und löscht, beschreibt die Seite zum WooCommerce-Entwickler in Wiesbaden.

Brauchen Sie die Leistung: PHP-Entwickler in Wiesbaden?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken