net core 3.1 利用serilog 將Log資訊整合至ELK (Elasticsearch,Logstash,Kibana)

由於原本的環境是將log寫至文字檔.但後期希望在新的開發上.
能保留現有的log機制但只保留最新的舊的能夠自動刪除.
當網路環境不穩定時,能夠先保留著本機的log檔,等到網路正常時可以正常傳送log資訊.
可以依照不同的專案存入指定的地方.
所以需求情境上整理如下

  1. 當網路環境延宕時,重啟後能夠做到續傳
  2. log文字檔能夠保留最新,自動刪除舊有的
  3. ​能依不同專案將log區分

腦補兼google的情況下,當然是用serilog嚕,
除了他整合至Microsoft.Extensions.Logging以外
擴充套件的完整與彈性也是相當完善
如下圖

安裝環境與ELK版本 (小弟在此就不多贅述如何安裝,會在參考連結中擺放相關資訊)
Ubuntu 18.04 
Elasticsearch v7.9.1
Kibana v7.9.1
Logstash v7.91


net core 3.1 所需安裝套件

Serilog.AspNetCore v3.4.0

dotnet add package Serilog.AspNetCore --version 3.4.0

Serilog.Settings.Configuration v3.1.0

dotnet add package Serilog.Settings.Configuration --version 3.1.0

Serilog.Sinks.Async v1.4.0

dotnet add package Serilog.Sinks.Async --version 1.4.0

Serilog.Sinks.Http v 7.0.1

dotnet add package Serilog.Sinks.Http --version 7.0.1

流程如下圖

接下來就是實作的部分嚕
Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }


    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog((ctx,config) =>
            {
                //設定只有Error時才紀錄
                config.MinimumLevel.Error();
                //設定送至logstash位置
                config.WriteTo.Http("http://localhost:8080/project_a");
          
            })
            .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}

很簡單易用,透過http這個方法,就可以將log資訊送往Logastash.
不過依照Serilog.Sinks.Http官方說明.當網路延宕重啟時他並不會保留數據如下圖說明

這樣就無法滿足第1,2點的需求
所幸Serilog.Sinks.Http 內提供另外兩個方法.
相同的是都會在本機儲存檔案,當網路延宕重啟時,會傳送尚未傳送的log
差異如下

透過bufferFileSizeLimitBytes 此參數(預設是1G=1024*1024*1024 ) 設定Log檔案大小,
當網路延宕發生時,會將Log紀錄保存至實體硬碟中,如果當單一檔案大小超過所設定或預設的數值,
則需等待下一次產生檔案的時間.
bufferPathFormat參數範例:
Buffer-{Date}.json -30-60分鐘之間自動產生
Buffer-{HalfHour}.json -每30分鐘產生
Buffer-{Hour}.json -每小時產生
簡單來說呢...就是當你log檔案超出你所設定的檔案大小,你就要等待新的log檔產生,才會繼續寫入log並往Logstash送

DurableHttpUsingTimeRolledBuffers

bufferFileSizeLimitBytes參數設定(預設是1G=1024*1024*1024 )單一Log檔案大小,
當網路延宕發生時,會將Log紀錄保存至實體硬碟中,如果當單一檔案大小超過所設定或預設的數值,
則會產生  {bufferBaseFileName 參數所定義的名稱}-{日期}_001.json 以此類推.....
當網路恢復正常時,會將未寄送的紀錄送出至Logstash,只保留兩個最新的檔案.舊的則會移除

DurableHttpUsingFileSizeRolledBuffers

兩相比較之後DurableHttpUsingFileSizeRolledBuffers確實能滿足原有需求1,2點.動動手改一下XD
Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }


    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog((ctx,config) =>
            {
                
                config.MinimumLevel.Error();
                config.WriteTo
                    .DurableHttpUsingFileSizeRolledBuffers(
                        "http://localhost:8080/project_a",
                        "test"
                    );
            })
            .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}

關於第三點,就從logstash 設定檔動手,依照request path去區分該往哪個index放
logstash.conf

filter {
     split {
           field => "events"
           target => "e"
           }

     mutate {
            add_field => {"project_name" => "%{[headers][request_path]}"}
            remove_field => ["events","headers"]
           }
  }

output {
   stdout { codec => rubydebug }
   if [project_name] == "/project_a" {
     elasticsearch {
       hosts => ["http://localhost:9200/"]
       index => "test-logs"
       document_type => "log"
       user => "elastic"
       password => "1qaz2wsx"
   }
 }
}

最後總是得測試一下.
LogSampleTestController.cs
 

[ApiController]
[Route("[controller]")]
public class LogSampleTestController : ControllerBase
{

    private readonly ILogger<LogSampleTestController> _logger;

    public LogSampleTestController(ILogger<LogSampleTestController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Get()
    {


        var logObj = new
        {
            name="toast",
            height=170,
            weight="secret",
            address="secret"
        };
        _logger.LogError(JsonConvert.SerializeObject(logObj));
        
        return Ok();
    }
}

最後開啟Kibana的網頁確認資料是否有寫入ES中

看樣子有正確寫入至ES.

Serilog 在使用上相對簡單,也省去自己或團隊上在重刻輪子的時間 XD.
 

 

GitHub:範例程式

參考連結:
Provided Sinks
Serilog.Sinks.Http - A Serilog sink sending log events over HTTP
Building logging system in Microservice Architecture with ELK stack and Serilog .NET Core [Part 2]
ELK環境建置與介紹