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

Js文件上传

文件上传是一个前端比较常见的功能,无论是以前的MVC客户端,还是现代化SPA客户端中,但万变不离其宗,其内核基本不变,本文就此讨论简单文件上传(以图片为例子)。

查看原文

本地部署DeepSeek大模型服务

之前的文章介绍了怎么对接DeepSeek的API实现自己的助手,但是依旧使用的DeepSeek官方服务器,在高峰期还是会出现超长延时的情况,本文介绍另一种思路,通过在本机上部署DeepSeek大模型服务来告别卡顿崩溃。

查看原文

git的一些技巧

git 是一个免费开源分布式版本控制系统,可以高效处理从小型到超大型项目内容管理,本文会介绍一些 git 使用的技巧。

查看原文

Blazor文件上传解决方案

Blazor 是由 Asp.Net Core 团队推出的一个Web前端SPA解决方案,其中包括了使用 WebAssembly 的 Blazor Wasm 和使用 SignalR 进行实时交互的 Blazor server。本篇文章中使用的是 Blazor Wasm 方案来验证上传文件的操作。

查看原文

记录Unity中的坑

Unity虽然使用C#来进行开发,但是Unity所使用的运行时和.net的原生运行时却又差别,这导致在.net中的某些代码块在Unity中运行会出现错误。

查看原文