基於.NetCore3.1系列 —— 日誌記錄之初識Serilog

一、前言

對內置日誌系統的整體實現進行了介紹之後,可以通過使用內置記錄器來實現日誌的輸出路徑。而在實際項目開發中,使用第三方日誌框架(如: Log4NetNLogLoggrSerilogSentry 等)來記錄也是非常多的。首先一般基礎的內置日誌記錄器在第三方日誌框架中都有實現,然後第三方日誌框架在功能上更加強大和豐富,能滿足我們更多的項目分析和診斷的需求。

所以在這一篇中,我們將介紹第三方日誌記錄提供程序——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)。可以將日誌事件格式化爲控制檯的可讀文本或者將事件化爲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"或諸如此類。

結構越多,你的日誌消息,通過解析/檢索系統(如fluentdelasticsearchkibana),可以利用該結構,並以更快的速度和更低的CPU /內存執行任務。

總之這不僅與速度和效率有關,更重要的是使用結構化日誌記錄和“結構化查詢”時,能以特定格式捕獲以及呈現結構化日誌,同時提供對開發者與程序友好的解析支持。可以更方便地以其爲條件進行篩選,搜索結果的相關性將更高。如果沒有這種搜索,那麼在不同上下文中出現的任何單詞都會給您帶來大量無關的點擊。

四、開始

爲了更好的理解認識Serilog,我們這簡單的創建一個新的項目來認識一下Serilog的使用。這裏我們就簡單的使用ConsoleDebug的方式來實現,後續有機會我們可以實現更多方式的接收器寫入日誌。

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對應的配置部分。可以再使用根據Serilog的配置規則進行相應配置替換它。

"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

測試在控制檯中輸出如下:

serilog

上述事件格式中,可以看出由以下幾個格式組成:

  • 事件發生時的時間戳[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,請將CompactJsonFormatterRenderedCompactJsonFormatter傳遞到接收器配置方法,如下:

 .WriteTo.Console(new RenderedCompactJsonFormatter())
 或
  .WriteTo.Console(new CompactJsonFormatter())

運行這個程序將產生使用Serilog的緊湊格式JSON,並在對應的輸出路徑中生成換行符分隔的JSON流。

serilog

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

由於日誌總是輸出一堆,我們不能快速的查找定位問題,其實 Serilog 輸出的日誌是非常簡潔的,只有 HTTP GET ... 這一條,其他都是 AspNetCore 系統本身輸出的,所以我們可以對輸出的日誌進行簡化操作。

4.3.6 輸出簡化

爲了使日誌輸出更簡潔,我們可以設置不輸出 AspNetCore Info 日誌,只需在 Serilog配置節點中設置 AspNetCore 日誌輸出級別爲 Warning

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    }
  }
}

serilog

五、總結

  1. 本篇主要是對Serilog的說明,認識到是一個基於日誌事件的而非日誌消息的結構化日誌類庫。
  2. 簡單的涉及對基礎知識的認識以及使用,通過構建一個新的項目來實現Serilog的日誌記錄以及怎麼使用這個框架。
  3. 在後續中如何結合這個日誌類庫引入項目中使用,以及對日誌怎麼存儲和查詢進行說明(會考慮 ELK存儲採集分析 )。
  4. 如果有不對的或不理解的地方,希望大家可以多多指正,提出問題,一起討論,不斷學習,共同進步。
  5. 參考資料: 官方簡介Serilog文檔serilog-aspnetcore
  6. 本文源碼下載地址
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章