扩展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
其他阅读

Linux中查看,添加,修改,删除用户和用户组

将用户分组是Linux系统中对用户进行管理及控制访问权限的一种手段。某个用户都属于某个用户组;一个组中可以有多个用户,一个用户也可以属于不同的组。当一个用户同时是多个组中的成员时,登录时所属的为默认组,而其他组称为附加组。本文将会介绍在 Linux 中查看,添加,修改,删除用户和用户组,注意:权限管理非常重要,可能一不小心就导致系统无法登录,请谨慎操作

查看原文

Web前端中实现一个拖动效果

在原生客户端开发中,窗口拖动是一个比较常用的操作。今天把这个效果在网页客户端中实现以下,文章记录一下效果。

查看原文

使用Cherry Studio搭配DeepSeek构建知识库

上篇文章,介绍了怎么在本地部署DeepSeek大模型服务,但是仅限于对话聊天,本文在之前的基础上,用DeepSeek搭配上Cherry Studio来构建个人知识库。

查看原文

Windows的刘海生成器

其实是很早之前在论坛讨论当时苹果率先推出了带刘海的 mac book pro 的时候,就花了几分钟做了一个模拟刘海的软件,可以给 Windows 系统加上刘海。

查看原文

C#完成一个应用内的消息中心

本文会讲解如何使用 C# 完成一个应用内部的消息中心(事件总线),事件驱动最大的好处就是可以很大程度的解耦合,松散结构。

查看原文