Serilog紀錄檔同步到redis中和自定義Enricher來增加額外的記錄資訊

2023-01-11 18:00:22

Serilog 紀錄檔同步到redis佇列中 後續可以通過佇列同步到資料庫、騰訊阿里等紀錄檔元件中,這裡redis庫用的新生命團隊的NewLife.Redis元件 可以實現輕量級訊息佇列(輕量級訊息佇列RedisQueue (newlifex.com)),也可以自行替換熟悉的元件

類庫目錄 該類庫需新增 Microsoft.AspNetCore.Http.Abstractions、NewLife.Redis、Newtonsoft.Json、Serilog包

 

 

 RedisStreamSink.cs 中的程式碼  定義RedisSink 將紀錄檔記錄到redis佇列中

using Microsoft.AspNetCore.Http;
using NewLife.Caching;
using Newtonsoft.Json;
using Serilog.Core;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Parsing;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace SeriLog.Sinks.RedisStream.Ms
{

    /// <summary>
    /// 用於序列化資料
    /// </summary>
    public class LogData
    {
        
        public DateTimeOffset Timestamp { get; set; }
      
        public LogEventLevel Level { get; set; }
        public string Message { get; set; }
        public string RequestIP { get; set; }
        public string HostName { get; set; }
        public static LogData LogEventToLogData(LogEvent logEvent)
        {
            var data = new LogData();
            data.Timestamp = logEvent.Timestamp;
            data.Level = logEvent.Level;
            return data;

        }
    }
    public class RedisStreamSink : ILogEventSink
    {
        private readonly ITextFormatter _formatter;
        private readonly FullRedis _redis;
        private readonly string _redisStreamName;
        public RedisStreamSink(FullRedis fullRedis, string redisStreamName, ITextFormatter textFormatter)
        {
            _redis = fullRedis;
            _redisStreamName = redisStreamName;
            _formatter = textFormatter;
        }
      
        public void Emit(LogEvent logEvent)
        {
            string message =string.Empty;
            using (var writer = new StringWriter())
            {
                _formatter.Format(logEvent, writer);
                message = writer.ToString();
            }
            var data = LogData.LogEventToLogData(logEvent);
            data.Message = message.Replace("\r\n","");
            //獲取自定義需要記錄的資訊例如使用者端ip地址和主機名
            data.RequestIP = logEvent.Properties.TryGetValue("RequestIP", out LogEventPropertyValue? propertyIpValue) ? propertyIpValue.ToString() : string.Empty;
            data.HostName = logEvent.Properties.TryGetValue("HostName", out LogEventPropertyValue? propertyHostNameValue) ? propertyHostNameValue.ToString() : string.Empty;
            Console.WriteLine("===================================\r\n" + JsonConvert.SerializeObject(data));
            //新增到redis 佇列中
            var queue = _redis.GetQueue<string>(_redisStreamName);
            queue.Add(JsonConvert.SerializeObject(data));
           
          

        }

    }
}

RedisStreamSinkExtensions.cs  中的程式碼

using Serilog.Configuration;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NewLife.Caching;
using Serilog.Formatting;

namespace SeriLog.Sinks.RedisStream.Ms
{
    public static class RedisStreamSinkExtensions
    {
        //序列化時message中顯示的內容 簡化輸出
        private const string DefaultOutputTemplate = "(RequestId:{RequestId}){Message:j}{Exception}";
        public static LoggerConfiguration RedisStreamSink(
            this LoggerSinkConfiguration loggerConfiguration,
            FullRedis redis,
            string redisStreamName,
            string outputTemplate = DefaultOutputTemplate,
            IFormatProvider formatProvider = null
           )
        {
            var formatter = new Serilog.Formatting.Display.MessageTemplateTextFormatter(outputTemplate, formatProvider);
            return loggerConfiguration.Sink(new RedisStreamSink(redis, redisStreamName, formatter));
        }
    }
}

RequestInfoEnricher.cs 中的程式碼  自定義新增RequestIP和Referer資訊

using Microsoft.AspNetCore.Http;
using NewLife.Model;
using Serilog.Core;
using Serilog.Events;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SeriLog.Sinks.RedisStream.Ms
{
    public class RequestInfoEnricher : ILogEventEnricher
    {
        private readonly IServiceProvider _serviceProvider;
        public RequestInfoEnricher(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
       
        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
        {
            var httpContext = _serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext;
            if (null != httpContext)
            {
                //這裡新增自定義需記錄的資訊
                logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("RequestIP", httpContext.Connection.RemoteIpAddress));
                logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Referer", httpContext.Request.Headers["Referer"]));
            }
        }
    }
}

EnricherExtensions.cs 中的程式碼

using Serilog.Configuration;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SeriLog.Sinks.RedisStream.Ms
{
    public static class EnricherExtensions
    {
        public static LoggerConfiguration WithRequestInfo(this LoggerEnrichmentConfiguration enrich, IServiceProvider serviceProvider)
        {
           
            if (enrich == null)
                throw new ArgumentNullException(nameof(enrich));

            return enrich.With(new  RequestInfoEnricher(serviceProvider));
        }
    }
}

在需要用到的專案中新增 SeriLog.Sinks.RedisStream.Ms 專案參照

public static void Main(string[] args)
        {
            var fullRedis = FullRedis.Create($"server=127.0.0.1:6379,db=1");
            var builder = WebApplication.CreateBuilder(args);
            //這一步必須放在CreateLogger之前否則 RequestInfoEnricher中獲取不到HttpContextAccessor
            builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        Log.Logger
= new LoggerConfiguration() .MinimumLevel.Information() .Enrich.WithProperty("HostName", Dns.GetHostName()) .WriteTo.RedisStreamSink(fullRedis, "logger") //logger 為佇列的名稱 .Enrich.WithRequestInfo(builder.Services.BuildServiceProvider()) .CreateLogger(); builder.Host.UseSerilog();

       .......後續忽略自行修改
    }