基于.NetCore3.1系列 —— 日志记录之初识Serilog
前言
對內置日志系統的整體實現進行了介紹之后,可以通過使用內置記錄器來實現日志的輸出路徑。而在實際項目開發中,使用第三方日志框架(如:Log4Net、NLog、Loggr、Serilog、Sentry 等)來記錄也是非常多的。首先一般基礎的內置日志記錄器在第三方日志框架中都有實現,然后第三方日志框架在功能上更加強大和豐富,能滿足我們更多的項目分析和診斷的需求。
所以在這一篇中,我們將介紹第三方日志記錄提供程序——Serilog
回顧
系統內置日志系列:
1. 基于.NetCore3.1系列 —— 日志記錄之日志配置揭秘
2. 基于.NetCore3.1系列 —— 日志記錄之日志核心要素揭秘
3. 基于.NetCore3.1系列 —— 日志記錄之自定義日志組件
從之前學習的內置日志系統中,我們根據日志配置的方式了解到了通過配置的方式,可以有效的輸出日志記錄,方便我們查找發現問題。
而在進一步對內部運行的主要核心機制進行深入探究后發現了內置日志記錄的幾個核心要素,在日志工廠記錄器(ILoggerFactory)中實現將日志記錄提供器(ILoggerProvider)對象都可以集成到Logger對象組合中,這樣的話,我們就可以通過基于ILoggerProvider自定義日志記錄程序集成到Logger中,再創建寫日志定義Ilogger,自定義日志記錄器實現日志的輸出方式,這樣實現自定義日志記錄工具。
在最后我們通過自定義的方式簡單的實現了自定義日志組件,在這個基礎上,我們可以根據具體的需求進行完善修改。當然了,我們也可以借用第三方日志框架組件程序進行使用。
說明
我們都知道日志記錄在項目開發中或者生產環境中,都起到舉足輕重的作用。因此,我們都會采用在項目加入第三方框架日志或自行封裝日志記錄來記錄日志。
所以在這一篇中,我們會采用在項目中使用Serilog,目的不僅僅在于希望在用戶使用之前發現代碼中的BUG和錯誤,更多的是方便我們可以快速的查詢生產環境的日志問題,深入的了解系統運行的表現。
從Serilog的官方介紹中,我們可以發現 其框架是.net中的診斷日志庫,可以在所有的.net平臺上運行。支持結構化日志記錄,對復雜、分布式、異步應用程序的支持非常出色。
Serilog是基于日志事件(log events),而不是日志消息(log message)??梢詫?strong>日志事件格式化為控制臺的可讀文本或者將事件化為JSON格式。應用程序中的日志語句會創建LogEvent對象,而連接到管道的接收器(sinks)會知道如何記錄它們。(接收器 包括各種終端、控制臺、文本、SqlServer、ElasticSearch等等可用的列表)
結構化與非結構化之間的問題:
對于日志的處理,在大部分情況下,會權衡是否對開發者的友好型以及對程序解析的方便性。在很多情況下,開發者可能只是想記錄一段日志而已,所以可以會考慮簡單的加上一行代碼來以達到記錄日志的目的,如(log.debug("Disk quota {0} exceeded by user {1}", quota, user);)當然了,日志的執行結構可能被存于文本文件或者數據庫中。這樣的日志從開發者的角度來說,清晰易懂,十分友好。
但是如果后續要使用程序取查找海量的的上述例子在某段時間內的特定用戶,則很難高效率地完成這一要求,因為需要對每個日志進行字符串解析。因此,我們就需要尋求更快更方便的方式來查找記錄。
非結構的日志
對自由格式文本的解析往往依賴于正則表達式,并且依賴于不變的文本。這會使解析自由格式的文本變得非常脆弱(即解析與代碼中的確切文本緊密耦合)。
還考慮搜索/查找的情況,例如:
SELECT text FROM logs WHERE text LIKE "Disk quota";LIKE條件需要與每個text行值進行比較;再次,這在計算上是相對浪費的,尤其是在使用通配符時:
SELECT text FROM logs WHERE text LIKE "Disk %";結構化的日志
使用結構化日志記錄,與磁盤錯誤相關的日志消息在JSON中可能如下所示:
{ "level": "DEBUG", "user": "username", "error_type": "disk", "text": "Disk quota ... exceeded by user ..." }這種結構的字段可以很容易地映射到例如 SQL表列名,這意味著查找可以更具體/更細粒度:
SELECT user, text FROM logs WHERE error_type = "disk";您可以在希望經常搜索/查找其值的列上放置索引,只要您不對LIKE這些列值使用子句即可。您可以將日志消息細分為特定類別的內容越多,查找的對象就越有針對性。例如,除了error_type上面示例中的字段/列之外,您甚至可以設置為be "error_category": "disk", "error_type": "quota"或諸如此類。
結構越多,你的日志消息,通過解析/檢索系統(如fluentd,elasticsearch,kibana),可以利用該結構,并以更快的速度和更低的CPU /內存執行任務。
總之這不僅與速度和效率有關,更重要的是使用結構化日志記錄和“結構化查詢”時,能以特定格式捕獲以及呈現結構化日志,同時提供對開發者與程序友好的解析支持??梢愿奖愕匾云錇闂l件進行篩選,搜索結果的相關性將更高。如果沒有這種搜索,那么在不同上下文中出現的任何單詞都會給您帶來大量無關的點擊。
開始
為了更好的理解認識Serilog,我們這簡單的創建一個新的項目來認識一下Serilog的使用。這里我們就簡單的使用Console和Debug的方式來實現,后續有機會我們可以實現更多方式的接收器寫入日志。
4.1 Serilog使用
4.1.1 安裝依賴包
Serilog.AspNetCore : 基于AspNetCore框架整合的Serilog日志記錄程序包,包含了Serilog基本庫和控制臺日志的實現。
當然了,你也可以直接安裝Serilog 基本庫,然后根據需要安裝對應的拓展包。
說明:
Serilog.Extensions.Logging 包含了注入了Serilog的拓展方法。
Serilog.Sinks.Async 實現了日志異步收集。
Serilog.Sinks.Console 實現了控制臺輸出日志。
Serilog.Sinks.Debug 實現了調試臺輸出日志。
Serilog.Sinks.File 實現了文件輸出日志。
4.1.2 配置Serilog
在應用程序中Program.cs文件中,配置Serilog記錄,確保正確記錄任何配置日志問題。
? ?public static void Main(string[] args){Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("Microsoft", LogEventLevel.Information).Enrich.FromLogContext().WriteTo.Console().CreateLogger();try{Log.Information("Starting web host");CreateHostBuilder(args).Build().Run();}catch (Exception ex){Log.Fatal(ex, "Host terminated unexpectedly");}finally{Log.CloseAndFlush();}}然后,添加UseSerilog()到CreateHostBuilder()中的通用主機中。
? ?public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args) //從appsettings.json中讀取配置。.UseSerilog() // <-- Add this line.ConfigureLogging((hostingContext, logging) =>{logging.ClearProviders(); //去掉默認添加的日志提供程序}).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();}); }最后,通過刪除默認記錄器的其余配置進行清理,從appsettings.json文件中刪除Logging對應的配置部分??梢栽偈褂酶鶕erilog的配置規則進行相應配置替換它。
"Serilog": { "MinimumLevel": { "Default": "Information", "Override": { "Microsoft": "Warning", "System": "Warning" } } }4.1.3 提示
當在IIS下運行時候,要在Visual Studio輸出窗口中查看Serilog輸出日志的時候,需要將輸出方式選擇為 Web 服務器方式,輸出窗口查看日志,或者使用WriteTo.Debug()替換記錄器配置中的WriteTo.Console()。
4.2 輸出格式
4.2.1 文本格式
作為文本,它的格式如下:
[21:45:15 INF] ?HTTP GET / responded 200 in 227.3253 ms測試在控制臺中輸出如下:
上述事件格式中,可以看出由以下幾個格式組成:
事件發生時的時間戳[timestamp]
描述何時應該捕獲事件的級別[level]
記錄事件的消息[message]內容]
描述事件的命名屬性[properties]
還可能有一個Exception對象
4.2.2 JSON格式
作為JSON格式,它的格式如下:
{"@t": "2020-08-27T13:59:44.6410761Z","@mt": "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms","@r": ["224.5185"],"RequestMethod": "GET","RequestPath": "/","StatusCode": 200,"Elapsed": 224.5185,"RequestId": "0HLNPVG1HI42T:00000001","CorrelationId": null,"ConnectionId": "0HLNPVG1HI42T" }在寫入日志文件中,根據Serilog的多種接收器的中(Console()、Debug()、File())等支持使用JSON寫入日志記錄,通過引用緊湊的JSON格式化類庫[Serilog.Formatting.Compact]接收所有JSON格式的輸出。
要編寫以換行符分隔的JSON,請將CompactJsonFormatter或RenderedCompactJsonFormatter傳遞到接收器配置方法,如下:
.WriteTo.Console(new RenderedCompactJsonFormatter())或.WriteTo.Console(new CompactJsonFormatter())運行這個程序將產生使用Serilog的緊湊格式JSON,并在對應的輸出路徑中生成換行符分隔的JSON流。
4.3 示例
4.3.1 安裝依賴包
安裝 Serilog.AspNetCore NuGet 包 ;
4.3.2 配置文件
在appsettings.json配置文件添加 Serilog 配置,WriteTo 指定輸出目標位置,它是一個數組類型,所以可以指定多個目標位置,這里暫時只指定輸出到控制臺:
{"Serilog": {"MinimumLevel": {"Default": "Debug"}} }4.3.3 設置配置信息
讀取配置文件信息,設置配置信息
public static IConfiguration Configuration { get; } = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true).AddEnvironmentVariables().Build();在main方法中,
public static void Main(string[] args){Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration).Enrich.FromLogContext().WriteTo.Debug() ? //輸出路徑.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}") ? ?//模板.CreateLogger();try{Log.Information("Starting web host");CreateHostBuilder(args).Build().Run();}catch (Exception ex){Log.Fatal(ex, "Host terminated unexpectedly");}finally{Log.CloseAndFlush();}}在Program.cs 添加 UseSerilog()
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();}).UseSerilog(); ?//添加4.3.4 設置請求管道
在 Startup.cs 的 中的Configure 請求管道中添加 UseSerilogRequestLogging:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseStaticFiles();app.UseSerilogRequestLogging();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();}); }重要的是UseSerilogRequestLogging()調用應出現在諸如MVC之類的處理程序之前。中間件不會對管道中出現在它之前的組件進行時間或日志記錄。通過將UseSerilogRequestLogging()放在它們之后,可以將其用于從日志中排除雜亂的處理程序,例如UseStaticFiles()。)
為了減少每個HTTP請求需要構造,傳輸和存儲的日志事件的數量。在同一事件上具有許多屬性還可以使請求詳細信息和其他數據的關聯更加容易。
默認情況下,以下請求信息將作為屬性添加:
請求方法
請求路徑
狀態碼
響應時間
您可以使用UseSerilogRequestLogging()上的選項回調來修改用于請求完成事件的消息模板,添加其他屬性或更改事件級別:
app.UseSerilogRequestLogging(options => {// 自定義消息模板options.MessageTemplate = "Handled {RequestPath}";// 發出調試級別的事件,而不是默認事件options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug; ?//將其他屬性附加到請求完成事件options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>{diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);}; });4.3.5 輸出效果
由于日志總是輸出一堆,我們不能快速的查找定位問題,其實 Serilog 輸出的日志是非常簡潔的,只有 HTTP GET ... 這一條,其他都是 AspNetCore 系統本身輸出的,所以我們可以對輸出的日志進行簡化操作。
4.3.6 輸出簡化
為了使日志輸出更簡潔,我們可以設置不輸出 AspNetCore Info 日志,只需在 Serilog配置節點中設置 AspNetCore 日志輸出級別為 Warning:
{"Serilog": {"MinimumLevel": {"Default": "Debug","Override": {"Microsoft": "Warning","System": "Warning"}}} }總結
? ? 1. 本篇主要是對Serilog的說明,認識到是一個基于日志事件的而非日志消息的結構化日志類庫。
? ? 2. 簡單的涉及對基礎知識的認識以及使用,通過構建一個新的項目來實現Serilog的日志記錄以及怎么使用這個框架。
? ? 3. 在后續中如何結合這個日志類庫引入項目中使用,以及對日志怎么存儲和查詢進行說明(會考慮 ELK存儲采集分析 )。
? ? 4. 如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。
? ? 5. 參考資料:官方簡介 、Serilog文檔、serilog-aspnetcore
? ? 6. 搜索關注公眾號【DotNet技術谷】--回復【serilog】,可獲取本篇文章的源碼。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的基于.NetCore3.1系列 —— 日志记录之初识Serilog的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据库单表千万行 LIKE 搜索优化手记
- 下一篇: 使用 C# 捕获进程输出