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

一、前言

  在上一章节中使用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));

                    //请求body
                    var 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

posted @ 2024-06-17 00:24  tuqunfu  阅读(744)  评论(0)    收藏  举报