扩展Serilog实现日志推送平台

最近在完成一个服务管理平台,提供可视化管理车间控制服务,包括服务的启停,日志的查看。在各服务中使用 Serilog 对日志进行记录,推送到服务管理平台,再进行统一分发,各客户端同步查看服务运行日志。

Serilog

Serilog 是一个用于 .NET 应用程序的诊断日志库。它易于设置,具有干净的 API,并且可以在所有最新的 .NET 平台上运行。虽然即使在最简单的应用程序中也很有用,但 Serilog 对结构化日志记录的支持在检测复杂、分布式和异步应用程序和系统时大放异彩,具体可以查看Serilog-Github

Serilog 提供了非常简单的扩展API,只需要继承 ILogEventSink 并实现 Emit 方法即可,下文提供了两个 Web 扩展。

HTTP扩展

编写 HttpSink 继承 ILogEventSink ,使用POST进行数据发送。

public class HttpSink : ILogEventSink
{
    private readonly Uri _uri;
    private readonly IFormatProvider _formatProvider;

    public HttpSink(Uri uri, IFormatProvider formatProvider)
    {
        _uri = uri;
        _formatProvider = formatProvider;
    }

    public async void Emit(LogEvent logEvent)
    {
        var message = logEvent.RenderMessage(_formatProvider);
        var req = new LogMessage(logEvent.Level.ToString(), message);
        byte[] b = JsonSerializer.SerializeToUtf8Bytes(req);
        var s = Encoding.UTF8.GetString(b);
        await new HttpClient().PostAsync(_uri, new StringContent(s, Encoding.UTF8, "application/json"));
    }
}

编写扩展方法,提供配置入口。可以根据实际需求重载很多方法,例如传入 string 类型的 url,或者提供用于通过服务验证的凭证等。

public static class LoggerConfigurationHttpRExtensions
{

    public static LoggerConfiguration Http(
        this LoggerSinkConfiguration loggerConfiguration,
        Uri uri,
        IFormatProvider formatProvider = null)
    {
        if (loggerConfiguration == null) 
             throw new ArgumentNullException(nameof(loggerConfiguration));

        if (uri == null) 
             throw new ArgumentNullException(nameof(uri));

        return loggerConfiguration.Sink(new HttpSink(uri, formatProvider));
    }
}

SignalR扩展

同样的,编写 SignalRSink 继承于 ILogEventSink,通过内置的 Invoke 实现消息的发送。

public class SignalRSink : ILogEventSink
{
    private readonly Uri _uri;
    private readonly string _channel;
    private readonly IFormatProvider _formatProvider;
    private readonly HubConnection _hub;

    public SignalRSink(Uri uri, string channel, IFormatProvider formatProvider)
    {
        _uri = uri;
        _channel = channel;
        _formatProvider = formatProvider;
        _hub = new HubConnectionBuilder()
            .WithUrl(uri)
            .Build();
    }

    public async void Emit(LogEvent logEvent)
    {
        if (_hub.State == HubConnectionState.Disconnected)
        {
            await _hub.StartAsync();
        }

        var message = logEvent.RenderMessage(_formatProvider);

        var req = new LogMessage(logEvent.Level.ToString(), message);
        await _hub.InvokeAsync(_channel, req);
    }
}

本文实现最简单的推送效果,只需要配置推送通道即可,后续可根据需要添加 Group 或者 User,甚至是 Client 相关的配置。

public static class LoggerConfigurationSignalRExtensions
{
    public static LoggerConfiguration SignalR(
        this LoggerSinkConfiguration loggerConfiguration,
        Uri uri,
        string channel,
        IFormatProvider formatProvider = null)
    {
        if (loggerConfiguration == null) 
             throw new ArgumentNullException(nameof(loggerConfiguration));
        if (uri == null) 
             throw new ArgumentNullException(nameof(uri));

        return loggerConfiguration.Sink(new SignalRSink(uri, channel, formatProvider));
    }
}

额外的支持

提供最基础的日志消息封装类 LogMessage,可根据需要进行扩展。

class LogMessage
{ 
    public string Name { get; set; }
    public string Level { get; set; }
    public string Message { get; set; }

    public LogMessage (string level, string message)
    {
        Level = level;
        Message = message;
    }
}

接收

使用 asp.net core 接收,对外提供 SignalR 接口和 Post 接口。

public class LogHub : Hub
{
    private readonly ILogger<LogHub> _logger;

    public LogHub(ILogger<LogHub> logger)
    {
        _logger = logger;
    }
    public async Task SendLog(LogMessage log)
    {
        Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}    {level}  =>  {log.Message}");

        await Clients.All.SendAsync("log", log);
    }
}

不要忘记添加 SignalR 支持

builder.Services.AddSignalR();

app.MapHub<LogHub>("/log");
app.MapPost("/test", async (IHubContext<LogHub> hub, [FromBody] LogMessage log) =>
    {
        Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}    {level}  =>  {log.Message}");
        await hub.Clients.All.SendAsync("log", log.Message);
        return log;
    })
    .WithName("testpost");

测试

新建一个项目,添加引用支持,通过如下配置,启动即可看到应用日志被推送到服务

var builder = WebApplication.CreateBuilder(args);

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.Http(new Uri("http://localhost:5268/test"))
    .WriteTo.SignalR(new Uri("http://localhost:5268/log"), "SendLog")
    .CreateLogger();

builder.Host.UseSerilog();

https://static.scung.cn/d1e0ad13-dd95-43f0-b61c-4c883937bbfd.png

发布时间:2022-04-04
其他阅读

解决ssh登录后闲置一段时间断开

最新新买了一台服务器,使用 ssh 登录之后发现一段时间没有输入就会自动断开,这里记录一下解决方法。

查看原文

JSON是什么

在现代化 Web 应用开发中,广泛使用一种名为 JSON 的数据交换格式。JSON 是一种轻量级数据交换格式,在不同系统之间提供标准且高效的数据交换。

查看原文

GUID和UUID

GUID,英文全称为 Global Unique Identifier,意为全局唯一标识符,UUID,英文全称为 Universally Unique Identifier,意为通用唯一标识符,都具有唯一性,GUID也是微软对UUID是的实现。

查看原文

Angular中制作一个按钮组件

本文将会介绍如何在Angular中制作一个自定义按钮组件,直接在原生按钮上添加特性即可使用,还提供多种颜色方便切换。

查看原文

删除 Office 中毒瘤:Office Plus 插件

在某次更新之后,Office Plus 插件就会自动添加到 Word、Excel、PowerPoint 中,在我看来算是一个毒瘤,极大地拖慢了 Office 的运行,本文教大家怎么删除 Office Plus 这个毒瘤。

查看原文