ASP.NET Core之Serilog、SEQ、ILogger构建可视化日志

news/2024/10/3 17:19:02

一、前言

  在上一章节中使用NLog+SEQ+ILogger构建可视化日志,本章基于Serilog来构建可视化日志。Serilog是.NET中一款非常出色的结构化日志的日志库,其与NLog对比在前一章节进行了说明。Serilog项目不仅仅包含核心项目Seirlog,而且还提供了很多接收器Sinks(超过100个),这些接收器是通过插件的方式来实现将日志写入到各种终端、文件、邮件、数据库或日志服务器。

二、实践

  第一步、安装SEQ服务,并创建Serilog所需要的ApiKey。

   第二步:创建一个ASP.NET Core的应用,引入相关的包,如下所示。

<ItemGroup><PackageReference Include="Serilog" Version="4.0.0" /><PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /><PackageReference Include="Serilog.Enrichers.Environment" Version="3.0.0" /><PackageReference Include="Serilog.Expressions" Version="5.0.0" /><PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" /><PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" /><PackageReference Include="Serilog.Sinks.Seq" Version="8.0.0" />
</ItemGroup>

  上述安装包主要包括Serilog(基础包),Serilog.AspNetCore(ASP.NET Core应用包),Serilog.Enrichers.Environment(将系统环境信息为日志事件属性记录到日志),并且提供丰富的Enrichers记录详细的日志信息,Serilog.Sinks.Async,Serilog.Sinks.Console,Serilog.Sinks.Seq都是Serilog异步记录写入日志文件、写入控制台打印、异步写入Seq等接收器,Serilog提供丰富的接收器。

 

   第三步、完成上述操作,下一步在代码中实现日志的收集、存储、验证查询功能,日志包含一般打印日志、接口请求日志,所以要对ILogger使用一般日志,接口请求日志进行处理,对于一般打印的日志在上一章节介绍Serilog已经实践了(映入安装包、定义Log配置、注入Serilog的服务,然后在代码中使用ILogger记录日志),对于请求日志则要使用中间件对请求进行处理(定义一个过滤器对接口请求Action相关上下文处理、定义一个特性对这个过滤器在Action方法上标记)。

  1、注入Serilog服务,定义一个服务扩展类,减少全部代码堆积在Program中,类名称为ServiceCollectionExtensions,并且使用Serilog的Console日志、文件日志、Seq日志存储代码如下。

using Serilog;
using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes;namespace tqf.serilogSeq.Demo
{public static class ServiceCollectionExtensions{public static IServiceCollection AddConfigSerilog(this IServiceCollection services, IConfiguration configuration){var seqApiKey = configuration["seq:apiKey"].ToString();var seqServerUrl = configuration["seq:serverUrl"].ToString();Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("System", Serilog.Events.LogEventLevel.Debug).MinimumLevel.Override("Microsoft", LogEventLevel.Debug).MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Error).MinimumLevel.Override("Microsoft.AspNetCore.Cors.Infrastructure.CorsService", LogEventLevel.Error).MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Error).MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Error)// 全部日志写入到Console.WriteTo.Async(c=>c.Console(theme:AnsiConsoleTheme.Literate,outputTemplate: "[T] {Timestamp:yyyy-MM-dd HH:mm:ss,fff} {MachineName}{NewLine}[L] [{Level}]{NewLine}[C] [{SourceContext}] {Message:lj}{NewLine}{Exception}{NewLine}"))// Information日志写入到文件.WriteTo.Async(c => c.File(path: "Logs/Information/.txt",rollingInterval: RollingInterval.Day,fileSizeLimitBytes: 1L * 1024 * 1024 * 1024,retainedFileCountLimit: 31,outputTemplate: "[T] {Timestamp:yyyy-MM-dd HH:mm:ss,fff} {MachineName}{NewLine}[L] [{Level}]{NewLine}[C] [{SourceContext}] {Message:lj}{NewLine}{Exception}{NewLine}",restrictedToMinimumLevel: LogEventLevel.Information))// Debug日志写入到文件.WriteTo.Async(c => c.File(path: "Logs/Debug/.txt",rollingInterval: RollingInterval.Day,fileSizeLimitBytes: 1L * 1024 * 1024 * 1024,retainedFileCountLimit: 31,outputTemplate: "[T] {Timestamp:yyyy-MM-dd HH:mm:ss,fff} {MachineName}{NewLine}[L] [{Level}]{NewLine}[C] [{SourceContext}] {Message:lj}{NewLine}{Exception}{NewLine}",restrictedToMinimumLevel: LogEventLevel.Debug))// Warning日志写入到文件.WriteTo.Async(c => c.File(path: "Logs/Warning/.txt",rollingInterval: RollingInterval.Day,fileSizeLimitBytes: 1L * 1024 * 1024 * 1024,retainedFileCountLimit: 31,outputTemplate: "[T] {Timestamp:yyyy-MM-dd HH:mm:ss,fff} {MachineName}{NewLine}[L] [{Level}]{NewLine}[C] [{SourceContext}] {Message:lj}{NewLine}{Exception}{NewLine}",restrictedToMinimumLevel: LogEventLevel.Warning))// Error日志写入到文件.WriteTo.Async(c => c.File(path: "Logs/Error/.txt",rollingInterval: RollingInterval.Day,fileSizeLimitBytes: 1L * 1024 * 1024 * 1024,retainedFileCountLimit: 31,outputTemplate: "[T] {Timestamp:yyyy-MM-dd HH:mm:ss,fff} {MachineName}{NewLine}[L] [{Level}]{NewLine}[C] [{SourceContext}] {Message:lj}{NewLine}{Exception}{NewLine}",restrictedToMinimumLevel: LogEventLevel.Error))// 全部日志写入到Seq.WriteTo.Async(c=>c.Seq(apiKey:seqApiKey,serverUrl:seqServerUrl)).Enrich.FromLogContext().Enrich.WithMachineName()// 过滤请求:/health心跳.Filter.ByExcluding("RequestPath like '/health%'").CreateBootstrapLogger();services.AddSerilog();return services;}}
}

  2、启用中间件,定义一个ApplicationBuilder扩展类,减少中间件代码堆积在Program中,类名称ApplicationBuilderExtensions,代码如下。

using Serilog;
using Serilog.Events;
using System.Net;namespace tqf.serilogSeq.Demo
{/// <summary>/// ApplicationBuilder 扩展/// </summary>public static class ApplicationBuilderExtensions{/// <summary>///  添加使用Serilog请求日志/// </summary>/// <param name="app"></param>/// <returns></returns>public static IApplicationBuilder UseSerilogRequestLogging(this IApplicationBuilder app){//允许body重用app.Use(next => context =>{context.Request.EnableBuffering();return next(context);});// 添加使用Serilog记录请求日志app.UseSerilogRequestLogging(options =>{// 请求日志输出模板options.MessageTemplate = "\n[R] {RequestMethod}={_RequestPath} | Status={StatusCode} | Time={Elapsed}ms\n[R] Req={_RequestBody} | Res={_ResponseBody}";// 发出调试级别事件而不是默认事件,将请求日志记录到:Debug日志options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;// 将其他属性附加到请求完成事件,将请求属性附加可以在模板中使用options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>{diagnosticContext.Set("_RequestPath", WebUtility.UrlDecode(httpContext.Request.Path + httpContext.Request.QueryString));//请求bodyvar requestContent = "";var method = httpContext.Request.Method.ToLower();if (method == "post" || method == "put"){httpContext.Request.Body.Position = 0;var requestReader = new StreamReader(httpContext.Request.Body);requestContent = requestReader.ReadToEnd();}diagnosticContext.Set("_RequestBody", requestContent);diagnosticContext.Set("_Service", AppDomain.CurrentDomain.FriendlyName);};});return app;}}
}

  3、定义一个Filter类,对Action的请求进行过滤操作,类名称RequestAuditLogFilter,代码如下。

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Serilog;
using Newtonsoft.Json;namespace tqf.serilogSeq.Demo
{/// <summary>/// 请求过滤器/// </summary>public class RequestAuditLogFilter : IResultFilter{private readonly IDiagnosticContext _diagnosticContext;public RequestAuditLogFilter(IDiagnosticContext diagnosticContext) { _diagnosticContext = diagnosticContext; }public void OnResultExecuted(ResultExecutedContext context){var result = context.Result as ObjectResult;var resultJson = JsonConvert.SerializeObject(result?.Value);_diagnosticContext.Set("_ResponseBody", resultJson);}public void OnResultExecuting(ResultExecutingContext context) { }}
}

  4、定义一个Attribute,实现过滤器标记在Action方法上,代码如下。

using Microsoft.AspNetCore.Mvc;namespace tqf.serilogSeq.Demo
{/// <summary>/// 请求特性,标签要记录请求日志/// </summary>public class RequestAuditLogAttribute : TypeFilterAttribute{public RequestAuditLogAttribute() : base(typeof(RequestAuditLogFilter)) { }}}

  5、在Program中使用扩展的ServiceCollection与ApplicationBuilder来注入Serilog的服务,启用Serilog的中间件,代码如下。

using Microsoft.Extensions.Configuration;namespace tqf.serilogSeq.Demo
{public class Program{public static void Main(string[] args){var builder = WebApplication.CreateBuilder(args);// Add services to the container.
builder.Services.AddControllers();// 使用Serilog记录日志
            builder.Services.AddConfigSerilog(configuration: builder.Configuration);var app = builder.Build();// Configure the HTTP request pipeline.
app.UseHttpsRedirection();app.UseAuthorization();// 使用Serilog记录请求日志
            app.UseSerilogRequestLogging();app.MapControllers();app.Run();}}
}

  6、在业务代码中使用日志,代码如下,注意注入IDiagnosticContext实例,然后在Action添加定义的特性RequestAuditLog记录请求日志。

using Microsoft.AspNetCore.Mvc;
using Serilog;namespace tqf.serilogSeq.Demo.Controllers
{/// <summary>/// 控制器/// </summary>
    [ApiController][Route("[controller]")]public class WeatherForecastController : ControllerBase{private static readonly string[] Summaries = new[]{"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"};private readonly ILogger<WeatherForecastController> _logger;private readonly IDiagnosticContext _diagnosticContext;public WeatherForecastController(ILogger<WeatherForecastController> logger, IDiagnosticContext diagnosticContext){_logger = logger;_diagnosticContext = diagnosticContext;}[HttpGet("LogInformation")][RequestAuditLog]public string LogInformation(){return "ok";}[HttpGet("LogError")][RequestAuditLog]public string LogError(){int a = 10;int b = 0;int c = a / b;return "ok";}[HttpPost("LogRequest")][RequestAuditLog]public List<string> LogRequest([FromBody] List<string> input){for (int i = 0; i < 10; i++){input.Add(Guid.NewGuid().ToString());}return input;}[HttpGet][RequestAuditLog]public IEnumerable<WeatherForecast> Get(){return Enumerable.Range(1, 5).Select(index => new WeatherForecast{Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),TemperatureC = Random.Shared.Next(-20, 55),Summary = Summaries[Random.Shared.Next(Summaries.Length)]}).ToArray();}}
}

  7、在appsetting中配置上述的Seq的地址与Key信息。

{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"Seq": {"apiKey": "GgnrgKNNuwBsP1JHbPLb","serverUrl": "http://localhost:5341"},"AllowedHosts": "*"
}

  第四步、校验结果,运行程序,通过查看Console、文件、SEQ是否保存相关日志数据,及其数据格式项。

   如上所示,在三个类型的接收器都接受到相关的数据类型和格式信息,很好完成日志记录的功能。

三、总结

  通过上述的实践,Serilog+SEQ+ILogger,提供高可扩展、高灵活性的日志记录服务功能,并且通过上述的扩展类,过滤器、特性、配置项的方式,提供一个低代码入侵的思想,对于日志管理真正优化的实现了。

参考:https://www.cnblogs.com/heyangyi/p/18082599,https://www.cnblogs.com/RainFate/p/16869403.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/44543.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

【知识点】深度优先搜索 Depth First Search

深度优先搜索 Depth First Search去年发布的笔记,今年加以改编。 世界上只有两种人,一种是讨厌递归的人,另一种是讨厌递归后又重新爱上递归的人...搜索算法被广泛的应用在计算机领域中。搜索算法的本质就是通过暴力枚举以及模拟的方式来求得最终的答案。但普通的暴力枚举局限…

linux Typora修改Windows文件产生乱码(未解决)

问题如同所示,出现了乱码,而且不同于普通的乱码,英文字符之类的也没有保留下来(说明不是编码格式问题),反而出现了红圈中的那些本不存在的字符(搜了一下似乎和Oracle有关)。 未解决 我猜测是linux和windows的文件系统不同导致修改文件使得文件损坏。 只能说多做备份,幸…

LVGL库的bar控件

进度条(lv_bar) 概述 1.进度条对象(组件)有一个背景和一个指示器. 指示器的宽度根据进度条的当前值2.自动设置. 3.如果设置进度条的宽度小于其高度,就可以创建出垂直摆放的进度条 4.不仅可以设置结束,还可以设置进度条的起始值,从而改变指标的起始位置样式和零件 LV_PART_MAI…

CMake Tutorial (3.30-rc3版) 练习和点评

CMake Tutorial (3.30-rc3版) 练习和点评 CMake 官方文档提供了 CMake Tutorial, 目前最新版是 CMake-3.30-rc3, 有12个Step供用户练习。 CMake Tutorial 是从 CMake 3.16 版本开始能从官方网页找到, 并且每一版都有改进 Tutorial 内容。 作为有实际C/C++项目中大幅使用 CMa…

多线程-信号量

ManualResetEvent的用法初始化:创建一个ManualResetEvent实例,并设置其初始状态。通常,初始状态可以设置为false(表示事件尚未发生)或true(表示事件已经发生)。例如:ManualResetEvent mre = new ManualResetEvent(false);等待事件:在需要等待事件发生的线程中,调用Wa…

过滤器和拦截器的区别

一、拦截器和过滤器的区别 1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。 2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理…

typora快捷键配置

typora高亮快捷键配置 (typora的高亮没有默认快捷键, 需要自己添加) 1.激活高亮功能 偏好设置 → markdown → markdown扩展语法 → 勾选高亮 2.添加快捷键 (原教程可参见官网) 首先可将typra的语言显示换成English, 方便后续定义: 偏好设置 → 通用 → 语言 每个软件, 理论上每…

Elasticsearch 系列(七)- 在ASP.NET Core中使用高级客户端NEST来操作Elasticsearch

本章将和大家分享在ASP.NET Core中如何使用高级客户端NEST来操作我们的Elasticsearch。本章将和大家分享在ASP.NET Core中如何使用高级客户端NEST来操作我们的Elasticsearch。 NEST是一个高级别的Elasticsearch .NET客户端,它仍然非常接近原始Elasticsearch API的映射。所有的…