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

命令行打包.net项目

.net 日常开发中,我们接触最多的就是 Visual Studio ,它是微软为了 .net 平台专门打造的 IDE (集成开发环境),为整个 .net 平台开发带来了无与伦比的图形化体验,但是有时候,我们也会遇到需要通过命令行来生成 .net 项目的情况,本文会介绍几种命令行打包的姿势。

查看原文

C#本质论7.0

《C#本质论》是C#最权威、最值得尊重的参考书之一,作者为此付出了非凡的努力!Mark Michaelis的《Essential C#》系列多年来一直是畅销经典。

查看原文

Nginx重定向HTTP到HTTPS

HTTP协议以纯文本形式进行数据的交互,数据明文传输,容易被监听,窃取和伪造,HTTPS在HTTP的基础上,使用了TLS/SSL对通信过程进行加密,数据得到了有效的保护,就算被拦截到也无法获取信息,更没法实施中间人攻击。本文将会介绍如何在Nginx中配置HTTP重定向到HTTPS。

查看原文

未来设计趋势-Bento Layout\便当布局

前几天找网站设计的灵感的时候,发现一个介绍2024年网站设计趋势的视频,里面介绍了一个叫 Bento Layout 的趋势,今天就给大家分享一下什么是 Bento Layout,中文称为“便当布局”。

查看原文

记录中文名WPF应用无法启动

今年开春,突然就收到部分用户反馈软件无法启动的问题,沟通后远程查看发现应用刚启动就直接崩溃了,在Windows的事件查看器可以看到应用的崩溃日志,发现是 ucrtbase.dll 模块崩溃,错误代码 0x0000409

查看原文