在 ASP.NET Core 中使用 Serilog/Fluentd 將日志寫入 Elasticsearch
本文轉載自微信公眾號「DotNET技術圈」,作者Andrew Lock 。轉載本文請聯系DotNET技術圈公眾號。
原文來自:https://andrewlock.net/writing-logs-to-elasticsearch-with-fluentd-using-serilog-in-asp-net-core/
對于在 Kubernetes 中運行的應用程序,將日志消息存儲在一個中心位置尤為重要。我認為這對所有應用程序都很重要,無論您使用的是 Kubernetes 還是 docker,但 pod 和容器的短暫性質使得后一種情況特別重要。
如果您沒有集中存儲容器中的日志,那么如果容器崩潰并重新啟動,日志可能會永遠丟失。
有很多方法可以實現這一目標。您可以直接從您的應用程序登錄Elasticsearch或Seq,或者登錄到Elmah.io等外部服務。一種常見的方法是使用 Fluentd 從容器的控制臺輸出中收集日志,并將這些日志通過管道傳輸到 Elasticsearch 集群。
默認情況下,ASP.NET Core 中的控制臺日志輸出格式為人類可讀格式。如果您采用 Fluentd/Elasticsearch 方法,您需要確保您的控制臺輸出采用 Elasticsearch 可以理解的結構化格式,即 JSON。
在這篇文章中,我描述了如何將 Serilog 添加到 ASP.NET Core 應用程序,以及如何自定義 Serilog 控制臺接收器的輸出格式,以便您可以使用 Fluentd 將控制臺輸出通過管道傳輸到 Elasticsearch。
請注意,也可以將 Serilog 配置為使用Elasticsearch sink直接寫入Elasticsearch。如果您沒有使用 Fluentd,或者沒有將您的應用程序容器化,那么這是一個不錯的選擇。
將日志寫入控制臺輸出
當您從模板創建新的 ASP.NET Core 應用程序時,您的程序文件將如下所示(至少在 .NET Core 2.1 中):
- public class Program
- {
- public static void Main(string[] args)
- {
- CreateWebHostBuilder(args).Build().Run();
- }
- public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseStartup<Startup>();
- }
靜態輔助方法WebHost.CreateDefaultBuilder(args)創建一個WebHostBuilder并連接許多標準配置選項[1]。默認情況下,它配置控制臺和調試記錄器提供程序:
- .ConfigureLogging((hostingContext, logging) =>
- {
- logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
- logging.AddConsole();
- logging.AddDebug();
- })
如果您從命令行使用 運行您的應用程序dotnet run,您將在控制臺中看到每個請求的日志。下面顯示了來自瀏覽器的兩個請求生成的日志 - 一個用于主頁,另一個用于 favicon.ico。
使用默認控制臺記錄器的控制臺輸出
不幸的是,控制臺記錄器在如何寫入日志方面沒有提供很大的靈活性。您可以選擇包含 scopes或禁用顏色,但僅此而已。
ASP.NET Core 中默認Microsoft.Extensions.Logging基礎結構的替代方法是使用 Serilog進行日志記錄,并將其作為標準 ASP.NET Core 記錄器進行連接。
將 Serilog 添加到 ASP.NET Core 應用程序
Serilog是一個成熟的開源項目,早于 ASP.NET Core 中的所有日志記錄基礎結構。在許多方面,ASP.NET Core 日志記錄基礎結構似乎以 Serilog 為模型:Serilog 具有類似的配置選項和可插拔的“接收器”來控制寫入日志的位置。
開始使用 Serilog 的最簡單方法是使用Serilog.AspNetCore NuGet 包。使用以下命令將其添加到您的應用程序中:
- dotnet add package Serilog.AspNetCore
您還需要添加一個或多個“sink”包,以控制日志的寫入位置。在這種情況下,我將安裝控制臺接收器,但如果您想一次寫入多個目的地,您也可以添加其他接收器。
- dotnet add package Serilog.Sinks.Console
Serilog.AspNetCore 包UseSerilog()在WebHostBuilder實例上提供了一個擴展方法。這用ILoggerFactorySerilog 的實現替換了默認值。您可以傳入現有Serilog.ILogger實例,也可以內聯配置記錄器。例如,以下代碼配置將寫入的最低日志級別 ( info) 并注冊控制臺接收器:
- public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseSerilog((ctx, config) =>
- {
- config
- .MinimumLevel.Information()
- .Enrich.FromLogContext()
- .WriteTo.Console();
- })
- .UseStartup<Startup>();
- }
當您使用 Serilog 而不是默認記錄器時再次運行應用程序會提供以下控制臺輸出:
使用 Serilog 而不是默認的控制臺記錄器的控制臺輸出
輸出類似于默認記錄器,但重要的是它是非??膳渲玫摹D梢噪S意更改輸出模板。例如,您可以通過包含SourceContext參數來顯示生成日志的類的名稱。
有關 Serilog.AspNetCore 包的更多詳細信息和示例,請參閱 GitHub 存儲庫[2]。有關控制臺格式選項,請參閱Serilog.Sinks.Console 存儲庫[3]。
除了對輸出模板的簡單更改外,控制臺接收器還允許完全控制消息的呈現方式。我們將使用該功能將日志呈現為 Fluentd 的 JSON,而不是人性化的格式。
自定義Serilog Console Sink的輸出格式寫入JSON
要更改數據的呈現方式,您可以添加自定義ITextFormatter. Serilog 包含一個JsonFormatter您可以使用的,但建議您考慮使用Serilog.Formatting.Compact包[4]:
“CompactJsonFormatter與 Serilog 的默認值相比,大大減少了小日志事件的字節數JsonFormatter,同時保持人類可讀。它通過更短的內置屬性名稱、更精簡的格式以及排除冗余信息來實現這一點。”
我們不會將這個包用于我們的 Fluentd/Elasticsearch 用例,但我將展示如何在任何情況下插入它。使用 添加包dotnet add package Serilog.Formatting.Compact,創建格式化程序的新實例,并將其傳遞給調用中的WriteTo.Console()方法UseSerilog():
- .UseSerilog((ctx, config) =>
- {
- config
- .MinimumLevel.Information()
- .Enrich.FromLogContext()
- .WriteTo.Console(new CompactJsonFormatter());
- })
現在,如果您運行您的應用程序,您將看到以 JSON 格式寫入控制臺的日志:
使用 CompactJsonFormatter 以 JSON 格式寫入控制臺的日志圖像
這個格式化程序可能對您有用,但就我而言,我希望編寫 JSON 以便 Elasticsearch 能夠理解它。您可以看到緊湊的 JSON 格式(在下面打印得很漂亮),正如承諾的那樣,為時間戳 ( @t)、消息模板 ( @mt) 和呈現的消息 ( @r)使用了緊湊的名稱:
- {
- "@t": "2018-05-17T10:23:47.0727764Z",
- "@mt": "{HostingRequestStartingLog:l}",
- "@r": [
- "Request starting HTTP\/1.1 GET http:\/\/localhost:5000\/ "
- ],
- "Protocol": "HTTP\/1.1",
- "Method": "GET",
- "ContentType": null,
- "ContentLength": null,
- "Scheme": "http",
- "Host": "localhost:5000",
- "PathBase": "",
- "Path": "\/",
- "QueryString": "",
- "HostingRequestStartingLog": "Request starting HTTP\/1.1 GET http:\/\/localhost:5000\/ ",
- "EventId": {
- "Id": 1
- },
- "SourceContext": "Microsoft.AspNetCore.Hosting.Internal.WebHost",
- "RequestId": "0HLDRS135F8A6:00000001",
- "RequestPath": "\/",
- "CorrelationId": null,
- "ConnectionId": "0HLDRS135F8A6"
- }
對于最簡單的 Fluentd/Elasticsearch 集成,我希望使用標準 Elasticsearch 名稱(例如@timestamp時間戳)輸出 JSON 。幸運的是,所需要的只是更換格式化程序。
使用與 Elasticsearch 兼容的 JSON 格式化程序
該Serilog.Sinks.Elasticsearch包包含正是我們所需要的格式ElasticsearchJsonFormatter。這使用標準 Elasticsearch 字段(如@timestamp和 )呈現數據fields。
不幸的是,目前除了復制和粘貼源代碼首先檢查許可證之外,將格式化程序添加到您的項目的唯一方法是安裝整個Serilog.Sinks.Elasticsearch包,它有很多依賴項。
理想情況下,我希望將格式化程序視為它自己的獨立包,就像Serilog.Formatting.Compac一我提出了一個問題[5]如果這對您來說不是問題(這對我來說不是問題,因為我已經依賴Elasticsearch.Net,那么添加 Elasticsearch Sink 來訪問格式化程序是最簡單的解決方案。使用添加接收器dotnet add package Serilog.Sinks.ElasticSearch,并更新您的 Serilog 配置使用ElasticsearchJsonFormatter:
- .UseSerilog((ctx, config) =>
- {
- config
- .MinimumLevel.Information()
- .Enrich.FromLogContext()
- .WriteTo.Console(new ElasticsearchJsonFormatter();
- })
連接此格式化程序后,控制臺輸出將包含常見的 Elasticsearch 字段,如@timestamp,如下面(漂亮打印)輸出所示:
- { "@timestamp": "2018-05-17T22:31:43.9143984+12:00", "level": "Information", "messageTemplate": "{HostingRequestStartingLog:l}", "message": "Request starting HTTP\/1.1 GET http:\/\/localhost:5000\/ ", "fields": { "Protocol": "HTTP\/1.1", "Method": "GET", "ContentType": null, "ContentLength": null, "Scheme": "http", "Host": "localhost:5000", "PathBase": "", "Path": "\/", "QueryString": "", "HostingRequestStartingLog": "Request starting HTTP\/1.1 GET http:\/\/localhost:5000\/ ", "EventId": { "Id": 1 }, "SourceContext": "Microsoft.AspNetCore.Hosting.Internal.WebHost", "RequestId": "0HLDRS5H8TSM4:00000001", "RequestPath": "\/", "CorrelationId": null, "ConnectionId": "0HLDRS5H8TSM4" }, "renderings": { "HostingRequestStartingLog": [ { "Format": "l", "Rendering": "Request starting HTTP\/1.1 GET http:\/\/localhost:5000\/ " } ] }}
現在日志以一種可以直接從 Fluentd 傳輸到 Elasticsearch 的格式呈現。我們可以只寫到控制臺。
根據托管環境在輸出格式化程序之間切換
最后的提示。如果您想在本地開發時獲得人類可讀的控制臺輸出,并且只在 Staging 或 Production 中使用 JSON 格式化程序怎么辦?
這很容易實現,因為UseSerilog擴展提供了IHostingEnvironment通過WebHostBuilderContext. 例如,在以下代碼段中,我將應用程序配置為在開發中使用人類可讀的控制臺,并在其他環境中使用 JSON 格式化程序。
- .UseSerilog((ctx, config) =>
- {
- config
- .MinimumLevel.Information()
- .Enrich.FromLogContext();
- if (ctx.HostingEnvironment.IsDevelopment())
- {
- config.WriteTo.Console();
- }
- else
- {
- config.WriteTo.Console(new ElasticsearchJsonFormatter());
- }
- })
除了環境之外,您還可以根據通過IConfiguration對象 at提供的配置值進行切換ctx.Configuration。
概括
將日志存儲在一個中心位置很重要,尤其是在您構建容器化應用程序時。一種可能的解決方案是將日志輸出到控制臺,讓 Fluentd 監控控制臺,并將輸出通過管道傳輸到 Elasticsearch 集群。在這篇文章中,我描述了如何將 Serilog 日志記錄添加到您的 ASP.NET Core 應用程序并將其配置為以 Elasticsearch 期望的 JSON 格式將日志寫入控制臺。
References
[1] 連接許多標準配置選項: https://github.com/aspnet/MetaPackages/blob/2.1.0-rc1-final/src/Microsoft.AspNetCore/WebHost.cs#L150
[2] 請參閱 GitHub 存儲庫: https://github.com/serilog/serilog-aspnetcore
[3] Serilog.Sinks.Console 存儲庫: https://github.com/serilog/serilog-sinks-console
[4] 考慮使用Serilog.Formatting.Compact包: https://github.com/serilog/serilog-formatting-compact
[5] 我提出了一個問題: https://github.com/serilog/serilog-sinks-elasticsearch/issues/176