Laravel Log Shipper: Logs zentral, sicher und queue-basiert versenden

Warum zentrale Logs bei Laravel-Projekten schneller weh tun, als man denkt

Wer mehrere Laravel-Anwendungen betreibt, kennt das Muster: Lokal in storage/logs zu schreiben ist bequem, bis es im Betrieb nicht mehr reicht. Spätestens wenn Anwendungen in mehreren Instanzen laufen (VMs, Container, Autoscaling, Kubernetes) zerfällt das „einfach ins File loggen“ in viele einzelne Log-Insellösungen. Dann beginnt die eigentliche Arbeit: Zugriff organisieren, Rotation berücksichtigen, Logs von mehreren Hosts zusammenkopieren, Zeitfenster abpassen, Berechtigungen klären.

Die Alternative, Logs einfach in einen Chat (Mattermost/Slack) zu pushen, wirkt im ersten Moment pragmatisch. In der Praxis erzeugen Sie damit oft einen Ereignisstrom ohne echte Struktur: Fehler verschwinden im Rauschen, Suche und Korrelation sind mühsam, und Datenschutzrisiken werden schnell übersehen.

Genau für diesen Punkt haben wir bei ADMIN INTELLIGENCE ein Composer-Package gebaut und nach OSS-Philosophie veröffentlicht: Laravel Log Shipper. Es verschickt Logs aus Ihrer Laravel-Anwendung strukturiert als JSON an einen zentralen HTTP-Endpunkt, typischerweise asynchron über eine Queue.

Dieser Beitrag behandelt bewusst nur den Log Shipper (das Package). Die optionale Gegenstelle („Logger“, eine zentrale Laravel-Anwendung) ist ein eigenes Projekt und wird separat ausführlicher vorgestellt. Wichtig ist: Der Shipper kann mit jedem HTTP-Endpunkt arbeiten, der JSON annimmt.

Was Laravel Log Shipper ist – und was nicht

Laravel Log Shipper ist ein zusätzlicher Logging-Channel für Laravel, den Sie in Ihren Logging-Stack hängen. Ab einem definierten Level (z. B. error) nimmt er Log-Events an, bereitet sie auf, bereinigt sensible Felder, ergänzt optional Request- und App-Kontext und verschickt das Ergebnis als JSON an einen Remote-Endpunkt.

Was das Package nicht ist:

  • Kein Ersatz für Ihre komplette Observability-Strategie (APM/Tracing/Metrics).
  • Kein SIEM.
  • Kein „wir sammeln alles“-System, das ungefiltert Request-Bodies oder personenbezogene Daten abzieht.

Worauf das Package zielt:

  • Zentralisierung von Applikationslogs über Projekte/Instanzen hinweg
  • Betriebsstabilität (Queue-first, Retries, Backoff, Circuit Breaker)
  • Datenschutz und Sicherheit (Sanitization, IP-Obfuskation, Payload-Limits, HTTPS-Erzwingung)
  • Betriebssignale zusätzlich zu Logs (Status-/Health-Metriken per Scheduler)

Eckdaten: Package, Versionen, Requirements

Requirements:

  • PHP ≥ 8.1
  • Laravel 10, 11 oder 12
  • Queue-Driver empfohlen (z. B. Redis, database, SQS)

Architektur: Wie „Log Shipping“ in Laravel sauber funktioniert

Laravel setzt für Logging auf Monolog und konfiguriert Channels in config/logging.php. Ein Shipper-Channel verhält sich wie jeder andere Channel – der Unterschied liegt in der Ausgabe: Statt in ein lokales File zu schreiben, verschickt er strukturierte Daten nach außen.

In einer belastbaren Architektur trennen Sie diese Verantwortlichkeiten klar:

  • Applikationscode erzeugt Log-Events (z. B. Log::error(...)).
  • Channel-Konfiguration entscheidet, welche Events überhaupt beim Shipper landen (über Level und Stack).
  • Sanitization/Obfuskation passiert vor dem Versand, damit keine sensiblen Inhalte das System verlassen.
  • Queue-Jobs übernehmen die Auslieferung, damit HTTP-Latenzen nicht in Ihren Requestpfad fallen.
  • Optional: Batch-Buffering reduziert Queue-Druck, wenn sehr viele Logs entstehen.
  • Optional: Status Push schickt periodisch Health-Metriken an denselben oder einen zweiten Endpunkt.

Der zentrale Gedanke: Logging darf nicht zum Single Point of Failure werden. Wenn Ihr Log-Server ausfällt, darf Ihre Anwendung nicht langsamer werden oder Requests verlieren.

Installation: Composer-Package einbinden

Die Installation ist standardmäßig:

composer require adminintelligence/laravel-log-shipper

Anschließend veröffentlichen Sie die Konfiguration:

php artisan vendor:publish --tag=log-shipper-config

Wenn Sie upgraden und neue Optionen brauchen, können Sie die Konfiguration erneut publizieren:

php artisan vendor:publish --tag=log-shipper-config --force

Das --force überschreibt Ihre bestehende config/log-shipper.php. In der Praxis sichern Sie die Datei vorher oder mergen die Änderungen manuell.

Grundkonfiguration über .env: Die wenigen Variablen, die zählen

Ein minimalistisches Setup benötigt vor allem: Aktivierung, Endpunkt, Key sowie Queue-Konfiguration.

LOG_SHIPPER_ENABLED=true
LOG_SHIPPER_ENDPOINT=https://your-log-server.com/api/ingest
LOG_SHIPPER_KEY=your-project-api-key
LOG_SHIPPER_QUEUE=redis
LOG_SHIPPER_QUEUE_NAME=logs

Praxisempfehlungen aus dem Betrieb:

  • Setzen Sie LOG_SHIPPER_QUEUE in Produktion auf einen echten Driver (redis, database, sqs). sync eignet sich eher für Debugging.
  • Nutzen Sie für Logs eine eigene Queue (LOG_SHIPPER_QUEUE_NAME=logs). So verhindern Sie, dass Log-Versand Business-Jobs verdrängt.
  • Behandeln Sie LOG_SHIPPER_KEY wie ein Secret. Er gehört in Secret-Management (Vault/KMS/CI-Variablen), nicht in Repos.

Integration in config/logging.php: Shipper sauber in den Stack hängen

Das Package bringt einen Custom-Logger mit, den Sie als Channel einhängen. Typisch ist, den Shipper zusätzlich zum lokalen Logging im stack zu nutzen.

'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'log_shipper'],
        'ignore_exceptions' => false,
    ],

    'log_shipper' => [
        'driver' => 'custom',
        'via' => \AdminIntelligence\LogShipper\Logging\CreateCustomLogger::class,
        'level' => 'error',
    ],

    // ... andere Channels
],

Ab dann verhält sich Logging im Code normal:

use Illuminate\Support\Facades\Log;

Log::error('Order processing failed', ['order_id' => 12345]);

Wenn Sie Queue-basierte Log-Auslieferung betreiben, sollten alle Beteiligten in Ihrem Team wirklich verstehen, wie Laravel Jobs ausführt, priorisiert und überwacht. Passend dazu: In unserem Beitrag zu Jobs und Warteschlangen in Laravel gehen wir tiefer auf Queue-Workflows, Worker-Betrieb und typische Fehlerbilder ein.

Der Endpunkt ist frei wählbar: JSON raus, beliebige Gegenstelle rein

Der Shipper ist so gebaut, dass er JSON verschickt. Das ist der entscheidende Punkt für Ihre Architektur: Sie müssen nicht eine spezielle Gegenstelle betreiben.

Sie können:

  • Einen eigenen kleinen HTTP-Service schreiben, der JSON annimmt und in Ihrer Datenbank ablegt.
  • Logs in ein bestehendes System leiten, sofern es einen HTTP-Ingest akzeptiert.
  • Einen Reverse Proxy vorschalten, der API-Key-Prüfung/TLS-Termination übernimmt.

Wir stellen zusätzlich eine optionale Partner-Anwendung bereit (zentraler Log-Aggregator in Laravel). Diese heißt „Logger“ und ist hier zu finden: https://github.com/ADMIN-INTELLIGENCE-GmbH/logger

Für diesen Artikel ist nur relevant: Der Shipper koppelt Sie nicht fest an diese Anwendung.

Payload-Design: Was wirklich übertragen wird (und warum Struktur gewinnt)

Ein klassisches Laravel-Log ist am Ende oft „nur“ eine Textzeile. Für Suche, Filter, Korrelation und Alerts brauchen Sie jedoch Struktur. Der Log Shipper versendet ein JSON-Objekt mit Kernfeldern und optionalen Kontextfeldern.

Kernfelder (immer dabei)

Verifiziert aus der Projektbeschreibung:

  • level (z. B. error, warning, …)
  • message
  • context (das Array aus Ihrem Log-Aufruf)
  • datetime im Format Y-m-d H:i:s.u
  • channel
  • extra (Monolog-Prozessor-Daten)

Optionale Kontextfelder (konfigurierbar)

Zusätzlich kann das Package Daten rund um Request und Umgebung mitsenden, unter anderem:

  • user_id
  • ip_address
  • user_agent
  • request_method
  • request_url
  • route_name
  • controller_action
  • app_env
  • app_debug
  • referrer

Sie können die Felder gezielt deaktivieren:

// config/log-shipper.php
'send_context' => [
    'user_id' => true,
    'ip_address' => true,
    'user_agent' => false,
    // ...
],

Empfehlung: Kontext standardisieren statt „irgendwas“ loggen

Wenn mehrere Teams/Services dieselbe zentrale Log-Suche nutzen, spart ein standardisiertes Schema massiv Zeit:

  • Nutzen Sie stabile Keys wie tenant_id, order_id, customer_id, request_id.
  • Vermeiden Sie große Arrays oder komplette Eloquent-Modelle im Kontext.
  • Loggen Sie Fehlerbedingungen maschinenlesbar (z. B. payment_provider=..., error_code=...).

Ein praktischer Ansatz ist, sich intern auf 10–20 Standardfelder zu einigen und alles andere bewusst zu vermeiden.

Datenschutz: Sanitization und IP-Obfuskation ohne Selbstbetrug

Logs sind für Incident-Analyse gemacht, aber sie enthalten schnell Daten, die Sie dort nicht haben wollen: Passwörter, Tokens, API-Keys, komplette Headers.

Data Sanitization (Redaction)

Der Shipper redigiert sensible Felder automatisch anhand typischer Pattern. Verifiziert sind unter anderem Pattern wie:

  • password, password_confirmation
  • credit_card, card_number, cvv
  • api_key, secret, token, authorization

Zusätzliche Felder können Sie ergänzen:

'sanitize_fields' => [
    'password',
    'credit_card',
    'ssn',
],

Damit vermeiden Sie, dass ein unglücklicher Log-Aufruf oder eine Exception unmaskierte Secrets nach außen sendet. Gleichzeitig gilt: Sanitization ist ein Airbag, keine Fahrweise. Regeln Sie im Team weiterhin sauber, was überhaupt geloggt werden darf.

IP Address Obfuscation: mask oder hash

Wenn IP-Adressen übertragen werden, brauchen Sie meist eine klare Entscheidung, wie Sie damit umgehen. Das Package unterstützt zwei Methoden:

Aktivierung:

LOG_SHIPPER_IP_OBFUSCATION_ENABLED=true
LOG_SHIPPER_IP_OBFUSCATION_METHOD=mask
  • mask (Default): IPv4 letztes Oktett auf 0, IPv6 letzte 64 Bit auf 0.
  • hash: Einweg-Hash mit stabiler Zuordnung (dieselbe IP ergibt denselben Hash).

Praxisentscheidung:

  • mask passt, wenn Sie nur grobe Netz-/Region-Informationen brauchen.
  • hash passt, wenn Sie wiederkehrende Clients erkennen möchten, ohne IPs zu speichern.

Transport-Sicherheit: HTTPS-Erzwingung und Payload-Limits

HTTPS Enforcement in Produktion

Laut Projektbeschreibung erzwingt das Package in Production-Umgebungen HTTPS und verwirft HTTP-Endpunkte. Das verhindert, dass API-Key und Logdaten unverschlüsselt über die Leitung gehen.

Für den Betrieb heißt das:

  • Stellen Sie TLS sauber bereit (Zertifikatskette, Hostname, Rotation).
  • Wenn Sie interne CAs nutzen, müssen Ihre Container/VMs diese CA auch vertrauen.

Wenn Sie im Zuge dessen Ihren Webserver/Proxy anfassen, kann auch HTTP/2 interessant sein, weil viele Teams beim gleichen Umbau TLS und Protokolle modernisieren. Dazu passt unser Artikel HTTP/2-Protokoll aktivieren.

Payload Size Limits (Default 1 MB)

Das Package begrenzt die maximale Payload-Größe standardmäßig auf 1 MB (1048576 Bytes). Wenn die Payload größer ist, kürzt der Shipper den Kontext und markiert ihn als getrimmt.

// config/log-shipper.php
'max_payload_size' => env('LOG_SHIPPER_MAX_PAYLOAD_SIZE', 1048576),

Warum das in echten Incidents hilft: Große Payloads kommen häufig aus „wir loggen mal eben das komplette Objekt“ oder aus Exceptions, die riesige Strukturen enthalten. Ein Größenlimit sorgt dafür, dass Logging nicht selbst zum Problem wird.

Betriebsmodi: Queue, Sync und wann Batch Shipping Sinn ergibt

Queue-Modus (empfohlen)

Im Standardfall versendet der Shipper per Queue-Job. Damit bleibt Ihre Webanwendung schnell, auch wenn das Zielsystem gerade langsam reagiert.

Best Practices:

  • Separate Queue für Logs
  • Worker-Prozesse mit klaren Ressourcenlimits
  • Monitoring der Queue-Länge und Job-Failures

Sync-Modus (gezielt einsetzen)

Sie können den Versand synchron ausführen:

LOG_SHIPPER_QUEUE=sync

Das eignet sich für Debugging oder sehr einfache Setups. In Produktion führt Sync oft zu genau dem, was Sie vermeiden wollten: zusätzliche Latenz und Fehleranfälligkeit im Requestpfad.

Batch Shipping über Redis (für hohen Durchsatz)

Wenn eine Applikation sehr viele Logevents erzeugt, kann „ein Job pro Log“ unnötigen Queue-Druck erzeugen. Dafür gibt es Batch Shipping:

LOG_SHIPPER_BATCH_ENABLED=true
LOG_SHIPPER_BATCH_SIZE=100
LOG_SHIPPER_BATCH_INTERVAL=1

Verifiziert aus der Projektbeschreibung:

  • Logs landen zunächst in einem Redis-Buffer.
  • Ein Scheduler-Command (log-shipper:ship-batch) läuft periodisch.
  • Er bündelt mehrere Logs zu einem Batch und verschickt sie in einem HTTP-Request.

Das ist besonders nützlich, wenn Sie viele identische Fehler in kurzer Zeit sehen (z. B. eine externe API fällt aus) und Sie den Overhead minimieren möchten.

Reliability-Mechanismen: Retries, Backoff, Circuit Breaker, Fallback

Wenn der Zielendpunkt ausfällt, passiert im Betrieb typischerweise Folgendes:

  • HTTP-Requests scheitern oder werden langsam.
  • Jobs laufen in Retries.
  • Ohne Schutzmechanismen wächst die Queue unendlich.

Der Log Shipper bringt mehrere Schutzschichten mit.

Retries & Backoff

Sie konfigurieren Anzahl und Wartezeiten:

'retries' => 3,
'backoff' => [2, 5, 10],

Das Verhalten ist im Betrieb angenehm vorhersehbar: kurzzeitige Störungen übersteht das System, ohne dass Sie sofort händisch eingreifen müssen.

Circuit Breaker

Der Circuit Breaker stoppt bei wiederholten Fehlern temporär das Dispatching, um Ihre Queue zu schützen:

'circuit_breaker' => [
    'enabled' => true,
    'failure_threshold' => 5,
    'duration' => 300,
],

Wichtig für die Erwartungshaltung: In der Zeit, in der der Circuit offen ist, werden Logs nicht verschickt. Das ist eine bewusste Entscheidung zugunsten der Stabilität.

Fallback-Channel

Wenn der Versand nach Retries endgültig fehlschlägt, kann ein lokaler Channel als Fallback dienen:

LOG_SHIPPER_FALLBACK=daily

Das ist sinnvoll, wenn Sie in Outage-Szenarien zumindest lokal noch eine Spur behalten möchten.

Ein typischer Fehler ist, als Fallback wieder log_shipper einzutragen und damit eine Endlosschleife zu bauen. Laut Projektbeschreibung verhindert das Package diese Konstellation.

Status Monitoring: Health-Metriken per Scheduler an den Server pushen

Neben Logevents kann das Package periodisch Statusdaten senden. Das ist kein vollständiges Monitoring-System, aber es liefert Betriebs-Signale direkt aus der Applikationsumgebung.

Aktivierung:

LOG_SHIPPER_STATUS_ENABLED=true
LOG_SHIPPER_STATUS_ENDPOINT=https://your-log-server.com/api/stats
LOG_SHIPPER_STATUS_INTERVAL=5
LOG_SHIPPER_DISK_PATH=/

Scheduler-Betrieb (Pflicht)

Status-Pushes hängen am Laravel Scheduler. Das Projekt nennt dafür den klassischen Cron-Eintrag:

* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1

Wenn Sie den Scheduler ohnehin bereits für andere Aufgaben nutzen, ist das nur ein weiterer Job. Falls nicht: Der Scheduler ist in vielen produktiven Laravel-Setups ein Muss, etwa für Aufräumjobs, Batches oder wiederkehrende Integrationen. Dazu passt unser Deep-Dive zum Laravel Scheduler.

Intervall-Logik

Verifiziert aus der Projektbeschreibung: LOG_SHIPPER_STATUS_INTERVAL interpretiert Werte als Minuten, Stunden oder täglich:

  • 1–59: alle N Minuten
  • 60–1439: alle N Stunden
  • ab 1440: täglich

Welche Metriken werden gesammelt?

Der Status-Payload kann (konfigurierbar) Informationen enthalten über:

  • PHP-Prozess-Memory (memory_usage, memory_peak)
  • Server-RAM (server_memory)
  • CPU-Auslastung (cpu_usage)
  • Disk-Space (disk_space) für einen konfigurierten Pfad
  • PHP- und Laravel-Version
  • Uptime (falls verfügbar)
  • Queue-Größe und Connection
  • DB-Status und Latenz
  • Dateigrößen und Ordnergrößen (für definierte Pfade)

Optional (laut Projektbeschreibung) gibt es „teure“ Checks, die Shell-Kommandos ausführen können:

  • Node.js/npm Versionen (node_npm)
  • Dependency Checks (dependency_checks)
  • Security Audits (security_audits)

Diese Checks sind per Default deaktiviert und sollten es in den meisten Installationen auch bleiben, bis Sie den Nutzen klar belegen können.

Konfiguration der Status-Sektion

Ein Ausschnitt, wie Sie Metriken, Dateien und Ordner steuern:

'status' => [
    'metrics' => [
        'system' => true,
        'queue' => true,
        'database' => true,
        'cache' => false,
        'filesize' => true,
        'foldersize' => true,

        'node_npm' => false,
        'dependency_checks' => false,
        'security_audits' => false,
    ],

    'monitored_files' => [
        storage_path('logs/laravel.log'),
        storage_path('logs/worker.log'),
    ],

    'monitored_folders' => [
        storage_path('logs'),
        storage_path('app/cache'),
    ],
],

Aus Betriebssicht lohnt es sich, diese Liste bewusst klein zu halten. Viele Teams überwachen lieber wenige, aber klare Signale:

  • Disk-Usage am Datenpfad
  • Queue-Länge
  • DB-Connectivity
  • Folder size von storage/logs (als Indikator für Log-Spikes)

Hier sehen Sie, wie so ein Status Update formattiert ist:

Upgrade-Hinweise: Warum ein Config-Republish manchmal nötig ist

Das Projekt weist explizit darauf hin, dass neue Features teilweise neue Config-Optionen benötigen (z. B. IP-Obfuskation, Payload-Limits oder erweiterte Status-Metriken). Wenn Sie upgraden und diese Features nutzen möchten, müssen Sie die Config neu publizieren.

In der Praxis bewährt sich dieses Vorgehen:

  1. Neue Version installieren.
  2. Aktuelle config/log-shipper.php sichern.
  3. Config mit --force republishen.
  4. Diff/ Merge sauber durchführen.
  5. Deployment inklusive Worker-Restart.

Typische Fehlerbilder und wie Sie sie vermeiden

Endpunkt ist erreichbar, aber nichts kommt an

Checkliste:

  • Ist LOG_SHIPPER_ENABLED=true?
  • Ist der Channel wirklich im stack?
  • Loggen Sie mindestens auf dem konfigurierten Level (error vs. info)?
  • Läuft der Queue-Worker und verarbeitet die Queue, die Sie konfiguriert haben (LOG_SHIPPER_QUEUE_NAME)?

Queue wächst, wenn der Log-Server down ist

  • Circuit Breaker aktivieren und Schwellenwerte passend setzen.
  • Retries/Backoff so konfigurieren, dass Sie nicht minutenlang „sinnlos“ retried.
  • Batch Shipping kann zusätzlich helfen, wenn das Problem durch zu viele Einzeljobs entsteht.

Datenschutz: „Wir shippen zu viel“

  • send_context reduzieren.
  • IP-Obfuskation aktivieren.
  • Sanitization-Liste um projektspezifische Felder ergänzen (z. B. session, jwt, access_token).
  • Keine kompletten Request-Bodies oder Headers in den Kontext werfen.

Performance: Payloads werden zu groß

  • Payload-Limit beibehalten oder sogar reduzieren.
  • Kontext bewusst klein halten.
  • Große Objekte (z. B. Eloquent-Modelle) vorher auf wenige Felder mappen.

Einordnung: Wann Laravel Log Shipper die richtige Lösung ist

Laravel Log Shipper passt besonders gut, wenn:

  • Sie viele Laravel-Projekte betreiben und Logs zentral suchen möchten.
  • Ihre Anwendungen in mehreren Instanzen laufen und lokales File-Logging nicht mehr reicht.
  • Sie Logs strukturiert verarbeiten wollen (JSON statt Text).
  • Sie Wert auf robuste Failure-Mechanismen legen (Queue, Retries, Circuit Breaker).

Wenn Sie hingegen primär „klassische“ Infrastruktur-Logs aus Syslog/NGINX/Kernel sammeln, ist ein dediziertes Log-Aggregationssystem für Infrastruktur oft die bessere Primärquelle. Der Shipper ergänzt dann die Applikationssicht.

Weitere praxisnahe Artikel rund um Laravel und Betrieb finden Sie auf https://blog.admin-code.de/. Wenn Sie Unterstützung beim sauberen Betrieb von Laravel-Stacks, Queues und Monitoring-Setups benötigen, können Sie uns auch direkt über https://www.admin-intelligence.de/kontakt/ erreichen.