01、記錄器的生命週期
Serilog 大多數情況下“只需使用”,並且在建立和處理日誌記錄器時不需要過多考慮。然而,由於以下原因:
某些接收器(sink)涉及後臺程序,特別是那些使用網路的接收器;
一些接收器(尤其是檔案和滾動檔案接收器)所持有的資源;
因此,某些使用模式在效果和可靠性上表現得更好。
1、在所有應用程式中
使用 Serilog 最簡單的方法是透過全域性 Log 類:
Log.Logger = new LoggerConfiguration()
.WriteTo.File(@"myapp\log.txt")
.CreateLogger();
Log.Information("Hello!");
// Your application runs, then:
Log.CloseAndFlush();
如果這樣做,只需配置一次日誌記錄器,然後在應用程式的整個生命週期中使用它。
要建立更專業的日誌記錄器:
呼叫 Log.ForContext(...) 來接收一個附加了額外屬性的 ILogger;這不需要任何特殊的關閉/重新整理邏輯,因為這將由父日誌記錄器處理。
在少數情況下,可以使用單獨的 LoggerConfiguration 建立一個額外的 ILogger,並使用 WriteTo.Logger(Log.Logger) 將事件傳遞到根日誌記錄器;在這種情況下,必須遵循下面的處置邏輯。
2、不使用 Log
如果您不希望使用靜態 Log 類,可以使用 LoggerConfiguration 建立一個 ILogger。
using (var log = new LoggerConfiguration()
.WriteTo.File(@"myapp\log.txt")
.CreateLogger())
{
log.Information("Hello again!");
// Your app runs, then disposal of `log` flushes any buffers
}
在這種情況下,不使用 Log.CloseAndFlush()。相反,當應用程式不再需要日誌記錄器時,會進行處置。
只有透過 LoggerConfiguration 建立的根日誌記錄器需要以這種方式處理。從 ForContext() 和類似方法返回的 ILoggers 不需要任何特殊處理。
3、使用 IoC 容器
請參見 Autofac 整合示例,瞭解如何與 Autofac IoC 容器一起使用可注入的 ILoggers。如果您希望更新此頁面以提供其他容器的說明,請提出問題。
02、可靠性
Serilog 認為,綜合考慮,日誌記錄的優先順序低於其他應用程式程式碼,絕不應在可避免的情況下影響正在執行的應用程式的操作。
在實踐中,這主要轉化為一種處理 Serilog 中異常的策略。在這個過程中會對可用性做出一些妥協。本文件解釋了作為 Serilog 庫使用者您可以期待的內容,以及如果您正在擴充套件 Serilog,如何編寫與其餘程式碼庫良好配合的程式碼。
1、配置
在配置時,即呼叫 LoggerConfiguration 方法時,錯誤分為兩類。
執行時配置錯誤
如果由於主機機器的執行時狀態無法配置接收器(sink),Serilog 將捕獲任何導致的異常並將其寫入 SelfLog(參見除錯和診斷)。
// X: does not exist, but this is a runtime condition
// so Serilog will not fail here.
Log.Logger = new LoggerConfiguration()
.WriteTo.File("X:\\log.txt")
.CreateLogger();
這種策略可以防止部署環境中的暫時性問題導致原本有效的應用程式失敗。
開發時不變性違規
在配置時,對於永遠無法有效執行的程式碼(例如,違反 API 不變性的情況)會做出一定的容忍:
// Null is never acceptable as a filename, so
// Seriog will throw ArgumentNullException here.
Log.Logger = new LoggerConfiguration()
.WriteTo.File(null)
.CreateLogger();
這一決定基於兩個原因:
這些錯誤不太可能透過開發者的工作站或測試環境,因為日誌記錄配置發生在啟動時,並且應該總是以相同的方式失敗。
允許無效值或靜默忽略它們會導致意外行為,這很難除錯,並使庫的正確配置更加困難。
如果您願意,可以將日誌記錄配置程式碼包裝在 try/catch 結構中,以避免異常傳播,但不推薦這樣做。
接收器作者: 實現這一點的責任在於接收器的實現本身,因此需要明確考慮/處理。
2、寫入日誌事件
事件分階段寫入日誌記錄管道。首先呼叫記錄器,然後構造事件,接下來對其進行豐富,應用過濾器,最後將其傳遞(“發出”)到配置的接收器。
呼叫記錄器
ILogger 和靜態 Log 類上的方法會靜默忽略無效引數:
// Safely does nothing
Log.Warning(null);
這樣做是因為在執行頻率較低的程式碼路徑中的日誌記錄語句可能不會被測試,因此不應在執行時失敗。
構造日誌事件
當構造日誌事件時,Serilog 可能會反射任何解構物件的屬性。
如果這些屬性丟擲異常,Serilog 會捕獲錯誤,寫入 SelfLog,並在解構物件中包含錯誤資訊而不是屬性值。
關於型別載入的說明
如果一個應用程式在沒有所需依賴項的情況下部署,載入器可能會無法找到/構造有效型別。這是一個主要的應用程式配置錯誤,可能在解構過程中或稍後,例如在 JIT 階段顯現出來。這種情況非常罕見。在這種情況下,Serilog 不會做任何事情,允許錯誤傳播。
裝飾器
裝飾器向日志事件新增屬性。裝飾器可能會丟擲異常:Serilog 會捕獲這些異常並寫入 SelfLog。
裝飾器作者: Serilog 自身實現了這一策略,裝飾器在意外失敗時應丟擲異常(儘管出於效能考慮,避免這種情況是明智的)。
過濾器
過濾器決定哪些事件會被傳遞到日誌記錄管道中。過濾器不會丟擲異常,而是寫入 SelfLog:
// No events will be carried through this pipeline, but no
// errors will be thrown.
Log.Logger = new LoggerConfiguration()
.Filter.ByExcluding(e => { throw new Exception(); })
.WriteTo.ColoredConsole()
.CreateLogger();
過濾器作者: 如果過濾器意外丟擲異常,這被視為一個錯誤——這對效能和功能的影響是顯著的。
發出到接收器
Serilog 捕獲並將接收器引發的任何異常寫入 SelfLog。通常情況下,這些異常是正常使用 Serilog 時發生的絕大多數異常。
接收器作者: 接收器在失敗時應丟擲異常。Serilog 將一致地捕獲和處理這些異常。
關於不可捕獲的異常的說明
需要注意的是,仍然存在一類不可捕獲的異常,Serilog 被迫傳播這些異常,例如 StackOverflowException 等。
3、非同步/批處理網路操作
許多 Serilog 接收器使用相同的基礎 PeriodicBatchingSink 架構。這些接收器(例如批處理的 Azure 表儲存接收器、CouchDB 接收器、RavenDB 接收器和 Seq 接收器(在非持久模式下))會快取日誌事件,從而減少將日誌資料傳輸到遠端主機所需的網路往返次數。
Log.Logger = new LoggerConfiguration()
.WriteTo.CouchDB("api/missing")
.CreateLogger()
這些接收器在寫入事件時不會失敗,但可能會在後臺非同步傳送批次時失敗。當批次傳送失敗時,詳細資訊將寫入 SelfLog。
正在傳送的批次將保留在記憶體中,並會以逐漸增加的時間間隔重試,時間間隔從 5 秒逐步增加到 10 分鐘。增加的時間間隔可以保護接收器,在經歷一段停機後重新上線時,避免連線洪水。
如果經過 4 次嘗試仍無法傳送批次,該批次將被丟棄並嘗試新的批次。這可以防止接收器拒絕的“壞”事件堵塞日誌記錄器。後續的成功將允許其他批次正常傳輸。
如果再有兩次嘗試失敗(總共 6 次失敗,通常在 10 分鐘左右),等待的日誌事件的整個緩衝區將被丟棄。這可以防止在日誌事件長時間無法送達時出現記憶體不足錯誤。
如果連線仍然中斷,緩衝區將每 10 分鐘重新整理一次,直到重新建立連線。
接收器作者: 透過從 PeriodicBatchingSink 派生,可以預設提供此行為。如果需要不同的行為,則需要實現自定義的 ILogEventSink。
注:相關原始碼都已經上傳至程式碼庫,有興趣的可以看看。https://gitee.com/hugogoos/Planner