在過去的幾年中,結構化日誌已經大受歡迎。而Serilog是 .NET 中最著名的結構化日誌類庫 ,咱們提供了這份的精簡指南來幫助你快速瞭解並運用它。css
0. 內容
- 設定目標
- 認識Serilog
- 事件和級別
- 觸發和收集結構化數據
- 爲過濾和關聯添加事件標記
- 大海撈針 [Finding needles in the haystack]
- 下一步是什麼?
- 得到幫助
1. 設定目標
你可能以前已經在項目中使用了Serilog,或者你有一個新的項目但願使用Serilog,又或者你只是對結構化日誌記錄感興趣: 那就很是好! 你來對地方了。html
然而,更進一步來講,你的目標多是:git
- 但願在用戶以前發現代碼中的BUG和錯誤
- 爲了更快的找到生產環境中的問題
- 深刻的瞭解系統運行表現
Serilog使用json格式來記錄應用程序中的事件,方便咱們能夠快速的查詢日誌,關鍵是能夠方便地過濾和查詢日誌,而不用編寫正則表達式。github
在本教程中,咱們將介紹最關鍵的幾個部分,幫助咱們能夠在生成環境中提供使人驚歎的診斷能力。[注:原文這句挺拗口]web
2. 認識 Serilog
那就讓咱們開始吧!爲了更好的理解,你能夠先建立一個新的.Net console 項目,能夠是netcore或者傳統的NETFramework版本。正則表達式
Serilog 經過NuGet分發,項目包括一個Seirlog核心項目Seirlog和不少接收器sinks(超過100個),這些接收是經過插件的方式來實現將日誌寫入到各類終端,文件,郵件,數據庫或日誌服務器shell
咱們將經過使用Serilog和Serilog.Sinks.Console這兩個組件開始,在稍後討論其餘選項:數據庫
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
這是世界上最簡單的Serilog配置:express
using Serilog;
class Program
{
public static void Main(string[] args) {
using (var log = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger())
{
log.Information("Hello, Serilog!");
log.Warning("Goodbye, Serilog.");
}
}
}
讓咱們稍微分解一下:json
LoggerConfiguration
類提供一個流式接口用於構建一個日誌記錄管道WriteTo.Console()
將控制檯接收器添加到上述管道中CreateLogger()
組裝並返回一個實現ILogger
接口的Logger
對象- 上述Logger對象一樣實現了IDisposable,因此咱們能夠在
using
中調用它 - 最後
log.Information()
和log.Warning()
觸發記錄日誌
這個日誌記錄管道是一個可釋放的[disposable],這可能會讓你有點意外,可是請記住,記錄器一般是要寫入文件,數據庫等等: 不少sinks 必須被徹底地關閉掉。儘管這樣,也僅僅在應用程序退出前,根logger才須要被釋放。而在應用程序中使用logger是不須要關心這些細節的。
你運行了這個程序嘛?這是你看到的效果吧?
Apart from just passing it around everywhere, there are two possibilities. [ 除了在各地傳遞外,還有兩種可能性。] If you're using an IoC container, you might have components receive an ILogger through dependency injection. [ 若是您使用的是IoC容器,則可能會讓組件經過依賴注入來接收ILogger。] Packages like AutofacSerilogIntegration can help with that. [ 像AutofacSerilogIntegration這樣的軟件包能夠提供幫助。]
如今最直接的問題是:咱們在應用程序的其餘類裏面如何得到這個log
對象,除了處處傳遞以外,還有兩個辦法。
- 若是你使用IoC容器,你能夠組件注入一個ILogger對象來接收,像
AutofacSerilogIntegration
的包括幫助你實現這種方式。 - 或者,您能夠將Logger對象存儲在衆所周知的位置; Serilog 已經內容內置了一個靜態的Log對象,就像這樣:
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
Log.Information("Hello again, Serilog!");
Log.CloseAndFlush();
Log
類提供全部與ILogger
接口相同的方法,這裏咱們顯示調用Log.CloseAndFlush()
來關閉它,而不是使用using
代碼塊
你可使用依賴注入的方式,也能夠是靜態屬性的方式 - 這取決你的我的喜愛問題。爲了簡單起見,咱們在本教程中使用了靜態Log的方式。
也許,你不是在編寫一個控制檯應用程序。咱們將使用Console應用做爲廣爲人知的示例,可是你一旦完成了本教程,您應該查看目標平臺的文檔(例如,ASP.NET Core)。
3. Event and Level [事件和級別]
和一些老的日誌類庫相比(如log4net),在使用Serilog時,你須要作的就是最大改變就是思考日誌事件[log events],而不是日誌消息[log message],一條事件[event]由如下幾個內容組成:
- 事件發生時的時間戳[timestamp]
- 描述什麼時候應該捕獲事件的級別[level]
- 記錄事件的消息[message]內容]
- 描述事件的命名屬性[properties]
- 還可能有一個Exception對象
您能夠將日誌事件格式化爲控制檯的可讀文本,正如咱們在第一個示例中看到的那樣:
11:33:01 [INF] Hello, Serilog!
或者,您能夠將相同的事件格式化爲JSON並將其發送到遠程日誌服務器:
json {"@t":"2017-11-20T11:33:01.22138","@m":"Hello, Serilog!"}
在背後,應用程序中的日誌語句會建立LogEvent
對象,而鏈接到管道的接收器[sinks]會知道如何記錄它們。
### Logging levels
Serilog速度很快,但始終構建和記錄詳細的日誌事件會浪費CPU,磁盤和網絡資源。爲了管理這個,Serilog事件被賦予了多種級別:Debug
, Information
, Warning
和 Error
等。對應的有一個Log.*()
方法來對應各個級別。
在開發過程當中,可能會打開調試級別的事件:
```csharp
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() // <- Set the minimum level
.WriteTo.Console()
.CreateLogger();
// In a tight loop...
Log.Debug("Processing item {ItemNumber} of {ItemCount}", itemNumber, itemCount);
```
在生成環境中,一般關閉調試的日誌,並將最小的日誌級別設置成Information
,以便只記錄重要的事件,在這篇文檔中能夠得到有關Serilog Logger Lever的更多信息
Tip: Serilog has special handling for Exception objects; methods like Log.Error() take the exception as the first parameter, e.g. [ 提示:Serilog對Exception對象有特殊的處理;] Log.Error(ex, "Task {TaskName} was canceled", task). [ Log.Error(例如,「任務{任務名稱}被取消」,任務)。] Don't include exceptions in the log message itself. [ 不要在日誌消息自己中包含異常。]
提示:Serilog對Exception對象有特殊的處理; 像方法Log.Error()
將 exception 做爲第一個參數,例如Log.Error(ex, "Task {TaskName} was canceled", task)
,不要將異常的包括在message消息中
4. 觸發和收集結構化數據
讓咱們回到最後一個代碼片斷:
var itemNumber = 10;
var itemCount = 999;
Log.Debug("Processing item {ItemNumber} of {ItemCount}", itemNumber, itemCount);
您是否注意到日誌消息中的{ItemNumber}
這樣的佔位符? 這不是一個C#的內插字符串[Interpolated string C# 6.0的新特性],Log.*()
方法接收一個消息模板,另一種.NET格式化字符串,除了支持傳統的{0}
的方式,還支持{Name}
的方式。
這看起來有點奇怪,除非您意識到經過這樣作,Serilog能夠將這些消息的一部分做爲類的屬性與人性化的文本一塊兒捕獲:
{
"@t": "2017-11-20T11:33:01.22138",
"@l": "Debug",
"@m": "Processing item 10 of 999",
"ItemNumber": 10,
"ItemCount": 999
}
咱們爲何要這樣作?由於經過這種有趣的字段插入方式,並做爲屬性記錄到事件日誌中,咱們能夠當即使用優雅的簡單的過濾器來查找事件,就像ItemNumber > 900
,而無需經過正則表達式從消息中提取了。
進一步,咱們可使用 @
結構捕獲運算符 來獲取不只僅是平坦的屬性值,而是完整的對象:
var user = new { Name = "Nick", Id = "nblumhardt" };
Log.Information("Logged on user {@User}", user);
在這裏,user
對象被捕獲,並生成的JSON中,以便咱們可使用查詢來查找事件,如:User.Id = 'nblumhardt'
{
"@t": "2017-11-20T11:33:01.22138",
"@m": "Logged on user {\"Name\": \"Nick\", \"Id\": \"nblumhardt\"}",
"User": {"Name": "Nick", "Id": "nblumhardt"}
}
生產環境的監控和調試已經很是困難和耗時,並且常常是壓力山大的任務:而經過將相關的數據放在手邊,Serilog除去了運維操做相關活動的最大難題之一。
Tip: 從Visual Studio Marketplace安裝使人驚歎的Serilog Analyzer,能夠幫助你檢查你的消息模板類型( 注:這個插件還能幫你經過配置代碼生成appsetting.json的內容,可是隻支持生成一級配置:( )
這實際上有多大的差別取決於你如何收集Serilog的事件。通常來講,日誌事件進入文本文件並用grep
進行搜索。Serilog也能夠記錄文本文件,但不能在記事本中執行ItemNumber> 900
,所以您須要評估更強大的工具來執行此操做。
寫 JSON 格式的日誌文件
若是您的需求很簡單,您能夠將JSON寫入日誌文件,並使用支持JSON的工具直接查詢文件。] [ Serilog的文件接收器[sink]和緊湊的JSON格式化類庫[compact JSON formatter]使第一部分變得簡單。 讓咱們從新建一個控制檯應用程序,並安裝下列軟件包:
dotnet add package Serilog
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Formatting.Compact
在Main
函數中插入:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.File(new CompactJsonFormatter(), "log.clef")
.CreateLogger();
var itemCount = 99;
for (var itemNumber = 0; itemNumber < itemCount; ++itemNumber)
Log.Debug("Processing item {ItemNumber} of {ItemCount}", itemNumber, itemCount);
Log.CloseAndFlush();
運行這個程序將產生使用Serilog的緊湊格式[compact],在文件log.clef中生成以換行符分隔的JSON
流,若是沒有使用CompactJsonFormatter
,則會建立一個簡單餓扁平日誌文件。
若是你在文本編輯器中打開文件,你會看到JSON事件,就像咱們上面使用的例子。
CLEF-tool 是查詢CLEF格式的日誌文件的方便的命令行應用程序:(注:貌似只支持windows)
注意第二行的過濾器ItemNumber> 95
:絕不費力地在大型日誌流中定位事件,就是結構化日誌記錄的好處吧。
將日誌寫入日誌服務器
將日誌事件從多個應用程序發送到中央服務器或日誌聚合服務很是方便,而不是試圖在生產環境中多個日誌進行渾水摸魚[shuffle log files]
日誌服務器一般經過HTTP/S或UDP在網絡上接收事件,併爲開發人員和操做員工程師提供用戶界面,以便在出現問題時搜索和分析日誌流。
Serilog接收器[sinks]支持大量的日誌服務器,其中許多具備結構化數據支持。
注:這段是廣告就不翻譯了,讀者能夠根據實際需求選擇本身的日誌服務器。
5. 爲過濾和關聯標記事件
咱們剛剛看到消息模板如何實現咱們傳統上認爲能夠有效搜索和分析的日誌「消息」。
結構化日誌記錄的另外一方面是經過某種因果關係或空間關聯來識別相關事件集合。事件觸發:[Events raised: ]
- 在處理單個HTTP請求期間
- 從特定的機器,應用程序,服務或組件
- 關於單個客戶,訂單或交易
- 原由於事件的因果鏈
Serilog經過enrichment來處理全部這些狀況(以及其餘狀況)。Enrichment只是爲事件添加附加屬性,而不是源自消息模板的屬性
添加[Enriching]特定的屬性
最簡單的enrichment方法將固定屬性值添加到源自日誌記錄管道的全部事件,能夠經過Enrich.WithProperty()
方法快速實現
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("Application", "Demo")
.WriteTo.Seq("http://localhost:5341") //Seq 日誌服務器
.CreateLogger();
在LogEvents
上,經過enrichment添加的屬性看起來與源自消息模板的屬性相同
json { "@t": "2017-11-20T11:33:01.22138", "@l": "Debug", "@m": "Processing item 10 of 999", "ItemNumber": 10, "ItemCount": 999, "Application": "Demo" }
豐富特殊的屬性
經過建立和使用上下文記錄器,能夠將屬性添加到一個或幾個相關事件中,而不是增長具備相同值的全部事件
var orderLog = Log.ForContext("OrderId", order.Id);
orderLog.Information("Looking up product codes");
// ...
orderLog.Information("Product lookup took {Elapsed} ms", elapsed.TotalMilliseconds);
在這裏,經過orderLog
發出的兩個事件都會附加一個OrderId
屬性。
Enrichmen
是附加的:若是應用程序屬性設置在管道級別,則上面的第二個事件將攜帶Elapsed
(來自消息),OrderId
(來自上下文記錄器)和Application
(來自記錄管道)。
豐富消息源類型信息
記錄器特定的enrichment一個特例是關於如何使用建立它們的類標記事件
在名爲HomeController
的類中,使用如下命令建立類型特定的記錄器:
private readonly ILogger _log = Log.ForContext<HomeController>();
經過_log
發出的事件將攜帶一個值爲MyApp.Controllers.HomeController
的SourceContext
屬性。
充分利用上下文
爲了豐富工做單元中全部事件[爲全部事件添加特定屬性],Serilog
提供了LogContext
,首先須要使用Enrich.FromLogContext()
在LoggerConfiguration
級別啓用:
Log.Logger = new LoggerConfiguraition()
.Enrich.FromLogContext()
// ...
LogContext能夠被認爲是一堆(key,value)
鍵值對;
using (LogContext.PushProperty("MessageId", message.Id))
{
Log.Debug("Dispatching message of type {MessageType}", message.GetType());
await handler.HandleAsync(message);
}
關於LogContext有趣的是沒有什麼須要對象須要傳遞。在示例代碼中,HandleAsync()
以及由它調用的任何其餘方法的實現能夠直接使用Log
和ILogger
- MessageId屬性將自動T並添加LogEvent
中。
Tip: LogContext
是一個堆棧;推送到堆棧上的屬性必須經過釋放從PushProperty()返回的對象, -- 上述經過手動使用using
塊的方式
已經存在的Enricher
全部enrichment API都是使用Enricher
的實現Serilog的ILogEventEnricher
接口的對象來實現的。
NuGet中爲線程細節,機器信息和用戶詳細信息等內容提供了一些有趣的預先構建的Enricher實現。
Serilog.Enrichers.Thread 經過 Enrich.WithThreadId() 來添加線程ID相關的擴展:
Log.Logger = new LoggerConfiguration()
.Enrich.WithThreadId()
// ...
這將爲事件附加一個ThreadId
屬性,以便交錯事件能夠再次分開。
咱們將在下一節中看到一個簡單的例子,說明如何編寫和插入本身的應用程序專用的Enricher
程序。
6. 大海撈針 Finding needles in the haystack
若是咱們已經知道如何使用Serilog調用消息模板和enrichment結構化日誌的兩個支柱,那麼第三個支柱就是隱式事件類型的概念。
結構化日誌適合有效處理大量日誌數據。關於大型日誌流的一個有趣的觀察是,真實產生的事件比編寫日誌語句代碼塊時要多的多(注:這算什麼發現)
Log.Debug("Processing item {ItemNumber} of {ItemCount}", itemNumber, itemCount);
這意味着,儘管生成了許多獨特的消息字符串,如"Processing item 31 of 4159"
,但由此日誌記錄語句生成的每一個事件共享相同的消息模板,即"Processing item {ItemNumber} of {ItemCount}"
Serilog及其許多sinks 利用這一事實從根本上改進了查詢和過濾日誌事件的體驗。若是消息模板與事件一塊兒保存,則下面的過濾器能夠當即從嘈雜的日誌記錄語句中排除成千上萬的事件,從而更容易看到不然會被淹沒的有趣事件:
@MessageTemplate <> 'Processing item {ItemNumber} of {ItemCount}'
反轉也適用 - 放大事件類型能夠從單個日誌記錄語句中查找全部事件
如何利用此功能取決於您存儲和搜索日誌的位置。接下來咱們會看看細節。
Tip:字符串連接,C#內插字符串,以及其餘技術手段來預格式化傳遞給Serilog的消息內容,會取消此功能。詳細請看 How (not) to parameterize Serilog events
隱式事件類型
存儲,而後過濾羅嗦的消息模板並不老是理想的。 相反,一般從消息模板建立一個數字哈希值,並將其與事件一塊兒存儲:
事件類型 enrichment
日誌文件和本地不支持消息模板的日誌服務器仍然能夠經過在Serilog管道中明確地enricher
事件來接收事件類型。
爲此,自定義enricher
程序將EventType
屬性附加到每一個事件
// Install-Package Murmurhash
class EventTypeEnricher : ILogEventEnricher
{
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) {
var murmur = MurmurHash.Create32();
var bytes = Encoding.UTF8.GetBytes(logEvent.MessageTemplate.Text);
var hash = murmur.ComputeHash(bytes);
var numericHash = BitConverter.ToUInt32(hash, 0);
var eventType = propertyFactory.CreateProperty("EventType", numericHash);
logEvent.AddPropertyIfAbsent(eventType);
}
}
當插入管道時,這會使{EventType}
屬性可用於sinks
Log.Logger = new LoggerConfiguration()
.Enrich.With<EventTypeEnricher>()
.WriteTo.Console(outputTemplate:
"{Timestamp:HH:mm:ss} [{EventType:x8} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();
WriteTo.Console()
的參數是一個Serilog輸出模板,描述瞭如何將日誌事件的屬性格式化爲文本。 大多數基於文本的sinks(包括Serilog.Sinks.File
)均可以接受輸出模板來指導格式化。
[ 輸出以下所示:]
7. 下一步作什麼
Serilog是一個強大的庫,擁有普遍的插件和工具生態系統。咱們只涉及絕對的基礎知識 - 取決於您但願如何使用Serilog以及您使用的框架和平臺,還有不少能夠發現的地方。
這裏有一些文章和擴展,供你參考:
- Debugging and diagnostics - if you're having trouble getting Serilog or a sink to work, check out this page on the Serilog wiki
appsettings.json
configuration - in this article we've only shown the C# configuration API; Serilog.Settings.Configuration adds support for logger configuration in .NET Core JSON settings- XML
<appSettings>
configuration - Serilog.Settings.AppSettings adds support for reading logger configuration from .NET Framework configuration files - ASP.NET Core integration - the Serilog.AspNetCore package seamlessly integrates Serilog into ASP.NET Core 2.0 apps to take advantage of the structured log events emitted by the framework
- ASP.NET integration - check out SerilogWeb.Classic for a quick-and-painless way to record unhandled exceptions and request timings from ASP.NET projects
- Smart logging middleware for ASP.NET Core - improve the quality of request logging in ASP.NET Core with the middleware from this article
- Timed operations - SerilogTimings is a small wrapper for Serilog that makes it easy to log operation timings
- Autofac-Serilog integration - use AutofacSerilogIntegration to inject Serilog ILoggers through Autofac with type information automatically added
- Code analysis for Serilog - mentioned earlier, Serilog Analyzer checks message template syntax in Visual Studio as-you-type, and detects many potential Serilog usage issues
- Dynamic filtering - Serilog.Filters.Expressions makes it possible to filter events using a simple domain-specific language
- Async wrapper - the latency of logging to files or the console can be reduced further using the Serilog.Sinks.Async package
- Sink READMEs - most sinks, including Serilog.Sinks.File, Serilog.Sinks.Console, Serilog.Sinks.Seq and others, have good README documents in their GitHub repositories with detailed instructions for using the sink
- Structured Logging Concepts in .NET series - this blog series on structured logging has more detail on much of what we've covered in this tutorial
- F# support - if your application is written in F#, Destructurama.FSharp will let you log F# types seamlessly through Serilog
- JSON.NET support - Destructurama.JsonNet extends Serilog to allow JSON.NET dynamic objects to be logged as structured data
- Exception enricher - Serilog.Exceptions collects additional exception-type-specific information and attaches it to log events
- Async stack trace unmangling - Serilog.Enrichers.Demystify plugs in Ben Adams' Demystifier to improve the readability of exception stack traces
8. 得到幫助
Serilog有三大優秀的社區支持渠道:
- Stack Overflow - 若是您有Serilog使用問題,Stack Overflow上的Serilog標籤是一個很好的開始; 它被積極監控,而且您將經過留下一個容易找到的答案來幫助其餘人解決同一個問題
- Gitter Chat - 若是您的問題不符合Stack Overflow格式,或者您只是想完善檢查方法,那麼Gitter聊天室是與Serilog社區中的其餘人聯繫的快捷方式
- GitHub Issues - 最後,若是你發現了一個bug或者想要對Serilog進行改進,GitHub就是這個地方; Serilog organization 包括了serilog全部的核心庫和問題跟蹤。
Happy logging!