ASP.NET Core 日誌收集(log4net+Kafka+ELK)
在開發環境中,記錄日誌這件事情常常被忽視,因為我們有強大的 IDE ,可以除錯,可以斷點,問題出現後一般都能很快解決。但在生產環境,異常一旦發生,如果不能重現,又沒有提前做好日誌記錄,就會非常被動,問題的定位也可能需要花很多時間,問題最終也是不了了之。給對方的回覆也許是這樣的: 可能你的網路那個時間點有問題 。
日誌當然不只是為了定位問題,實際我們還可以通過日誌進行一系列的分析,通過分析結果我們可以獲得很多有效資訊。這篇文章將介紹如何在 .NET Core 專案中使用 log4net 進行日誌收集,雖然是 log4net ,但其他 log4xxx 也類似,畢竟 Kafka + ELK 這一套和語言無關。
在使用 log4net 前,我們需要先搭建好 Kafka 和 ELK 環境,之前的文章介紹過 ofollow,noindex">ELK + Filebeat 搭建日誌系統 、 Elasticsearch 叢集 、 Kafka 叢集 ,這裡會將使用 Kafka 作日誌儲存,實際情況完全可以 log4net + Filebeat + ELK 、log4net + Redis + ELK 等組合。Kafka、Redis 、Filebeat 都有其自身的特點,根據專案情況選擇一種適合的方案即可。
以下 Kafka 和 ELK 的測試環境將基於 Windows 的非叢集模式,叢集模式請參考上面的文章。
基礎環境搭建
下載 ELK ( 當前最新 6.5.1 ), Kafka ( Binary downloads ) 、 Zookeeper 最新版本,下載後分別解壓。每個服務的配置檔案這裡不詳細介紹了,使用預設配置即可。
ELK
Elasticsearch
在 elasticsearch-6.5.1 目錄下,執行啟動命令:
bin\elasticsearch
啟動成功後,可通過 http://localhost:9200 檢視 Elasticsearch 的一些基本資訊。
Logstash
將 logstash-6.5.1\config 目錄下的 logstash-sample.conf 重新命名為 logstash.conf,內容暫替換為:
# 輸入 input { # 標準輸入(命令列輸入) stdin { } } # 輸出 output { # 標準輸出 stdout { codec => rubydebug } }
執行啟動命令:
bin\logstash -f config\logstash.conf
啟動成功後,在當前的啟動視窗中輸入測試訊息:hi ,馬上會返回輸出一段 Json 格式的資料,message 為輸入的內容,其他欄位是內建的。
Kibana
在 kibana-6.5.1-windows-x86_64 目錄下,執行啟動命令:
bin\kibana
啟動成功後,通過 http://localhost:5601 訪問 Kibana UI,Kibana 預設配置的 Elasticsearch 地址是 http://localhost:9200,所以 Kibana 啟動之前,確保 Elasticsearch 已啟動,不然會提示 Elasticsearch 狀態不正常。
Zookeeper
將 zookeeper-3.4.13\conf 目錄下的 zoo_sample.cfg 重新命名為 zoo.cfg,執行啟動命令:
bin\zkServer
Kafka
在 kafka_2.12-2.1.0 目錄下,執行啟動命令:
.\bin\windows\kafka-server-start.bat .\config\server.properties
Zookeeper 預設訪問地址是 localhost:2181,Kafka 啟動後預設的 zookeeper.connect=localhost:2181,Kafka 強依賴於 Zookeeper ,所以必須先啟動 Zookeeper。
通過以上步驟,完成了 Kafka 和 ELK 單例項的環境搭建。
專案搭建
- 建立一個基於 .NET Core 的 WebApi 專案;
- Nuget 安裝 Microsoft.Extensions.Logging.Log4Net.AspNetCore;
-
啟動檔案中注入 Log4Net;
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureLogging((logging) => { // 過濾掉 System 和 Microsoft 開頭的名稱空間下的元件產生的警告級別以下的日誌 logging.AddFilter("System", LogLevel.Warning); logging.AddFilter("Microsoft", LogLevel.Warning); logging.AddLog4Net(); }) .UseStartup<Startup>();
-
根目錄下新增 log4net.config,設定為 “如果較新則複製”;
log4net 提供了多種內建的 appender,配置使用哪種 appender,就會以對應 appender 的實現方式來記錄日誌。以下例子使用了 FileAppender,日誌的格式由 layout 設定,追加的方式寫入 Logs/log-file.log。
<?xml version="1.0" encoding="utf-8" ?> <log4net> <appender name="FileAppender" type="log4net.Appender.FileAppender"> <file value="Logs/log-file.log" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" /> </layout> </appender> <root> <level value="ALL"/> <appender-ref ref="FileAppender" /> </root> </log4net>
appender 支援自定義,只要符合 appender 定義規則即可,這裡我開發了一個將日誌寫入 Kafka 的 Nuget 包, log4net.Kafka.Core ,安裝後在 log4net.config 新增 KafkaAppender 配置即可。 log4net.Kafka.Core 原始碼 在 github 上 ,可以 Fork 自行修改。
<?xml version="1.0" encoding="utf-8" ?> <log4net> <appender name="KafkaAppender" type="log4net.Kafka.Core.KafkaAppender, log4net.Kafka.Core"> <KafkaSettings> <broker value="127.0.0.1:9092" /> <topic value="api-log" /> </KafkaSettings> <layout type="log4net.Kafka.Core.KafkaLogLayout,log4net.Kafka.Core" > <appid value="api-test" /> </layout> </appender> <root> <level value="ALL"/> <appender-ref ref="KafkaAppender" /> </root> </log4net>
broker: Kafka 服務地址,叢集可使用,分割;
topic:日誌對應的 Topic 名稱;
appid:服務唯一標識,輔助識別日誌來源;
KafkaAppender 日誌寫入 Kafka Topic 的格式如下:
{ "host_name": "Beck", "logger_name": "WebApi.Controllers.ValuesController", "log_timestamp": 1543745129111, "level": "INFO", "message": "自定義的日誌內容", "app_id": "api-test", "exception": null }
接下來我們需要修改 Logstash 的配置檔案,之前 Logstash 的 input 設定的是標準輸入,現在需要改成 Kafka 輸入( 可以理解為 Logstash 是 Kafka 的消費端, 將消費 api-log 這個 Topic ),輸出改到 Elasticsearch, 這裡使用 app_id 作為索引名稱,使得每個服務的日誌相互獨立,便於後續檢視。修改後的 logstash.conf 如下:
# 輸入 input { kafka { codec => "json" bootstrap_servers => "localhost:9092" topics => ["api-log"] auto_offset_reset => "earliest" } } filter { date { # 時間戳轉換 match => [ "log_timestamp" , "UNIX_MS" ] } } # 輸出 output { # 輸出到 Elasticsearch elasticsearch { # Elasticsearch 地址 hosts => ["localhost:9200"] # Elasticsearch 索引名 index => "logstash-%{app_id}" } }
在 ValuesController 修改程式碼進行測試:
public class ValuesController : ControllerBase { private readonly ILogger _logger; public ValuesController(ILogger<ValuesController> logger) { _logger = logger; } [HttpGet] public ActionResult<IEnumerable<string>> Get() { _logger.LogInformation("request api/values"); _logger.LogError(new Exception("出錯啦!!!"), "request api/values"); return new string[] { "value1", "value2" }; } }
介面呼叫完成後,可以通過 Kibana 檢視到索引 logstash-api-test 的日誌資訊。
目前 log4net.Kafka.Core 封裝的並不完善,後面會繼續優化。