Zum Hauptinhalt springen
Wenn ein Paket eine eigene Log-Ausgabe bereitstellen soll, trennen Sie den Mechanismus, der den Logger erzeugt, von der Channel-Konfiguration, die die nutzende Anwendung wählt. Das bloße Registrieren eines Treibers baut bestehende Channels nicht neu auf, und auch nach einer Konfigurationsänderung kann weiterhin der alte Handler verwendet werden. Diese Seite basiert auf der Implementierung von Laravel Framework v13.39.0. Die Grundlagen finden Sie unter Logging, die Vorbereitung des Providers unter Paketentwicklung.

Den passenden Erweiterungspunkt wählen

Wenn sich das Ziel mit einem vorhandenen Treiber erreichen lässt, müssen Sie keinen eigenen Treiber hinzufügen. Die Klassen für via und tap werden über den Container aufgelöst, sodass auch Konstruktorinjektion möglich ist. tap ist eine Anpassung bei der Erzeugung des Channels und kein Hook, der bei jedem einzelnen Log-Eintrag ausgeführt wird.
audit in logging.channels.audit ist der Channel-Name. Das darin enthaltene driver ist der Name der Erzeugungsmethode. Mit Log::channel('audit') wählen Sie den Channel-Namen, sodass der Name auf Aufruferseite erhalten bleibt, auch wenn Sie später den Treiber ändern.

Zuerst eine Factory mit custom und via veröffentlichen

Im fiktiven Paket Acme\Audit stellen wir eine Factory für die Ausgabe in eine Datei bereit. Sie wird in src/Logging/CreateAuditLogger.php abgelegt, das per PSR-4 geladen werden kann.
In der nutzenden Anwendung fügen Sie dem bestehenden channels-Array in config/logging.php das folgende Element hinzu. Die vorhandenen Einstellungen für default und stack bleiben erhalten.
LogManager::createCustomDriver() bezieht die Klasse aus via über den Container und ruft sie mit dem Konfigurations-Array des Channels als einzigem Argument auf. Bei diesem Ansatz allein ist kein Provider für die Treiberregistrierung nötig.
Diese Factory übergibt level explizit an den StreamHandler. Der LogManager wendet das level aus der Konfiguration nicht pauschal auf den Logger an, den eine custom-Factory zurückgibt. Auch wenn Sie eigene Konfigurationsoptionen hinzufügen, müssen diese in der Factory berücksichtigt werden.
Wenn Sie nur das Ausgabeziel der Datei ändern möchten, genügt der Treiber single. Dieses Beispiel ist die minimale Konfiguration, um paketeigene Handler oder Initialisierungen einzubinden. Geben Sie für via einen Klassennamen an, der im Konfigurations-Cache gespeichert werden kann, und legen Sie im Konfigurations-Array keine Closures oder Logger-Instanzen ab.

Für eigene Treibernamen extend verwenden

Wenn Sie der nutzenden Seite eine Konfiguration wie driver: acme-audit anbieten möchten, registrieren Sie einen Treiber, der dieselbe Factory verwendet. In boot() von src/AuditServiceProvider.php erweitern Sie den log-Service von Laravel.
Der Provider wird etwa über die Paket-Erkennung geladen. Auf der nutzenden Seite ersetzen Sie channels.audit durch die folgende Konfiguration anstelle der vorherigen. via wird nicht mehr benötigt.
  • Der LogServiceProvider von Laravel registriert log als Singleton. callAfterResolving() wartet, solange der Service noch nicht aufgelöst ist, und wendet die Registrierung auch auf die Instanz an, wenn er bereits aufgelöst wurde. Details finden Sie unter Hooks nach der Service-Auflösung.
  • extend() registriert lediglich die Erzeugungs-Closure. Beim Registrieren dieses Callbacks werden weder Channels erzeugt noch Logs geschrieben.
  • callCustomCreator() übergibt der Closure $app und das Konfigurations-Array des Channels. Das unterscheidet sich von den Argumenten, die an eine via-Factory übergeben werden.
  • Eine erneute Registrierung desselben Treibernamens überschreibt die Erzeugungs-Closure. Verwenden Sie keine eingebauten Namen wie single und keine Namen, die mit anderen Paketen kollidieren.
In der geprüften Implementierung wird eine an extend() übergebene anonyme Closure an den Scope des LogManagers neu gebunden. $this in einer nicht-statischen Closure ist nicht der Service Provider. Verwenden Sie wie im Beispiel eine statische Closure mit dem Argument $app und machen Sie die Implementierung nicht von $this des Providers abhängig.
Das Paket stellt den Treiber bereit, und die nutzende Seite entscheidet, in welchem Channel oder Stack er verwendet wird. Gestalten Sie den Provider so, dass er weder logging.default noch die gesamten bestehenden logging.channels überschreibt.

Channels werden pro Name zwischengespeichert

LogManager::get() speichert aufgelöste Channels in $channels. Wird ein Channel mit demselben Namen erneut abgerufen, liest der Manager das Konfigurations-Array nicht neu ein, sondern gibt das zwischengespeicherte Objekt zurück. Dies ist nicht der in Dateien gespeicherte Konfigurations-Cache, sondern das Vorhalten von Objekten im aktuellen LogManager. Eine Änderung über config() oder eine erneute Registrierung mit extend() allein tauscht bereits erzeugte Handler nicht aus.
In diesem Beispiel ist $same dasselbe Objekt wie $before. Sobald forgetChannel('audit') den Eintrag im Manager entfernt, wird beim nächsten Abruf $after aus der neuen Konfiguration erzeugt.

forgetChannel aktualisiert keine bestehenden Referenzen

forgetChannel() entfernt lediglich den Eintrag im Manager.
  • Services, die $before halten, können den alten Logger weiterhin verwenden. Die Operation ersetzt bestehende Referenzen nicht durch das neue Objekt.
  • Ein bereits erzeugter Stack, etwa über Log::channel('stack'), besitzt ein eigenständiges Monolog, das die Handler und Processors der untergeordneten Channels sammelt. Wenn Sie nur einen untergeordneten Channel mit forgetChannel() entfernen, ändert sich die Zusammensetzung des bestehenden Stacks nicht. Entfernen Sie bei Bedarf auch den Eintrag für den Channel-Namen des Stacks und rufen Sie ihn neu ab.
  • forgetChannel() selbst ruft close() der Handler nicht auf. Die API garantiert nicht das Beenden von Verbindungen oder Puffern.
Nehmen Sie bei gewöhnlichen Deployments die Aktualisierung des Konfigurations-Caches und den Neustart verwendeter langlebiger Worker in den Betriebsablauf auf. Verlassen Sie sich nicht auf einen Mechanismus, der die Konfiguration pro Request umschreibt und neu auflöst, und berücksichtigen Sie auch den Lebenszyklus von Services.

Für die Einbindung in einen Stack sind auch Monolog-kompatible Operationen nötig

Ein eigener Treiber kann eine Implementierung von Psr\Log\LoggerInterface zurückgeben, doch createStackDriver() von Laravel ruft bei untergeordneten Channels getHandlers() und getProcessors() auf. Diese sind nicht Teil des PSR-Interfaces. Da ein beliebiger PSR-Logger nicht zwangsläufig auch in einem Stack funktioniert, sollten Pakete, die auch die Einbindung in einen Stack unterstützen, wie im Beispiel ein Monolog zurückgeben oder den Einsatzbereich explizit angeben und testen.

Nicht nur Mocks, sondern die tatsächliche Channel-Auflösung prüfen

Mit Log::shouldReceive('channel') allein lassen sich die Registrierung des Providers, die Auswahl der Factory und die Übernahme der Konfiguration nicht prüfen. Das folgende Beispiel nutzt die Testumgebung für Pakete und durchläuft den tatsächlichen Manager und die Factory. Als Ausgabeziel für Tests wird php://memory angegeben. Hier werden der Aufbau des Channels und die Einstellungen des Handlers geprüft, ohne tatsächlich Logs zu schreiben.
LogManager::get() fängt Exceptions ab, die während der Erzeugung eines Channels auftreten, und kann stattdessen den Emergency-Logger zurückgeben. Die Feststellungen „Es trat keine Exception auf“ oder „Es war ein LoggerInterface“ allein beweisen nicht, dass der gewünschte Channel aufgebaut wurde. Der obige Test prüft daher auch den Namen, den Handler und das Log-Level des Monolog-Loggers, damit ein Fallback nicht fälschlich als Erfolg gewertet wird. Schreibfehler nach der Erzeugung werden nicht zwangsläufig über denselben Mechanismus abgefangen.

Zusätzliche Prüfungen für die langfristige Wartung

Verwendete Primärquellen

Zuletzt geändert am 28. Oktober 2026