淺談 .NET 預設 Logger 及其優化技巧
TLDR
- .NET 預設已整合 Console、Debug 與 EventSource 等 Logger Provider。
- 透過
appsettings.json的LogLevel區段,可針對不同命名空間或 Provider 設定精細的日誌過濾。 - 使用
logger.IsEnabled(LogLevel)可避免在日誌等級未啟用時,執行昂貴的資料運算。 - 應優先使用結構化日誌(使用
{Placeholder}),而非字串串接,以提升效能並利於 ELK 等系統分析。 - 若需在日誌中顯示大括號,必須使用雙大括號
進行轉義。 - 建議使用
AddJsonConsole()輸出結構化 JSON,以利於現代化日誌管理。
預設 Logger 機制與設定
WebApplication.CreateBuilder() 在初始化時會自動載入預設的 Logger Provider,包含 Console、Debug 及 EventSource。開發者可透過 builder.Logging 進行自訂,例如清除預設設定或加入特定 Provider。
什麼情況下會遇到這個問題:當專案需要調整預設的日誌輸出目的地(如僅輸出至 Console)或需要完全控制日誌來源時。
csharp
// 調整 LoggerProvider 的範例
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders(); // 清除預設的 Provider
builder.Logging.AddConsole(); // 加入 Console Provider
builder.Logging.AddDebug(); // 加入 Debug Provider使用 Appsettings.json 控制日誌等級
透過設定檔,可以針對特定命名空間(Namespace)或 Provider 設定不同的日誌等級。
什麼情況下會遇到這個問題:當生產環境中需要隱藏過多的 Debug 資訊,但又希望保留特定服務的 Warning 或 Error 紀錄時。
json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"LoggerTest.Services.TestService1": "Warning"
}
}
}- 外層
LogLevel適用於所有 Provider。 - 可使用萬用字元
*進行批次設定,例如"*.Services": "Warning"。
效能優化:使用 Logger.IsEnabled
在產生日誌前若涉及昂貴的運算(如資料庫查詢),應先檢查該等級是否啟用。
什麼情況下會遇到這個問題:當日誌內容需要從資料庫撈取大量資料或進行複雜計算,且該日誌等級在生產環境通常是關閉的。
csharp
if (logger.IsEnabled(LogLevel.Information)) {
// 僅在 Information 等級啟用時執行,避免浪費效能
int processedRecords = await database.GetProcessedRecordsCount();
logger.LogInformation("系統已完成資料更新,共處理 {Count} 筆資料。", processedRecords);
}結構化日誌的優勢與實作
結構化日誌能避免字串串接帶來的效能損耗,並能將參數分離,利於後續的日誌分析工具(如 ELK)進行索引。
什麼情況下會遇到這個問題:當需要將日誌匯出至集中式管理系統,或希望提升應用程式在高負載下的日誌處理效能時。
正確的結構化寫法
避免使用字串相加,改用佔位符:
csharp
// 推薦寫法
logger.LogInformation("使用者 {UserId} 已登入系統,所屬部門: {Department}", user.Id, user.Department);特殊字元處理
若日誌內容本身包含大括號,必須使用雙大括號進行轉義:
csharp
// 輸出結果為:這是一個 JSON 範例:{"name": "value"}
logger.LogInformation("這是一個 JSON 範例:{{\"name\": \"value\"}}");輸出 JSON 格式
將 AddConsole() 替換為 AddJsonConsole(),可直接輸出結構化資料:
csharp
builder.Logging.AddJsonConsole();異動歷程
- 2025-03-23 初版文件建立。
