Serilog日志同步到redis中和自定义Enricher来增加额外的记录信息
阅读原文时间:2023年07月10日阅读:1

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();
       
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();

       …….后续忽略自行修改
    }