想在套件中提供專屬的日誌輸出時,請將建立 logger 的機制與使用端應用程式所選擇的通道設定分開。單純註冊驅動並不會重新建立既有的通道,即使變更設定後,仍可能繼續使用舊的 Handler。
本頁確認了 Laravel Framework v13.39.0 的實作。基本操作請參閱日誌,提供者的準備請參閱套件開發。
選擇需要的擴充點
若既有驅動就能實現,就不需要再新增自訂驅動。
via 的類別與 tap 的類別會由容器解析,因此也可以使用建構子注入。tap 是在建立通道時進行的調整,並不是每寫入一筆日誌就執行的 hook。
logging.channels.audit 中的 audit 是通道名稱,其中的 driver 則是建立方式的名稱。Log::channel('audit') 選擇的是通道名稱,因此即使之後變更驅動,呼叫端仍可維持相同的名稱。
先以 custom 與 via 公開工廠
以虛構的 Acme\Audit 套件為例,提供用於檔案輸出的工廠。將其放在可透過 PSR-4 載入的 src/Logging/CreateAuditLogger.php。
在使用端應用程式中,於 config/logging.php 既有的 channels 陣列加入下列元素。既有的 default 與 stack 設定請保留。
LogManager::createCustomDriver() 會從容器取得 via 的類別,並以通道的設定陣列作為唯一引數呼叫它。若只使用這種方式,就不需要用於註冊驅動的提供者。
這個工廠會明確地將 level 傳給 StreamHandler。LogManager 並不會將設定中的 level 一律套用到 custom 工廠所回傳的 logger。新增自訂設定項目時,也請在工廠端自行反映。
若只是要變更檔案的輸出位置,使用 single 驅動就足夠了。這個範例是用來嵌入套件專屬 Handler 或初始化處理的最小設定。via 請使用可儲存在設定快取中的類別名稱,不要在設定陣列中存放閉包或 logger 的實例。
提供自訂驅動名稱時使用 extend
若想對使用端公開 driver: acme-audit 這樣的設定,請註冊使用同一個工廠的驅動。在 src/AuditServiceProvider.php 的 boot() 中擴充 Laravel 的 log 服務。
提供者可透過套件自動探索等方式載入。使用端的 channels.audit 請從先前的設定替換為下列設定,不再需要 via。
- Laravel 的
LogServiceProvider 會將 log 註冊為 singleton。callAfterResolving() 在尚未解析時會等待,若已解析則也會將註冊反映到該實例。詳情請參閱服務解析後的 hook。
extend() 只會註冊用於建立的閉包。註冊這個回呼時,並不會建立通道或寫入日誌。
callCustomCreator() 會將 $app 與通道設定陣列傳給閉包,這與傳給 via 工廠的引數不同。
- 以相同驅動名稱重新註冊時,會覆寫用於建立的閉包。請勿使用
single 等內建名稱,或可能與其他套件衝突的名稱。
在確認的實作中,傳給 extend() 的匿名閉包會被重新綁定到 LogManager 的作用域。非 static 閉包中的 $this 並不是服務提供者。請如範例般使用 static 閉包並透過引數的 $app 實作,不要依賴提供者的 $this。
套件負責提供驅動,要在哪個通道或 stack 中使用則由使用端選擇。請設計成不會在提供者中改寫 logging.default 或整個既有的 logging.channels。
通道會依名稱保留
LogManager::get() 會將解析完成的通道儲存在 $channels 中。取得同名的通道時,不會重新讀取設定陣列,而是回傳已保留的物件。
這與儲存在檔案中的設定快取不同,是目前 LogManager 內部的物件保留。僅變更 config() 或重新註冊 extend(),並不會替換已建立的 Handler。
此範例中的 $same 與 $before 是同一個物件。forgetChannel('audit') 解除 Manager 的保留後,下一次取得時會依新設定建立 $after。
forgetChannel 不會更新既有的參照
forgetChannel() 所做的是刪除 Manager 內的項目。
- 持有
$before 的服務之後仍可繼續使用舊的 logger。這並不是將既有參照替換為新物件的操作。
- 透過
Log::channel('stack') 等方式建立的 stack,擁有彙整子通道 Handler 與 Processor 的獨立 Monolog。即使只對子通道執行 forgetChannel(),既有 stack 的組成也不會改變。必要時,請也解除該 stack 通道名稱的保留並重新取得。
forgetChannel() 本身不會呼叫 Handler 的 close(),並不是保證連線或緩衝區結束處理的 API。
在一般的部署中,請將設定快取的更新與所使用之長時間執行 worker 的重新啟動納入維運流程。不要依賴每次請求都改寫設定並重新解析的機制,也請考量服務的生命週期。
加入 stack 時也需要相容於 Monolog 的操作
自訂驅動可以回傳 Psr\Log\LoggerInterface 的實作,但 Laravel 的 createStackDriver() 會對子通道呼叫 getHandlers() 與 getProcessors()。這些方法並不包含在 PSR 的介面中。
回傳任意 PSR logger 不一定能在 stack 中運作,因此若套件也要支援加入 stack,請如範例般回傳 Monolog,或明確說明其使用範圍並加以測試。
不只使用 mock,也要驗證實際的通道解析
僅使用 Log::shouldReceive('channel'),無法確認提供者的註冊、工廠的選擇與設定的反映。以下是在套件的測試環境中,實際經過 Manager 與工廠的範例。
測試用的輸出位置指定為 php://memory。這裡驗證通道的建構與 Handler 的設定,並不實際寫入日誌。
LogManager::get() 可能會捕捉建立通道時發生的例外,並回傳 emergency logger。僅確認「沒有拋出例外」或「是 LoggerInterface」,並不能證明目標通道已成功建構。上面的測試也確認了 Monolog 的名稱、Handler 與日誌等級,避免將 fallback 誤認為成功。建立後發生的寫入錯誤,不一定都會被同一機制捕捉。
長期維護時應追加的驗證
參考的第一手資料