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