使用Serilog记录MVC属性

译者: Akini Xu

原文: Logging MVC properties with Serilog.AspNetCore

作者: Andrew Lock

此文是在ASP.NET Core 3.0中使用Serilog第3篇:

  1. 使用Serilog减少日志的详细程度
  2. 使用Serilog记录路由的端点
  3. 使用Serilog记录MVC属性
  4. 在Serilog日志中排除健康检查日志

在本系列的上一篇中,我介绍了如何配置Serilog的RequestLogging中间件,来向Serilog的摘要日志中添加额外属性。 这些属性可以从HttpContext中获得,直接被中间件使用。

有一些属性(MVC特有的功能,例如Action名称,RazorPages处理程序名称或ModelValidationState等)仅在MVC上下文中可用,因此Serilog的中间件不能直接访问。

在本文中,我将介绍如何通过创建Action/Page的过滤器来记录这些属性,以方便中间件生成日志时使用。

Serilog的作者Nicholas Blumhardt在一篇文章中解决了这个问题。 他在示例中创建了一个attribute,并在actions/controllers中使用。 本文并没有采用这种方式,我想实现一种更通用的解决方案。 另外还可以查看他有关ASP.NET Core 3.0的Serilog相关文章

记录MVC中的额外信息

ASP.NET Core中有很多功能被封装在MVC“基础架构”之内。 目前,端点路由这个功能已经被迁移到.NET Core框架中实现了。 ASP.NET Core团队一直在努力将更多的MVC特有功能(例如模型绑定或操作结果)从MVC内部移出,然后迁移到核心框架中。 有关更多信息,请参见Ryan Nowak在NDC上对Houdini项目的讨论

就目前情况而言,MVC内部仍然有一些信息,是不太容易从外部获取的。 当使用Serilog的RequestLogging中间件时,以下信息就不太容易获取:

  • HandlerName (OnGet)
  • ActionId (1fbc88fa-42db-424f-b32b-c2d0994463f1)
  • ActionName (MyController.SomeApiMethod (MyTestApp))
  • RouteData ({action = "SomeApiMethod", controller = "My", page = ""})
  • ValidationState (True/False)

上一篇文章中,我介绍了如何使用RequestLogging中间件将额外附加信息写入Serilog的请求日志。 这仅适用于HttpContext中可访问的属性。 在这篇文章中,我将介绍如何在action filter中使用IDiagnosticContext将MVC特有的属性也添加到日志中。 还将介绍如何使用page filter添加RazorPages特有的属性(例如HandlerName)。

使用Action过滤器记录MVC特有属性

过滤器相当于MVC框架中的一个微型的中间件管道。MVC中有多种类型的过滤器,它们会在过滤器管道的不同位置运行(有关MVC过滤器更多详细信息,请参见此文章)。 在本文中,我们将使用最常见的过滤器之一,即Action filter

Action过滤器会在MVCAction方法执行前和执行后运行。 这个过滤器可以访问到许多MVC特有的属性,例如将要执行的Action及执行它的参数。

下面的Action过滤器实现了IActionFilter接口。 当Action的方法被执行之前,会先调用此过滤器的OnActionExecuting方法,在此方法中获取MVC特有的属性,添加到IDiagnosticContext中。

public class SerilogLoggingActionFilter : IActionFilter
{
private readonly IDiagnosticContext _diagnosticContext;
public SerilogLoggingActionFilter(IDiagnosticContext diagnosticContext)
{
_diagnosticContext = diagnosticContext;
}

public void OnActionExecuting(ActionExecutingContext context)
{
_diagnosticContext.Set("RouteData", context.ActionDescriptor.RouteValues);
_diagnosticContext.Set("ActionName", context.ActionDescriptor.DisplayName);
_diagnosticContext.Set("ActionId", context.ActionDescriptor.Id);
_diagnosticContext.Set("ValidationState", context.ModelState.IsValid);
}

// Required by the interface
public void OnActionExecuted(ActionExecutedContext context){}
}

Startup.ConfigureServices()中注入MVC服务时,同时注入全局范围过滤器:

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(opts =>
{
opts.Filters.Add<SerilogLoggingPageFilter>();
});
// ... other service registration
}

无论您使用的是AddControllersAddControllersWithViewsAddMvc还是AddMvcCore,都可以以相同的方式注入全局过滤器。

完成此配置后,再次访问MVC的Controller,则会在Serilog请求日志消息中看到额外信息(ActionNameActionIdRouteDataValidationState):

您可以使用此方式,添加您所需的任何其他信息添加到日志中。 注意:不要记录敏感信息或个人身份信息

Nicholas Blumhardt的文章中建议,Action过滤器从ActionFilterAttribute派生的,方便其用作于controlleraction。这样做意味着,必须使用IServiceProvider来获取单例的IDiagnosticContext对象。

上面的方法改为使用构造函数注入,因此不能当Attribute使用。 另外,生命周期是scoped,非singleton,因此每次请求都会创建一个新实例。

如果要记录MVC过滤器管道中其他位置的相关属性,可以采用类似的方式实现其它过滤器,例如Resource filterResult filterAuthorization filter

使用Page过滤器记录RazorPages属性

上面的IActionFilter只能在MVC controllerAPI controller上运行,但不能在RazorPages上运行。 如果要记录为指定Razor页面对应的HandlerName,则需要创建一个自定义IPageFilter

Page filters类似于Action filters,但只仅适用于Razor Pages 。 下面的示例从PageHandlerSelectedContext获取HandlerName。最后调用IDiagnosticContext .Set()来记录属性。

public class SerilogLoggingPageFilter : IPageFilter
{
private readonly IDiagnosticContext _diagnosticContext;
public SerilogLoggingPageFilter(IDiagnosticContext diagnosticContext)
{
_diagnosticContext = diagnosticContext;
}

public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
var name = context.HandlerMethod?.Name ?? context.HandlerMethod?.MethodInfo.Name;
if (name != null)
{
_diagnosticContext.Set("RazorPageHandler", name);
}
}

// Required by the interface
public void OnPageHandlerExecuted(PageHandlerExecutedContext context){}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context) {}
}

请注意,之前编写的IActionFilter不会在Razor Pages上运行,因此,如果也想为RazorPages记录诸如RouteDataValidationState之类的其他详细信息,那么也需要在此处添加那些代码。 context属性包含大多数需要的属性,例如ModelStateActionDescriptor

接下来,您需要在Startup.ConfigureServices()方法中注册Page filters

public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore(opts =>
{
opts.Filters.Add<SerilogLoggingPageFilter>();
});
services.AddRazorPages();
}

添加过滤器后,对Razor Pages的请求额外属性将会添加到Serilog请求日志中。 请参见下图中的RazorPageHandler属性:

总结

默认情况下,当用Serilog的请求日志记录中间件替换ASP.NET Core基础日志组件时,会丢失一些信息(与开发环境的默认配置相比)。 在本文中,我介绍了如何自定义Serilog的RequestLoggingOptions来添加MVC特有的其他属性。

需要添加MVC相关的属性到Serilog请求日志中,创建IActionFilter并使用IDiagnosticContext.Set()添加属性。

需要添加Razor页面相关的属性添加到Serilog请求日志中,创建IPageFilter并使用IDiagnosticContext添加属性。

文章作者: Akini Xu
文章链接: https://blog.ibestread.com/using-serilog-aspnetcore-in-asp-net-core-3-logging-mvc-propertis-with-serilog/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 嘉阅
支付宝打赏
微信打赏