Serilog 日志庫的簡介
〇、前言
相較于 log4net,Serilog 則是新項目的首選,現代化、高性能、易用,是 .NET 日志的未來方向。如需了解 log4net 詳見往期博文://www.lnzwny.com/hnzhengfy/p/19121607/log4net。
log4net 雖然是(shi)一個廣泛使用(yong)的、功能強(qiang)大的日志記(ji)錄庫,而且專(zhuan)為(wei) .NET 平(ping)臺設(she)計,但由(you)于她(ta)是(shi)較老的日志框架(jia),其生態在 .NET Core/.NET 5+ 環境(jing)中支持有限,因此還是(shi)要(yao)慎重考(kao)慮。
| 方面 | log4net | Serilog |
|---|---|---|
| 日志模型 | 文本日志:日志是純字符串,信息混在一起。 例如:"用戶 alice 從 192.168.1.1 登錄" |
結構化日志:日志是帶屬性的數據事件。 例如:"用戶 {UserName} 從 {IP} 登錄",UserName="alice", IP="192.168.1.1" |
| 配置方式 | 主要靠 XML 文件,復雜且不易讀。 | 支持代碼和 appsettings.json,更現代、簡潔。 |
| API 使用 | 需為每個類創建 logger 實例,較繁瑣。 | 提供靜態 Log 類,一行代碼即可記錄,更簡單。 |
| 生態系統 | 老牌庫,Appenders 豐富但更新慢。 | Sinks 生態活躍,原生支持 Elasticsearch、Seq、云平臺等。 |
| .NET 集成 | 在 ASP.NET Core 中集成較麻煩。 | 與 ASP.NET Core 深度集成,自動記錄請求上下文。 |
| 維護狀態 | 活躍維護,持續更新,最新版 3.2.0(20250823)。 | 活躍維護,持續更新,支持最新 .NET 版本。 |
經過對比,當然還是 Serilog 更有優勢,那么本文將就概念方面來詳細介紹下,后續(xu)會(hui)繼續(xu)更(geng)新相關用法實踐,有興趣可持續(xu)關注。
一、Serilog 簡介
Serilog 是一個為 .NET 應用程序設計的強大的診斷日志庫,以易于設置、簡潔的 API 和跨所有最新 .NET 平臺的兼容性而著稱。
它是 .NET 平臺中非常流行且強大的結構化日志庫,其最大特點是結構化日志記錄(Structured Logging)。
1.1 核心特點:結構化日志記錄(Structured Logging)
日志(zhi)不是簡單(dan)的(de)字符串(chuan),而是包含命名屬(shu)性的(de)結構化(hua)事件。
// 例如記錄用戶登錄事件
string username = "bob";
string clientIp = "10.0.0.5";
int userId = 789;
Log.Information("用戶 {UserName} (ID: {UserId}) 從 {ClientIP} 登錄",
username, userId, clientIp);
// 生成的結構化數據(以 JSON 形式表示):
{
"Timestamp": "2025-03-28T14:30:00Z",
"Level": "Information",
"MessageTemplate": "用戶 {UserName} (ID: {UserId}) 從 {ClientIP} 登錄",
"RenderedMessage": "用戶 bob (ID: 789) 從 10.0.0.5 登錄",
"Properties": {
"UserName": "bob",
"UserId": 789,
"ClientIP": "10.0.0.5"
}
}
// 可以直接在 Seq 或 Elasticsearch 中搜索,例如:UserName = 'bob'
這樣便(bian)于和另外的(de)日(ri)志(zhi)接收系統對接,針(zhen)對日(ri)志(zhi)量較(jiao)大的(de)場景(jing)比較(jiao)友好(hao)。
1.2 核心特點:強大的 Sink 生態系統
Serilog 通過 Sinks(接收器)將日志事件發送到各種目的地。這種(zhong)“即插即用”的(de)(de)架構是 Serilog 強大和流行(xing)的(de)(de)關鍵。
官方 Sinks:Serilog.Sinks.Console(控制臺)、Serilog.Sinks.File(文件)、Serilog.Sinks.Seq(Seq 服務器)、Serilog.Sinks.Elasticsearch(Elasticsearch)等。
第三方 Sinks:支持 Splunk、DataDog、Graylog、Kafka、RabbitMQ、AWS CloudWatch 等(deng)
優勢:可以同時配置多個 Sinks,將同一日志事件發送到不同地方。
工作原理:
日志事件生成:業務端代碼調用 Log.Information("..."),Serilog 創建一個 LogEvent 對象。
事件處理:該事件可能經過過濾、豐富(Enrichment)等處理。
分發到 Sinks:Serilog 核心引擎將處理后的 LogEvent 分發給所有配置的 Sinks。
Sink 執行寫入:每個 Sink 根(gen)據(ju)自己的(de)邏輯,將 LogEvent 轉換為適合目(mu)標系統的(de)格式(如(ru) JSON、文本行),并通過相應協議(yi)(如(ru) HTTP、文件 I/O)發(fa)送出去。
1.3 核心特點:簡潔易用的 API
使(shi)用靜態 Log 類(lei),無需依賴(lai)注入(ru)即可在任何(he)地方記錄(lu)日志。
它極大(da)(da)地簡化了日(ri)(ri)志(zhi)記錄的(de)開(kai)發體驗,讓(rang)開(kai)發者能夠以最(zui)少的(de)代碼、最(zui)直觀的(de)方(fang)式完(wan)成強大(da)(da)的(de)日(ri)(ri)志(zhi)功(gong)能。
// Serilog:全局靜態 Log 類,任何地方直接調用
Log.[Level](string messageTemplate, params object[] propertyValues); // 格式
Log.Verbose("調試信息: {Detail}", detail); // 示例
Log.Debug("進入方法: {MethodName}", "ProcessOrder");
Log.Information("訂單 {OrderId} 已創建", orderId);
Log.Warning("庫存不足,商品 {ProductId}", productId);
Log.Error(exception, "處理支付失敗,用戶 {UserId}", userId);
Log.Fatal("應用程序即將退出");
// 對比傳統日志庫 log4net 的方式:
// 每個類都需要聲明一個 logger 實例,代碼重復,不夠優雅
private static readonly ILog log = LogManager.GetLogger(typeof(MyClass));
public void DoSomething()
{
log.Info("用戶登錄");
}
支持所有(you)標準日(ri)志級(ji)別:Verbose,Debug,Information,Warning,Error,Fatal。
使用 {PropertyName} 占位符,清晰表達意圖。
自動提取屬性值,生成結構化數據。
支持格式化說明符,如 {LoginTime:HH:mm}。
即使沒(mei)有(you)提(ti)供(gong)參數,也不會拋出(chu)異常(會原樣輸(shu)出(chu)占位符)。
// 異常處理的優雅支持
// 記錄異常是常見需求,Serilog 提供了專門的重載,讓異常日志既簡潔又完整
try
{
// ...
}
catch (Exception ex)
{
Log.Error(ex, "處理訂單 {OrderId} 失敗", orderId);
}
// 第一個參數是 Exception 對象,Serilog 會自動捕獲堆棧跟蹤。
// 后續參數用于填充消息模板中的屬性。
// 生成的日志事件同時包含異常詳情和業務上下文。
| 特性 | 如何體現“簡潔易用” |
|---|---|
| 靜態 Log 類 | 全局可用,無需實例化,減少樣板代碼 |
| 統一方法簽名 | 所有日志級別使用相同模式,降低記憶負擔 |
| 消息模板 | 結構化日志 + 直觀語法,代碼可讀性強 |
| 異常支持 | 一行代碼記錄異常和上下文 |
| 異步/批量 | 通過 .Async() 包裝器輕松實現,無需手動線程管理 |
| 豐富 Sink | 配置簡單,更換輸出目標只需修改幾行代碼 |
1.4 核心特點:異步日志記錄
Serilog 通過將日志寫入操作從應用程序的主要執行線程中分離出來,顯著(zhu)提升了應(ying)(ying)用(yong)程序的性能和響應(ying)(ying)能力(li)。
異步非由 Serilog 核心庫直接實現,而是通過 Serilog.Sinks.Async 這個專門的包來完成。特別適(shi)合高(gao)并(bing)發、高(gao)吞吐量(liang)的(de)場景。
在傳統的同步日志中,當代碼執行到Log.Information("...")時,當(dang)前(qian)線程(cheng)必須等待日志被完全(quan)寫(xie)入目(mu)標(如(ru)(ru)文件、數據庫、網絡(luo)等)后才(cai)能繼續執行。如(ru)(ru)果日志目(mu)標響應慢(如(ru)(ru)網絡(luo)延遲、磁盤(pan)I/O瓶頸),這會直接阻塞(sai)業(ye)務(wu)線程(cheng),導致(zhi)應用性能下(xia)降。
Serilog.Sinks.Async 在日志管道中引入了一個異步代理(Asynchronous Sink)。當調用 Log.Information("...") 時,日志事件(Log Event)會被快速地放入一個內存中的隊列(通常是 BlockingCollection),然后調用線程立即返回,繼續執行后續業務代碼。一個或多個后臺工作線程會從這個隊列中取出日志事件,并在后臺安全地將它們(men)寫入最(zui)終(zhong)的日志接收器(Sinks)。
異步(bu)執(zhi)行(xing)帶來的好(hao)處:
- 提升應用性能與響應速度
減少(shao)主線程阻塞(sai):這是最(zui)直接(jie)的(de)好處(chu)。業務(wu)邏輯不再需要等待緩慢(man)的(de)I/O操作,大大縮短了請(qing)求處(chu)理時間,尤其是在高并發場景下效果顯著(zhu)。
平滑性(xing)能波動:即使日志目標出現(xian)暫時性(xing)延遲,也不(bu)會立(li)刻影(ying)響(xiang)到應用(yong)程序的響(xiang)應時間,因(yin)為(wei)日志事件已經“脫手”進(jin)入隊列。
- 配置簡單,透明集成
使用 Serilog.Sinks.Async 非常簡單。只需在配置日志管道時,將任何現有的 Sink(如 WriteTo.File(...))包裝在WriteTo.Async(...)中即可。例如:
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File("logs/myapp.txt")) // 將文件Sink包裝在Async中
.CreateLogger();
業務(wu)代(dai)碼中(zhong)調用 Log.Information(...) 的方式完(wan)全不變(bian),異步化對業務(wu)層是透(tou)明的。
- 基于隊列的緩沖機制
Serilog.Sinks.Async 內部使用一個線程安全(quan)的隊列來暫存日志事件。
這(zhe)個(ge)隊列充當了生產(chan)者(zhe)(zhe)-消(xiao)費(fei)者(zhe)(zhe)模(mo)型中的緩沖區。應(ying)用程序(xu)線程是“生產(chan)者(zhe)(zhe)”,后(hou)臺線程是“消(xiao)費(fei)者(zhe)(zhe)”。
隊列的(de)大(da)小可以配置(通過 AsyncOptions),允(yun)許用(yong)戶(hu)在內存使用(yong)和日志丟失風險之間進行權衡(heng)。
- 可配置的背壓處理(Backpressure Handling)
采用隊(dui)列(lie)的方式就會有一(yi)個隱患(huan),當日志產生速度(du)(du)遠超(chao)后臺(tai)線程處理速度(du)(du)時,隊(dui)列(lie)會不斷增長,可能導致內(nei)存耗盡(jin)。
然(ran)而,Serilog.Sinks.Async 提供了多(duo)種策(ce)略(lve)來應對這種情況(通過 AsyncOptions 配置(zhi)):
Blocking(默認):當隊列滿時,Log.Information() 調用會阻塞,直到隊列有空間。這保證了日志不丟失,但可能影響性能。
DropWrite:當隊列滿時,新產生的日志事件會被直接丟棄。這保證了應用性能,但犧牲了日志的完整性。
OverflowAction:更高級的選項,允許你定義更復雜的策略,如丟棄舊日志或觸發警報。
- 優雅關閉與日志完整性
當應用程序(xu)關閉時,正確(que)地(di)處理異(yi)步(bu)日志至關重要,以(yi)確(que)保隊列中(zhong)所有待處理的日志都能被寫入。
必須(xu)在程序退出前調用 Log.CloseAndFlush()。它會停(ting)止接收新日志,然后等待后臺線程將隊列中(zhong)剩余(yu)的所有日志事(shi)件(jian)處理完(wan)畢,最后才返(fan)回(hui)。
忽略此步驟是導致日志丟失的最常見原因。
- 與結構化日志的完美結合
Serilog 的核(he)心是結(jie)構化日(ri)志(zhi)(Structured Logging),即日(ri)志(zhi)以帶有屬性的結(jie)構化數據(ju)形式記錄(lu),而(er)非純文本(ben)。
異步記錄與結構(gou)化(hua)日志相(xiang)得(de)益彰。結構(gou)化(hua)日志的序列化(hua)(如(ru)序列化(hua)為JSON)可能消耗CPU,異步化(hua)可以(yi)將這部分開銷也(ye)移出主線程。
總的來說,Serilog 是構建高性能、高響應性 .NET 應用程序日志方案的關鍵組件,尤其適用于Web API、高并發服務等對延遲敏感的場景。但對于日志量不大,對延遲不太敏感的場景,還是更推薦同步的,因為異步無法避免的會占用更多資源。
1.5 核心特點:豐富的配置方式
Serilog 允(yun)許開發者根(gen)據項目類型(xing)、環境需求和團隊習慣,選擇最適(shi)合(he)的方式來設置(zhi)日志管道(Logger Pipeline)。這種靈活性(xing)極大地提(ti)升了(le)可(ke)維護性(xing)和適(shi)應性(xing)。
- 代碼配置(Code-based Configuration)
這是最基礎、最靈活、也(ye)是最推薦的方式,通過(guo) C# 代(dai)碼直接構建 LoggerConfiguration 對象。
特點:
完全控制:提供對日志配置的完全編程控制,可以執行復雜的邏輯(如條件判斷、循環、讀取環境變量等)。
編譯時檢查:得益于強類型,配置錯誤通常能在編譯時被發現。
易于調試:可以在配置代碼中設置斷點,逐步檢查配置過程。
動態(tai)(tai)性:可(ke)以根據運行時(shi)條件(如環境變量、命(ming)令行參數(shu))動態(tai)(tai)調整配置。
核心 API:
LoggerConfiguration():創建配置構建器。
.MinimumLevel.*():設置全局或特定來源的最低日志級別(如 .MinimumLevel.Debug())。
.WriteTo.*():添加日志接收器(Sinks),指定日志輸出目標(如文件、控制臺、數據庫)。
.Filter.*():添加過濾器,決定哪些日志事件應被處理或丟棄。
.Enrich.*():添加“豐富器”(Enrichers),為日志事件自動添加上下文信息(如機器名、進程ID、線程ID、請求ID)。
.Destructure.*():配置對象解構策略,控制復雜對象如何被序列化到日志中。
.CreateLogger():最(zui)終生成(cheng) ILogger 實(shi)例(li)并賦值(zhi)給(gei) Log.Logger。
如下示例詳解:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // 全局設置日志的最低輸出級別為 Debug
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) // Microsoft.* 相關的日志只有 Information 及以上才會輸出
.Enrich.FromLogContext() // 添加 Serilog 的結構化上下文,允許在特定作用域內添加結構化數據(比如用戶ID、請求ID)
.Enrich.WithMachineName() // 自動添加當前機器名(Environment.MachineName)
.Enrich.WithThreadId() // 添加當前線程 ID,便于多線程/異步調試時追蹤日志來源
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") // 將日志寫入控制臺(stdout)示例:[14:23:01 INF] User 'alice' logged in successfully.
.WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day) // 每天生成一個新的日志文件,這需要引用 Serilog.Sinks.File 和 Serilog.Sinks.RollingFile 包
.WriteTo.Async(a => a.Elasticsearch(new ElasticsearchSinkOptions(new Uri("//localhost:9200"))
{ // 使用 .WriteTo.Async() 包裝 Elasticsearch 輸出,使其異步寫入,避免阻塞主線程
AutoRegisterTemplate = true
}))
// 使用 Filter.ByExcluding 排除某些日志事件
// Matching.FromSource("Microsoft.AspNetCore.StaticFiles"):匹配來自 Microsoft.AspNetCore.StaticFiles 命名空間的日志源
.Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.StaticFiles"))
.CreateLogger();
- JSON 配置(JSON Configuration)
Serilog 支持從標準的 .NET 配(pei)置文件(如 appsettings.json)中(zhong)讀(du)取配(pei)置。這(zhe)對于希望將(jiang)配(pei)置與代碼分離的項目(mu)非常有用。
特點:
配置與代碼分離:配置信息獨立于代碼,便于非開發人員(如運維)修改。
環境友好:可以利用 appsettings.Development.json、appsettings.Production.json 等文件實現環境差異化配置。
易于部署:部署時只需替(ti)換(huan)配置文件即(ji)可(ke)調整日志(zhi)行為,無需重新編譯。
如何實現?
首先安裝 Serilog.Settings.Configuration NuGet 包;
在 appsettings.json 中定義 Serilog 配置節點;
在程序啟動時,使用 ReadFrom.Configuration(IConfiguration) 方法讀取。
配置示例:
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ], // 指定使用的Sinks包
"MinimumLevel": "Debug",
"Override": {
"Microsoft": "Information",
"System": "Warning"
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/myapp.txt",
"rollingInterval": "Day"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Destructure": [
{
"Name": "ToMaximumDepth",
"Args": { "maximumDestructuringDepth": 4 }
}
]
}
}
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) // 從 IConfiguration 讀取 Serilog 配置
.CreateLogger();
- App.config / Web.config(XML Configuration)
對于傳統的 .NET Framework 應用程(cheng)序(xu)(非(fei) .NET Core),Serilog 也支持通(tong)過 app.config 或(huo) web.config 文件進行配置(zhi)(zhi)。類似于 JSON 配置(zhi)(zhi),但使用 XML 語法。
配置方法就是,安裝 Serilog.Settings.AppSettings NuGet 包。在 .config 文件的 <appSettings> 節點中添(tian)加(jia)以 serilog: 為前綴的鍵值(zhi)對。
配置示例:
<configuration>
<appSettings>
<add key="serilog:minimum-level" value="Debug" />
<add key="serilog:write-to:Console" />
<add key="serilog:write-to:File.path" value="logs\myapp.txt" />
<add key="serilog:enrich:WithMachineName" />
</appSettings>
</configuration>
Log.Logger = new LoggerConfiguration()
.ReadFrom.AppSettings() // 從AppSettings讀取
.CreateLogger();
- 環境變量配置
Serilog 可以直接從環境變量中讀取配置,這在容器化(Docker, Kubernetes)和云原生環境中非常實用。
特點:
云原生友好:容器和云平臺(如 Azure, AWS)通常通過環境變量傳遞配置。
動態注入:無需修改代碼或配置文件,通過部署腳本或平臺設置即可改變日志行為。
與 appsettings.json 結合:環(huan)境變量可以覆蓋 appsettings.json 中的值(zhi)。
實現方式:
使用 Serilog.Settings.Configuration 包時,IConfiguration 本身支持從環境變量讀取。
環境變量的名稱需(xu)要(yao)遵循特定的格(ge)式來映射(she)到(dao) JSON 結(jie)構(gou)。例如,要(yao)覆(fu)蓋 appsettings.json 中(zhong)的 Serilog:MinimumLevel,可(ke)以設置環境變量 Serilog__MinimumLevel=Warning(雙下(xia)劃線 __ 表示層級)。
- 組合與優先級
Serilog 允許組合多種配置方式,通常遵循一定的優先級。
代碼配置:最高優先級,直接在代碼中設置的值會覆蓋其他來源。
環境變量:通常優先級高于配置文件中的靜態值。
JSON / XML 配置文件:基礎配置來源。
默認值:如果以上都沒有設置,則使用 Serilog 的內置默認值。
推薦配置方式:
// 1. 構建 IConfiguration,從多個來源讀取(包括環境變量)
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) // 作為基礎配置 reloadOnChange:啟用配置熱重載
.AddJsonFile($"appsettings.{environment}.json", optional: true) // optional: true 允許特定環境文件不存在,此時將回退到基礎配置
.AddEnvironmentVariables() // 允許環境變量覆蓋,在 Docker、Kubernetes、Azure App Service 等云平臺中,環境變量是傳遞配置的首選方式
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) // 從配置文件和環境變量加載基礎配置
.MinimumLevel.Override("MyApp.Sensitive", LogEventLevel.Verbose) // 允許在代碼中強制覆蓋某些關鍵部分的日志行為,即“安全護欄”,防止因配置錯誤導致的嚴重后果
.CreateLogger();
關于 .AddEnvironmentVariables() 配置:
云原(yuan)生(sheng)友好(hao):在 Docker、Kubernetes、Azure App Service 等(deng)云平(ping)臺中(zhong),環(huan)境變量是(shi)傳遞配(pei)置(zhi)的首(shou)選方式。它安全(quan)(避免(mian)將敏(min)感(gan)信息寫入代碼或配(pei)置(zhi)文件)、靈活(通過部署腳本或平(ping)臺界面即可修改)且(qie)與(yu)代碼解耦(ou)。
最高優先級覆蓋:在(zai)配置提(ti)供程序的加載順序中,AddEnvironmentVariables() 通(tong)常在(zai)最后(hou),因此它的值(zhi)優先級最高,可以覆蓋 appsettings.json 中的任何設置。
示例場景:
基礎配置 appsettings.json 中設置 Serilog:MinimumLevel=Information。
運維人員在生產環境中發現一個偶發問題,需要臨時開啟 Debug 日志。
無需修改任何代碼或配置文件,只需在服務器或容器中設置環境變量 Serilog__MinimumLevel=Debug。
應用(yong)重(zhong)啟后(或配合熱重(zhong)載),日志(zhi)級別立即(ji)生(sheng)效,問(wen)題排查(cha)完畢后,移除(chu)環境變量(liang)即(ji)可(ke)恢(hui)復原狀。零代碼(ma)變更(geng),快速響應。
1.6 核心特點:日志豐富(Enrichment)
日志(zhi)豐富(fu)是 Serilog 區別于許(xu)多(duo)其(qi)他 .NET 日志(zhi)框架(如內置的(de) ILogger)的(de)一(yi)個關鍵優勢。
它允許在不修改代碼或日志語句的情況下,為日志事件自動添加上下文信息。這(zhe)極大地提升(sheng)了(le)日志的可讀性、可追溯(su)性和診斷能(neng)力。
簡單來說,Enrichment 就是“給日志事件自動添加額外的、有價值的上下文信息”。
Serilog 的 LoggerConfiguration 允許通過 .Enrich.With(...) 方法注冊一個或多個豐富器(Enricher)。
一個豐富器是一個實現了 ILogEventEnricher 接口的對象,它有一個 Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 方法(fa)。Serilog 會在每條日志事件(jian)被處理時調用所有注冊(ce)的豐富(fu)器。
Serilog 內(nei)置的豐(feng)富器(qi)(Built-in Enrichers)如(ru)下:
Enrich.WithProperty()
// 為所有日志事件添加一個靜態的、固定的屬性。
// 場景:添加應用名稱、版本號、環境(Development/Production)等。
// 例如:.Enrich.WithProperty("Application", "MyWebApp") // 每條日志都會自動包含 "Application": "MyWebApp"
Enrich.WithMachineName() (Serilog.Enrichers.Environment)
// 功能:添加運行應用的機器名稱。
// 場景:在多服務器部署中,快速定位日志來源機器。
Enrich.WithEnvironmentUserName() / Enrich.WithEnvironmentName() (Serilog.Enrichers.Environment)
// 功能:添加當前操作系統用戶名和環境變量(如 ASPNETCORE_ENVIRONMENT)。
// 場景:了解運行環境和用戶。
Enrich.WithThreadId() / Enrich.WithThreadName() (Serilog.Enrichers.Thread)
// 功能:添加當前線程 ID 或名稱。
// 場景:在多線程應用中追蹤日志的執行線程。
Enrich.WithProcessId() / Enrich.WithProcessName() (Serilog.Enrichers.Process)
// 功能:添加進程 ID 和進程名稱。
// 場景:區分同一機器上運行的多個實例。
Enrich.WithDemystifiedStackTraces() (Serilog.Exceptions)
// 功能:當記錄異常時,提供更清晰、去除了編譯器生成代碼的堆棧跟蹤(“去神秘化”)。
// 場景:讓異常堆棧更易讀。
Enrich.WithClientIp() / Enrich.WithClientAgent() (Serilog.AspNetCore)
// 功能:在 ASP.NET Core 應用中,自動從 HTTP 上下文中提取客戶端 IP 地址和 User-Agent。
// 場景:Web 應用必備,用于安全審計和用戶行為分析。
Enrich.WithRequestHeader() / Enrich.WithRequestProperty() (Serilog.AspNetCore)
// 功能:從 HTTP 請求頭或屬性中提取特定值作為日志屬性。
// 例如
.Enrich.WithRequestHeader("X-Correlation-ID") // 提取關聯ID
.Enrich.WithRequestProperty("TenantId") // 提取路由或中間件設置的屬性
Enrich.FromLogContext() (極其重要!)
// 功能:這是 Serilog 最強大的豐富器之一。它允許你在代碼的特定作用域內動態地添加上下文信息。
// 機制:使用 LogContext.PushProperty("PropertyName", value)。
// 作用域:通過 using 語句創建一個作用域,該作用域內的所有日志都會自動包含 PushProperty 添加的屬性。
// 例如:
using (LogContext.PushProperty("TransactionId", transactionId))
using (LogContext.PushProperty("UserId", userId))
{
Log.Information("開始處理交易");
// ... 更多操作 ...
Log.Information("交易處理完成");
// 這兩條日志都自動包含 TransactionId 和 UserId
}
// 作用域結束后,這些屬性自動移除
// 場景:處理用戶請求、數據庫事務、消息處理等需要貫穿整個操作流程的上下文。
如何試下自定義豐富器?
實現 ILogEventEnricher 接口。然后在(zai) Enrich 方法中,使用 propertyFactory 創(chuang)建 LogEventProperty,最后并添加到 logEvent.Properties 中。
如下示例:
public class UtcTimestampEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var utcNow = DateTimeOffset.UtcNow;
var property = propertyFactory.CreateProperty("UtcTimestamp", utcNow);
logEvent.AddPropertyIfAbsent(property);
}
}
// 使用
Log.Logger = new LoggerConfiguration()
.Enrich.With<UtcTimestampEnricher>()
.WriteTo.Console()
.CreateLogger();
Enrichment 帶來的(de)優(you)勢(shi)可以概括為:
減少代碼重復:無需在每個日志語句中手動添加通用信息。
提高日志質量:確保關鍵上下文(如 RequestId、UserId)不會被遺漏。
非侵入性:業務代碼更干凈,日志語句更簡潔。
動態上下文:通過 LogContext 支持基于作用域的動態豐富。
易于擴展:可以輕松添加新的豐富器或自定義邏輯。
強大的診斷能力:豐富的上(shang)下文信息使(shi)得在(zai)海量(liang)日(ri)志中追蹤(zong)問題(ti)、分析用(yong)戶行為變得非常(chang)高效。
1.7 核心特點:對象解構(Destructuring)
簡單(dan)來(lai)說,對象解(jie)構是(shi)指 Serilog 在記錄(lu)日志時,能夠深入分析(introspect)你傳遞給日志方法(fa)的復(fu)雜(za)對象(如(ru)自定義類、集(ji)合、匿(ni)名(ming)對象等),并將它們(men)的內部(bu)屬性(xing)和值提取出來(lai),以結(jie)(jie)構化的方式(通常是(shi) JSON 格式)記錄(lu)到日志中,而不(bu)是(shi)僅(jin)僅(jin)記錄(lu)對象的類型名(ming)稱或 ToString() 的結(jie)(jie)果。
幾個主要特點如下:
- 深度屬性提取(Deep Property Extraction)
Serilog 不僅能解(jie)構對(dui)(dui)象(xiang)的直接(jie)屬性,還能遞歸地解(jie)構嵌套(tao)對(dui)(dui)象(xiang)。
例如,如果 User 類中有一個 Address 屬(shu)性(也是一個復雜對象),解構后 Address 的屬(shu)性(如 Street, City)也會被完整記錄。
public class Address { public string Street { get; set; } public string City { get; set; } }
public class User { public int Id { get; set; } public string Name { get; set; } public Address HomeAddress { get; set; } }
var user = new User {
Id = 123,
Name = "Alice",
HomeAddress = new Address { Street = "123 Main St", City = "Wonderland" }
};
logger.LogInformation("User created: {User}", user);
// 解構后的 JSON 片段:
// "User": {
// "Id": 123,
// "Name": "Alice",
// "HomeAddress": {
// "Street": "123 Main St",
// "City": "Wonderland"
// }
// }
- 集合解構(Collection Destructuring)
數組、列表、字典等集合類型也會被解構。
數組和列表會被轉換為 JSON 數組。
字典會被轉換為 JSON 對象,其鍵值對成為對象的屬性。
var tags = new List<string> { "urgent", "bug", "frontend" };
var metadata = new Dictionary<string, object> { { "Priority", 1 }, { "AssignedTo", "Bob" } };
logger.LogInformation("Task updated with tags: {Tags} and metadata: {Metadata}", tags, metadata);
// 解構后的 JSON 片段:
// "Tags": ["urgent", "bug", "frontend"],
// "Metadata": { "Priority": 1, "AssignedTo": "Bob" }
- 匿名對象支持(Anonymous Object Support)
可以直接(jie)傳入匿名對象(xiang),Serilog 會(hui)完(wan)美地將其解構(gou)并作為日志(zhi)的一部分。
logger.LogInformation("Operation completed", new { DurationMs = 45, RecordsProcessed = 100 });
// "DurationMs": 45, "RecordsProcessed": 100
- 性能考量與控制(Performance and Control)
潛在開銷:深(shen)度解構一個(ge)非常龐大(da)或深(shen)層(ceng)嵌套的對象可能(neng)會帶來性能(neng)開銷(CPU、內(nei)存、日志大(da)小)。
解構策略(Destructuring Policies):Serilog 允許(xu)你(ni)通過 Destructure 配置(zhi)來精細化控制解構行(xing)為(wei):
??ByTransforming<T>(...):為特定類型 T 定義自定義的解構邏輯。例如,你可能只想記錄 User 對象的 Id 和 Name,而忽略敏感的 PasswordHash。.Destructure.ByTransforming<User>(user => new { user.Id, user.Name })
??ByIgnoringPropertiesOfType<T>():忽略特定類型的屬性(例如,忽略所有 byte[] 或 Stream 類型的屬性,因為它們不適合日志)。
??ByIgnoringMembers(...):忽略特定成員(屬性或字段)。
??MaximumDepth(int depth):設置解構的最大遞歸深度,防止無限遞歸或過深的結構。
??MaximumStringLength(int length):限制字符串屬性的最大長度,防止超長日志條(tiao)目。
@ 符號(The @ Operator):這是一個非常強大的內聯控制語法。在日志消息的占位符前加上 @,可以強制對該位置的對象進行解構,即使它是一個簡單(dan)的值類型或字符串。
更重要的(de)是,@ 是解構(gou)的(de)“開關”。如(ru)果你(ni)不希望(wang)某個復(fu)雜對象被解構(gou),可以使用 $ 符號(但 Serilog 實際上是用 @ 來 啟用 解構(gou),不加 @ 則可能只(zhi)記錄 ToString())。
在 Serilog 中,想要確保對象被解構,在占位符前使(shi)用 @ 就(jiu)可(ke)以了。
var user = new User { ... };
// 推薦寫法,明確要求解構
logger.LogInformation("User action: {@User}", user);
// 如果不加 @,且 User 類沒有重寫 ToString(),可能只記錄類型名
logger.LogInformation("User action: {User}", user); // 可能不是你想要的!
// 使用 @ 也可以解構集合或值類型(雖然對值類型意義不大)
logger.LogInformation("Scores: {@Scores}", new[] { 95, 87, 92 });
- 與結構化日志的協同(Synergy with Structured Logging)
對象解構(gou)是 Serilog 實現真(zhen)正結構(gou)化日志的核心支柱。
解構后的數據是結構化的 JSON 對象,而(er)不是難以解析的文本。這(zhe)使得日志(zhi)可(ke)以被現(xian)代日志(zhi)聚合和分析工具(ju)(如 Seq, Elasticsearch, Splunk, Datadog)高效地索引、搜索、過(guo)濾(lv)和可(ke)視化。
這樣就可(ke)(ke)以輕(qing)松地根據 User.Name 條(tiao)件,來(lai)查(cha)詢(xun) Alice,其中(zhong) Status 為 Failed 的日(ri)志,這在純(chun)文本日(ri)志中(zhong)是幾乎(hu)不可(ke)(ke)能高效完成的。
Serilog 的(de)對(dui)(dui)象解構(gou)功能將日(ri)志記(ji)(ji)錄從(cong)簡單的(de)文本記(ji)(ji)錄提(ti)(ti)升到了結(jie)構(gou)化數據記(ji)(ji)錄的(de)新高度(du)。它通(tong)過自動、深(shen)度(du)地(di)分(fen)析(xi)和(he)提(ti)(ti)取(qu)復雜對(dui)(dui)象的(de)內部結(jie)構(gou),生(sheng)成富含上下(xia)文的(de) JSON 數據,使得日(ri)志不再(zai)是難(nan)以解讀的(de)“黑盒”,而是可(ke)以被程序高效處理(li)和(he)分(fen)析(xi)的(de)寶貴資(zi)產(chan)。熟練掌(zhang)握 @ 操(cao)作符(fu)和(he) Destructure 配置策略,是發揮 Serilog 強大威(wei)力的(de)關鍵。
1.8 幾個適用場景
- 微服務架構
每個(ge)微服務獨(du)立記(ji)錄(lu)日(ri)志(zhi),通(tong)過集中式日(ri)志(zhi)系統(如 Seq、Elasticsearch)進行(xing)聚合(he),便于跨服務的請求追蹤和(he)問題排查(cha)。
- 云原生應用
與(yu) Kubernetes、Docker 等容器(qi)化(hua)平(ping)臺無縫集成(cheng),通過(guo) WriteTo.Console() 將日志(zhi)輸(shu)出到標準輸(shu)出,被(bei)容器(qi)編排平(ping)臺自動捕獲
- 需要復雜日志分析的系統
需(xu)要(yao)對日(ri)志(zhi)進行(xing)聚合、分(fen)(fen)析和可視化(如使用 Seq 的查詢語(yu)言),適(shi)合需(xu)要(yao)從(cong)日(ri)志(zhi)中提取結構化數據進行(xing)業(ye)務分(fen)(fen)析的場(chang)景。
- 高性能要求的應用
異步(bu)日志記錄(lu)機制(zhi)確保日志不會(hui)成(cheng)為性能瓶頸,特別(bie)適合高并發、高吞吐(tu)量的系統。
- 與現代監控系統集成
與 Seq、Elasticsearch、Splunk、DataDog 等監(jian)控平臺無縫集(ji)成,便于構建完(wan)整的可(ke)觀(guan)測性系統。
二、簡單列一下有哪些 Serilog Sink 類型
文(wen)件系統 Sink、控制臺與調試 Sinks、集中式(shi)日志與分析平臺 Sinks、消息(xi)隊列 Sinks、數據庫(ku) Sinks、其他 Sinks 等(deng)等(deng)。
Serilog的強大之(zhi)處在于其Sink的靈活性(xing)和(he)可組(zu)合(he)性(xing),一個典型的生產環境(jing)配置可能會(hui)結合(he)多種分類的Sink。
后續博主(zhu)將(jiang)繼續以(yi) Sink 的各個類(lei)型(xing)為基礎(chu),進行實(shi)踐并(bing)輸出實(shi)踐記(ji)錄,有興(xing)趣可持(chi)續關注(zhu)。
本文來自博客園,作者:橙子家,歡迎微信掃(sao)碼關注博(bo)主【橙(cheng)子(zi)家czzj】,有任何疑問歡迎溝通,共同成(cheng)長(chang)!
