前言
相較于 log4net,Serilog 則是新項目的首選,現(xiàn)代化、高性能、易用,是 .NET 日志的未來方向。
log4net 雖然是一個廣泛使用的、功能強大的日志記錄庫,而且專為 .NET 平臺設(shè)計,但由于她是較老的日志框架,其生態(tài)在 .NET Core/.NET 5+ 環(huán)境中支持有限,因此還是要慎重考慮。
| 方面 | log4net | Serilog |
|---|
| 日志模型 | 文本日志:日志是純字符串,信息混在一起。 例如:"用戶 alice 從 192.168.1.1 登錄" | 結(jié)構(gòu)化日志:日志是帶屬性的數(shù)據(jù)事件。 例如:"用戶 {UserName} 從 {IP} 登錄",UserName="alice", IP="192.168.1.1" |
| 配置方式 | 主要靠 XML 文件,復(fù)雜且不易讀。 | 支持代碼和 appsettings.json,更現(xiàn)代、簡潔。 |
| API 使用 | 需為每個類創(chuàng)建 logger 實例,較繁瑣。 | 提供靜態(tài) Log 類,一行代碼即可記錄,更簡單。 |
| 生態(tài)系統(tǒng) | 老牌庫,Appenders 豐富但更新慢。 | Sinks 生態(tài)活躍,原生支持 Elasticsearch、Seq、云平臺等。 |
| .NET 集成 | 在 ASP.NET Core 中集成較麻煩。 | 與 ASP.NET Core 深度集成,自動記錄請求上下文。 |
| 維護狀態(tài) | 活躍維護,持續(xù)更新,最新版 3.2.0(20250823)。 | 活躍維護,持續(xù)更新,支持最新 .NET 版本。 |
經(jīng)過對比,當(dāng)然還是 Serilog 更有優(yōu)勢,那么本文將就概念方面來詳細(xì)介紹下,后續(xù)會繼續(xù)更新相關(guān)用法實踐,有興趣可持續(xù)關(guān)注。
一、Serilog 簡介
Serilog 是一個為 .NET 應(yīng)用程序設(shè)計的強大的診斷日志庫,以易于設(shè)置、簡潔的 API 和跨所有最新 .NET 平臺的兼容性而著稱。
它是 .NET 平臺中非常流行且強大的結(jié)構(gòu)化日志庫,其最大特點是結(jié)構(gòu)化日志記錄(Structured Logging)。
1.1 核心特點:結(jié)構(gòu)化日志記錄(Structured Logging)
日志不是簡單的字符串,而是包含命名屬性的結(jié)構(gòu)化事件。
// 例如記錄用戶登錄事件
string username = "bob";
string clientIp = "10.0.0.5";
int userId = 789;
Log.Information("用戶 {UserName} (ID: {UserId}) 從 {ClientIP} 登錄",
username, userId, clientIp);
// 生成的結(jié)構(gòu)化數(shù)據(jù)(以 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'
這樣便于和另外的日志接收系統(tǒng)對接,針對日志量較大的場景比較友好。
1.2 核心特點:強大的 Sink 生態(tài)系統(tǒng)
Serilog 通過 Sinks(接收器)將日志事件發(fā)送到各種目的地。這種“即插即用”的架構(gòu)是 Serilog 強大和流行的關(guān)鍵。
官方 Sinks:Serilog.Sinks.Console(控制臺)、Serilog.Sinks.File(文件)、Serilog.Sinks.Seq(Seq 服務(wù)器)、Serilog.Sinks.Elasticsearch(Elasticsearch)等。
第三方 Sinks:支持 Splunk、DataDog、Graylog、Kafka、RabbitMQ、AWS CloudWatch 等
優(yōu)勢:可以同時配置多個 Sinks,將同一日志事件發(fā)送到不同地方。
工作原理:
日志事件生成:業(yè)務(wù)端代碼調(diào)用 Log.Information("..."),Serilog 創(chuàng)建一個 LogEvent 對象。
事件處理:該事件可能經(jīng)過過濾、豐富(Enrichment)等處理。
分發(fā)到 Sinks:Serilog 核心引擎將處理后的 LogEvent 分發(fā)給所有配置的 Sinks。
Sink 執(zhí)行寫入:每個 Sink 根據(jù)自己的邏輯,將 LogEvent 轉(zhuǎn)換為適合目標(biāo)系統(tǒng)的格式(如 JSON、文本行),并通過相應(yīng)協(xié)議(如 HTTP、文件 I/O)發(fā)送出去。
1.3 核心特點:簡潔易用的 API
使用靜態(tài) Log 類,無需依賴注入即可在任何地方記錄日志。
它極大地簡化了日志記錄的開發(fā)體驗,讓開發(fā)者能夠以最少的代碼、最直觀的方式完成強大的日志功能。
// Serilog:全局靜態(tài) Log 類,任何地方直接調(diào)用
Log.[Level](string messageTemplate, params object[] propertyValues); // 格式
Log.Verbose("調(diào)試信息: {Detail}", detail); // 示例
Log.Debug("進入方法: {MethodName}", "ProcessOrder");
Log.Information("訂單 {OrderId} 已創(chuàng)建", orderId);
Log.Warning("庫存不足,商品 {ProductId}", productId);
Log.Error(exception, "處理支付失敗,用戶 {UserId}", userId);
Log.Fatal("應(yīng)用程序即將退出");
// 對比傳統(tǒng)日志庫 log4net 的方式:
// 每個類都需要聲明一個 logger 實例,代碼重復(fù),不夠優(yōu)雅
private static readonly ILog log = LogManager.GetLogger(typeof(MyClass));
public void DoSomething()
{
log.Info("用戶登錄");
}
支持所有標(biāo)準(zhǔn)日志級別:Verbose,Debug,Information,Warning,Error,F(xiàn)atal。
使用 {PropertyName} 占位符,清晰表達意圖。
自動提取屬性值,生成結(jié)構(gòu)化數(shù)據(jù)。
支持格式化說明符,如 {LoginTime:HH:mm}。
即使沒有提供參數(shù),也不會拋出異常(會原樣輸出占位符)。
// 異常處理的優(yōu)雅支持
// 記錄異常是常見需求,Serilog 提供了專門的重載,讓異常日志既簡潔又完整
try
{
// ...
}
catch (Exception ex)
{
Log.Error(ex, "處理訂單 {OrderId} 失敗", orderId);
}
// 第一個參數(shù)是 Exception 對象,Serilog 會自動捕獲堆棧跟蹤。
// 后續(xù)參數(shù)用于填充消息模板中的屬性。
// 生成的日志事件同時包含異常詳情和業(yè)務(wù)上下文。
| 特性 | 如何體現(xiàn)“簡潔易用” |
|---|
| 靜態(tài) Log 類 | 全局可用,無需實例化,減少樣板代碼 |
| 統(tǒng)一方法簽名 | 所有日志級別使用相同模式,降低記憶負(fù)擔(dān) |
| 消息模板 | 結(jié)構(gòu)化日志 + 直觀語法,代碼可讀性強 |
| 異常支持 | 一行代碼記錄異常和上下文 |
| 異步/批量 | 通過 .Async() 包裝器輕松實現(xiàn),無需手動線程管理 |
| 豐富 Sink | 配置簡單,更換輸出目標(biāo)只需修改幾行代碼 |
1.4 核心特點:異步日志記錄
Serilog 通過將日志寫入操作從應(yīng)用程序的主要執(zhí)行線程中分離出來,顯著提升了應(yīng)用程序的性能和響應(yīng)能力。
異步非由 Serilog 核心庫直接實現(xiàn),而是通過 Serilog.Sinks.Async 這個專門的包來完成。特別適合高并發(fā)、高吞吐量的場景。
在傳統(tǒng)的同步日志中,當(dāng)代碼執(zhí)行到Log.Information("...")時,當(dāng)前線程必須等待日志被完全寫入目標(biāo)(如文件、數(shù)據(jù)庫、網(wǎng)絡(luò)等)后才能繼續(xù)執(zhí)行。如果日志目標(biāo)響應(yīng)慢(如網(wǎng)絡(luò)延遲、磁盤I/O瓶頸),這會直接阻塞業(yè)務(wù)線程,導(dǎo)致應(yīng)用性能下降。
Serilog.Sinks.Async 在日志管道中引入了一個異步代理(Asynchronous Sink)。當(dāng)調(diào)用 Log.Information("...") 時,日志事件(Log Event)會被快速地放入一個內(nèi)存中的隊列(通常是 BlockingCollection),然后調(diào)用線程立即返回,繼續(xù)執(zhí)行后續(xù)業(yè)務(wù)代碼。一個或多個后臺工作線程會從這個隊列中取出日志事件,并在后臺安全地將它們寫入最終的日志接收器(Sinks)。
異步執(zhí)行帶來的好處:
減少主線程阻塞:這是最直接的好處。業(yè)務(wù)邏輯不再需要等待緩慢的I/O操作,大大縮短了請求處理時間,尤其是在高并發(fā)場景下效果顯著。
平滑性能波動:即使日志目標(biāo)出現(xiàn)暫時性延遲,也不會立刻影響到應(yīng)用程序的響應(yīng)時間,因為日志事件已經(jīng)“脫手”進入隊列。
使用 Serilog.Sinks.Async 非常簡單。只需在配置日志管道時,將任何現(xiàn)有的 Sink(如 WriteTo.File(...))包裝在WriteTo.Async(...)中即可。例如:
Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File("logs/myapp.txt")) // 將文件Sink包裝在Async中
.CreateLogger();
業(yè)務(wù)代碼中調(diào)用 Log.Information(...) 的方式完全不變,異步化對業(yè)務(wù)層是透明的。
Serilog.Sinks.Async 內(nèi)部使用一個線程安全的隊列來暫存日志事件。
這個隊列充當(dāng)了生產(chǎn)者-消費者模型中的緩沖區(qū)。應(yīng)用程序線程是“生產(chǎn)者”,后臺線程是“消費者”。
隊列的大小可以配置(通過 AsyncOptions),允許用戶在內(nèi)存使用和日志丟失風(fēng)險之間進行權(quán)衡。
采用隊列的方式就會有一個隱患,當(dāng)日志產(chǎn)生速度遠(yuǎn)超后臺線程處理速度時,隊列會不斷增長,可能導(dǎo)致內(nèi)存耗盡。
然而,Serilog.Sinks.Async 提供了多種策略來應(yīng)對這種情況(通過 AsyncOptions 配置):
Blocking(默認(rèn)):當(dāng)隊列滿時,Log.Information() 調(diào)用會阻塞,直到隊列有空間。這保證了日志不丟失,但可能影響性能。
DropWrite:當(dāng)隊列滿時,新產(chǎn)生的日志事件會被直接丟棄。這保證了應(yīng)用性能,但犧牲了日志的完整性。
OverflowAction:更高級的選項,允許你定義更復(fù)雜的策略,如丟棄舊日志或觸發(fā)警報。
當(dāng)應(yīng)用程序關(guān)閉時,正確地處理異步日志至關(guān)重要,以確保隊列中所有待處理的日志都能被寫入。
必須在程序退出前調(diào)用 Log.CloseAndFlush()。它會停止接收新日志,然后等待后臺線程將隊列中剩余的所有日志事件處理完畢,最后才返回。
忽略此步驟是導(dǎo)致日志丟失的最常見原因。
Serilog 的核心是結(jié)構(gòu)化日志(Structured Logging),即日志以帶有屬性的結(jié)構(gòu)化數(shù)據(jù)形式記錄,而非純文本。
異步記錄與結(jié)構(gòu)化日志相得益彰。結(jié)構(gòu)化日志的序列化(如序列化為JSON)可能消耗CPU,異步化可以將這部分開銷也移出主線程。
總的來說,Serilog 是構(gòu)建高性能、高響應(yīng)性 .NET 應(yīng)用程序日志方案的關(guān)鍵組件,尤其適用于Web API、高并發(fā)服務(wù)等對延遲敏感的場景。但對于日志量不大,對延遲不太敏感的場景,還是更推薦同步的,因為異步無法避免的會占用更多資源。
1.5 核心特點:豐富的配置方式
Serilog 允許開發(fā)者根據(jù)項目類型、環(huán)境需求和團隊習(xí)慣,選擇最適合的方式來設(shè)置日志管道(Logger Pipeline)。這種靈活性極大地提升了可維護性和適應(yīng)性。
這是最基礎(chǔ)、最靈活、也是最推薦的方式,通過 C# 代碼直接構(gòu)建 LoggerConfiguration 對象。
特點:
完全控制:提供對日志配置的完全編程控制,可以執(zhí)行復(fù)雜的邏輯(如條件判斷、循環(huán)、讀取環(huán)境變量等)。
編譯時檢查:得益于強類型,配置錯誤通常能在編譯時被發(fā)現(xiàn)。
易于調(diào)試:可以在配置代碼中設(shè)置斷點,逐步檢查配置過程。
動態(tài)性:可以根據(jù)運行時條件(如環(huán)境變量、命令行參數(shù))動態(tài)調(diào)整配置。
核心 API:
LoggerConfiguration():創(chuàng)建配置構(gòu)建器。
.MinimumLevel.*():設(shè)置全局或特定來源的最低日志級別(如 .MinimumLevel.Debug())。
.WriteTo.*():添加日志接收器(Sinks),指定日志輸出目標(biāo)(如文件、控制臺、數(shù)據(jù)庫)。
.Filter.*():添加過濾器,決定哪些日志事件應(yīng)被處理或丟棄。
.Enrich.*():添加“豐富器”(Enrichers),為日志事件自動添加上下文信息(如機器名、進程ID、線程ID、請求ID)。
.Destructure.*():配置對象解構(gòu)策略,控制復(fù)雜對象如何被序列化到日志中。
.CreateLogger():最終生成 ILogger 實例并賦值給 Log.Logger。
如下示例詳解:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // 全局設(shè)置日志的最低輸出級別為 Debug
.MinimumLevel.Override("Microsoft", LogEventLevel.Information) // Microsoft.* 相關(guān)的日志只有 Information 及以上才會輸出
.Enrich.FromLogContext() // 添加 Serilog 的結(jié)構(gòu)化上下文,允許在特定作用域內(nèi)添加結(jié)構(gòu)化數(shù)據(jù)(比如用戶ID、請求ID)
.Enrich.WithMachineName() // 自動添加當(dāng)前機器名(Environment.MachineName)
.Enrich.WithThreadId() // 添加當(dāng)前線程 ID,便于多線程/異步調(diào)試時追蹤日志來源
.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("http://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();
Serilog 支持從標(biāo)準(zhǔn)的 .NET 配置文件(如 appsettings.json)中讀取配置。這對于希望將配置與代碼分離的項目非常有用。
特點:
配置與代碼分離:配置信息獨立于代碼,便于非開發(fā)人員(如運維)修改。
環(huán)境友好:可以利用 appsettings.Development.json、appsettings.Production.json 等文件實現(xiàn)環(huán)境差異化配置。
易于部署:部署時只需替換配置文件即可調(diào)整日志行為,無需重新編譯。
如何實現(xiàn)?
首先安裝 Serilog.Settings.Configuration NuGet 包;
在 appsettings.json 中定義 Serilog 配置節(jié)點;
在程序啟動時,使用 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();
對于傳統(tǒng)的 .NET Framework 應(yīng)用程序(非 .NET Core),Serilog 也支持通過 app.config 或 web.config 文件進行配置。類似于 JSON 配置,但使用 XML 語法。
配置方法就是,安裝 Serilog.Settings.AppSettings NuGet 包。在 .config 文件的 <appSettings> 節(jié)點中添加以 serilog: 為前綴的鍵值對。
配置示例:
<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 可以直接從環(huán)境變量中讀取配置,這在容器化(Docker, Kubernetes)和云原生環(huán)境中非常實用。
特點:
云原生友好:容器和云平臺(如 Azure, AWS)通常通過環(huán)境變量傳遞配置。
動態(tài)注入:無需修改代碼或配置文件,通過部署腳本或平臺設(shè)置即可改變?nèi)罩拘袨椤?br>與 appsettings.json 結(jié)合:環(huán)境變量可以覆蓋 appsettings.json 中的值。
實現(xiàn)方式:
使用 Serilog.Settings.Configuration 包時,IConfiguration 本身支持從環(huán)境變量讀取。
環(huán)境變量的名稱需要遵循特定的格式來映射到 JSON 結(jié)構(gòu)。例如,要覆蓋 appsettings.json 中的 Serilog:MinimumLevel,可以設(shè)置環(huán)境變量 Serilog__MinimumLevel=Warning(雙下劃線 __ 表示層級)。
Serilog 允許組合多種配置方式,通常遵循一定的優(yōu)先級。
代碼配置:最高優(yōu)先級,直接在代碼中設(shè)置的值會覆蓋其他來源。
環(huán)境變量:通常優(yōu)先級高于配置文件中的靜態(tài)值。
JSON / XML 配置文件:基礎(chǔ)配置來源。
默認(rèn)值:如果以上都沒有設(shè)置,則使用 Serilog 的內(nèi)置默認(rèn)值。
推薦配置方式:
// 1. 構(gòu)建 IConfiguration,從多個來源讀?。òōh(huán)境變量)
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) // 作為基礎(chǔ)配置 reloadOnChange:啟用配置熱重載
.AddJsonFile($"appsettings.{environment}.json", optional: true) // optional: true 允許特定環(huán)境文件不存在,此時將回退到基礎(chǔ)配置
.AddEnvironmentVariables() // 允許環(huán)境變量覆蓋,在 Docker、Kubernetes、Azure App Service 等云平臺中,環(huán)境變量是傳遞配置的首選方式
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) // 從配置文件和環(huán)境變量加載基礎(chǔ)配置
.MinimumLevel.Override("MyApp.Sensitive", LogEventLevel.Verbose) // 允許在代碼中強制覆蓋某些關(guān)鍵部分的日志行為,即“安全護欄”,防止因配置錯誤導(dǎo)致的嚴(yán)重后果
.CreateLogger();
關(guān)于 .AddEnvironmentVariables() 配置:
云原生友好:在 Docker、Kubernetes、Azure App Service 等云平臺中,環(huán)境變量是傳遞配置的首選方式。它安全(避免將敏感信息寫入代碼或配置文件)、靈活(通過部署腳本或平臺界面即可修改)且與代碼解耦。
最高優(yōu)先級覆蓋:在配置提供程序的加載順序中,AddEnvironmentVariables() 通常在最后,因此它的值優(yōu)先級最高,可以覆蓋 appsettings.json 中的任何設(shè)置。
示例場景:
基礎(chǔ)配置 appsettings.json 中設(shè)置 Serilog:MinimumLevel=Information。
運維人員在生產(chǎn)環(huán)境中發(fā)現(xiàn)一個偶發(fā)問題,需要臨時開啟 Debug 日志。
無需修改任何代碼或配置文件,只需在服務(wù)器或容器中設(shè)置環(huán)境變量 Serilog__MinimumLevel=Debug。
應(yīng)用重啟后(或配合熱重載),日志級別立即生效,問題排查完畢后,移除環(huán)境變量即可恢復(fù)原狀。零代碼變更,快速響應(yīng)。
1.6 核心特點:日志豐富(Enrichment)
日志豐富是 Serilog 區(qū)別于許多其他 .NET 日志框架(如內(nèi)置的 ILogger)的一個關(guān)鍵優(yōu)勢。
它允許在不修改代碼或日志語句的情況下,為日志事件自動添加上下文信息。這極大地提升了日志的可讀性、可追溯性和診斷能力。
簡單來說,Enrichment 就是“給日志事件自動添加額外的、有價值的上下文信息”。
Serilog 的 LoggerConfiguration 允許通過 .Enrich.With(...) 方法注冊一個或多個豐富器(Enricher)。
一個豐富器是一個實現(xiàn)了 ILogEventEnricher 接口的對象,它有一個 Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) 方法。Serilog 會在每條日志事件被處理時調(diào)用所有注冊的豐富器。
Serilog 內(nèi)置的豐富器(Built-in Enrichers)如下:
Enrich.WithProperty()
// 為所有日志事件添加一個靜態(tài)的、固定的屬性。
// 場景:添加應(yīng)用名稱、版本號、環(huán)境(Development/Production)等。
// 例如:.Enrich.WithProperty("Application", "MyWebApp") // 每條日志都會自動包含 "Application": "MyWebApp"
Enrich.WithMachineName() (Serilog.Enrichers.Environment)
// 功能:添加運行應(yīng)用的機器名稱。
// 場景:在多服務(wù)器部署中,快速定位日志來源機器。
Enrich.WithEnvironmentUserName() / Enrich.WithEnvironmentName() (Serilog.Enrichers.Environment)
// 功能:添加當(dāng)前操作系統(tǒng)用戶名和環(huán)境變量(如 ASPNETCORE_ENVIRONMENT)。
// 場景:了解運行環(huán)境和用戶。
Enrich.WithThreadId() / Enrich.WithThreadName() (Serilog.Enrichers.Thread)
// 功能:添加當(dāng)前線程 ID 或名稱。
// 場景:在多線程應(yīng)用中追蹤日志的執(zhí)行線程。
Enrich.WithProcessId() / Enrich.WithProcessName() (Serilog.Enrichers.Process)
// 功能:添加進程 ID 和進程名稱。
// 場景:區(qū)分同一機器上運行的多個實例。
Enrich.WithDemystifiedStackTraces() (Serilog.Exceptions)
// 功能:當(dāng)記錄異常時,提供更清晰、去除了編譯器生成代碼的堆棧跟蹤(“去神秘化”)。
// 場景:讓異常堆棧更易讀。
Enrich.WithClientIp() / Enrich.WithClientAgent() (Serilog.AspNetCore)
// 功能:在 ASP.NET Core 應(yīng)用中,自動從 HTTP 上下文中提取客戶端 IP 地址和 User-Agent。
// 場景:Web 應(yīng)用必備,用于安全審計和用戶行為分析。
Enrich.WithRequestHeader() / Enrich.WithRequestProperty() (Serilog.AspNetCore)
// 功能:從 HTTP 請求頭或?qū)傩灾刑崛√囟ㄖ底鳛槿罩緦傩浴?
// 例如
.Enrich.WithRequestHeader("X-Correlation-ID") // 提取關(guān)聯(lián)ID
.Enrich.WithRequestProperty("TenantId") // 提取路由或中間件設(shè)置的屬性
Enrich.FromLogContext() (極其重要!)
// 功能:這是 Serilog 最強大的豐富器之一。它允許你在代碼的特定作用域內(nèi)動態(tài)地添加上下文信息。
// 機制:使用 LogContext.PushProperty("PropertyName", value)。
// 作用域:通過 using 語句創(chuàng)建一個作用域,該作用域內(nèi)的所有日志都會自動包含 PushProperty 添加的屬性。
// 例如:
using (LogContext.PushProperty("TransactionId", transactionId))
using (LogContext.PushProperty("UserId", userId))
{
Log.Information("開始處理交易");
// ... 更多操作 ...
Log.Information("交易處理完成");
// 這兩條日志都自動包含 TransactionId 和 UserId
}
// 作用域結(jié)束后,這些屬性自動移除
// 場景:處理用戶請求、數(shù)據(jù)庫事務(wù)、消息處理等需要貫穿整個操作流程的上下文。
如何試下自定義豐富器?
實現(xiàn) ILogEventEnricher 接口。然后在 Enrich 方法中,使用 propertyFactory 創(chuàng)建 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 帶來的優(yōu)勢可以概括為:
減少代碼重復(fù):無需在每個日志語句中手動添加通用信息。
提高日志質(zhì)量:確保關(guān)鍵上下文(如 RequestId、UserId)不會被遺漏。
非侵入性:業(yè)務(wù)代碼更干凈,日志語句更簡潔。
動態(tài)上下文:通過 LogContext 支持基于作用域的動態(tài)豐富。
易于擴展:可以輕松添加新的豐富器或自定義邏輯。
強大的診斷能力:豐富的上下文信息使得在海量日志中追蹤問題、分析用戶行為變得非常高效。
1.7 核心特點:對象解構(gòu)(Destructuring)
簡單來說,對象解構(gòu)是指 Serilog 在記錄日志時,能夠深入分析(introspect)你傳遞給日志方法的復(fù)雜對象(如自定義類、集合、匿名對象等),并將它們的內(nèi)部屬性和值提取出來,以結(jié)構(gòu)化的方式(通常是 JSON 格式)記錄到日志中,而不是僅僅記錄對象的類型名稱或 ToString() 的結(jié)果。
幾個主要特點如下:
Serilog 不僅能解構(gòu)對象的直接屬性,還能遞歸地解構(gòu)嵌套對象。
例如,如果 User 類中有一個 Address 屬性(也是一個復(fù)雜對象),解構(gòu)后 Address 的屬性(如 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);
// 解構(gòu)后的 JSON 片段:
// "User": {
// "Id": 123,
// "Name": "Alice",
// "HomeAddress": {
// "Street": "123 Main St",
// "City": "Wonderland"
// }
// }
數(shù)組、列表、字典等集合類型也會被解構(gòu)。
數(shù)組和列表會被轉(zhuǎn)換為 JSON 數(shù)組。
字典會被轉(zhuǎn)換為 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);
// 解構(gòu)后的 JSON 片段:
// "Tags": ["urgent", "bug", "frontend"],
// "Metadata": { "Priority": 1, "AssignedTo": "Bob" }
可以直接傳入匿名對象,Serilog 會完美地將其解構(gòu)并作為日志的一部分。
logger.LogInformation("Operation completed", new { DurationMs = 45, RecordsProcessed = 100 });
// "DurationMs": 45, "RecordsProcessed": 100
潛在開銷:深度解構(gòu)一個非常龐大或深層嵌套的對象可能會帶來性能開銷(CPU、內(nèi)存、日志大小)。
解構(gòu)策略(Destructuring Policies):Serilog 允許你通過 Destructure 配置來精細(xì)化控制解構(gòu)行為:
??ByTransforming<T>(...):為特定類型 T 定義自定義的解構(gòu)邏輯。例如,你可能只想記錄 User 對象的 Id 和 Name,而忽略敏感的 PasswordHash。.Destructure.ByTransforming<User>(user => new { user.Id, user.Name })
??ByIgnoringPropertiesOfType<T>():忽略特定類型的屬性(例如,忽略所有 byte[] 或 Stream 類型的屬性,因為它們不適合日志)。
??ByIgnoringMembers(...):忽略特定成員(屬性或字段)。
??MaximumDepth(int depth):設(shè)置解構(gòu)的最大遞歸深度,防止無限遞歸或過深的結(jié)構(gòu)。
??MaximumStringLength(int length):限制字符串屬性的最大長度,防止超長日志條目。
@ 符號(The @ Operator):這是一個非常強大的內(nèi)聯(lián)控制語法。在日志消息的占位符前加上 @,可以強制對該位置的對象進行解構(gòu),即使它是一個簡單的值類型或字符串。
更重要的是,@ 是解構(gòu)的“開關(guān)”。如果你不希望某個復(fù)雜對象被解構(gòu),可以使用 $ 符號(但 Serilog 實際上是用 @ 來 啟用 解構(gòu),不加 @ 則可能只記錄 ToString())。
在 Serilog 中,想要確保對象被解構(gòu),在占位符前使用 @ 就可以了。
var user = new User { ... };
// 推薦寫法,明確要求解構(gòu)
logger.LogInformation("User action: {@User}", user);
// 如果不加 @,且 User 類沒有重寫 ToString(),可能只記錄類型名
logger.LogInformation("User action: {User}", user); // 可能不是你想要的!
// 使用 @ 也可以解構(gòu)集合或值類型(雖然對值類型意義不大)
logger.LogInformation("Scores: {@Scores}", new[] { 95, 87, 92 });
對象解構(gòu)是 Serilog 實現(xiàn)真正結(jié)構(gòu)化日志的核心支柱。
解構(gòu)后的數(shù)據(jù)是結(jié)構(gòu)化的 JSON 對象,而不是難以解析的文本。這使得日志可以被現(xiàn)代日志聚合和分析工具(如 Seq, Elasticsearch, Splunk, Datadog)高效地索引、搜索、過濾和可視化。
這樣就可以輕松地根據(jù) User.Name 條件,來查詢 Alice,其中 Status 為 Failed 的日志,這在純文本日志中是幾乎不可能高效完成的。
Serilog 的對象解構(gòu)功能將日志記錄從簡單的文本記錄提升到了結(jié)構(gòu)化數(shù)據(jù)記錄的新高度。它通過自動、深度地分析和提取復(fù)雜對象的內(nèi)部結(jié)構(gòu),生成富含上下文的 JSON 數(shù)據(jù),使得日志不再是難以解讀的“黑盒”,而是可以被程序高效處理和分析的寶貴資產(chǎn)。熟練掌握 @ 操作符和 Destructure 配置策略,是發(fā)揮 Serilog 強大威力的關(guān)鍵。
1.8 幾個適用場景
每個微服務(wù)獨立記錄日志,通過集中式日志系統(tǒng)(如 Seq、Elasticsearch)進行聚合,便于跨服務(wù)的請求追蹤和問題排查。
與 Kubernetes、Docker 等容器化平臺無縫集成,通過 WriteTo.Console() 將日志輸出到標(biāo)準(zhǔn)輸出,被容器編排平臺自動捕獲
需要對日志進行聚合、分析和可視化(如使用 Seq 的查詢語言),適合需要從日志中提取結(jié)構(gòu)化數(shù)據(jù)進行業(yè)務(wù)分析的場景。
異步日志記錄機制確保日志不會成為性能瓶頸,特別適合高并發(fā)、高吞吐量的系統(tǒng)。
與 Seq、Elasticsearch、Splunk、DataDog 等監(jiān)控平臺無縫集成,便于構(gòu)建完整的可觀測性系統(tǒng)。
二、簡單列一下有哪些 Serilog Sink 類型
文件系統(tǒng) Sink、控制臺與調(diào)試 Sinks、集中式日志與分析平臺 Sinks、消息隊列 Sinks、數(shù)據(jù)庫 Sinks、其他 Sinks 等等。
Serilog的強大之處在于其Sink的靈活性和可組合性,一個典型的生產(chǎn)環(huán)境配置可能會結(jié)合多種分類的Sink。
轉(zhuǎn)自https://www.cnblogs.com/hnzhengfy/p/19167414