如何在ASP.NET Core控制器请求范围内添加Serilog环境属性?
给ASP.NET Core请求范围的所有Serilog日志添加路由相关属性的正确做法
你之前的思路方向是对的,但确实没处理好LogContext的作用域问题——这也是很多人踩坑的点。先直接给你最可靠的两种解决方案,再解释为什么之前的写法有问题。
一、优先选择:用中间件实现全请求范围的属性注入
这是最推荐的方式,因为中间件能覆盖整个HTTP请求的生命周期(从请求进入到响应返回),而且能确保LogContext的属性被正确清理,不会泄漏到其他请求。
1. 编写中间件
public class SerilogRequestPropertyMiddleware { private readonly RequestDelegate _next; public SerilogRequestPropertyMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 从路由数据中获取目标值,这里对应你的"myvalue"路由参数 string? propValue = context.GetRouteValue("myvalue") as string; // 用using包裹PushProperty,请求结束时自动Dispose,确保属性被移除 using (LogContext.PushProperty("MyProperty", propValue ?? "N/A")) { // 传递请求到下一个中间件 await _next(context); } } } // 扩展方法,方便在Program.cs里注册 public static class SerilogRequestPropertyMiddlewareExtensions { public static IApplicationBuilder UseSerilogRequestProperty(this IApplicationBuilder app) { return app.UseMiddleware<SerilogRequestPropertyMiddleware>(); } }
2. 注册中间件并配置Serilog
在Program.cs里,一定要把中间件放在**UseRouting之后,UseEndpoints之前**,这样才能正确获取到路由数据;同时确保Serilog启用了FromLogContext enrichment:
var builder = WebApplication.CreateBuilder(args); // 配置Serilog,必须加上.Enrich.FromLogContext()才能读取LogContext的属性 builder.Host.UseSerilog((ctx, lc) => lc .ReadFrom.Configuration(ctx.Configuration) .Enrich.FromLogContext() ); var app = builder.Build(); // 注册中间件的位置很关键 app.UseRouting(); // 放在UseRouting之后,确保能拿到路由数据 app.UseSerilogRequestProperty(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.Run();
二、备选方案:用ActionFilter(仅覆盖控制器动作阶段)
如果你的属性只需要在控制器动作执行期间生效,也可以用ActionFilter,但必须正确处理PushProperty的释放:
public class SerilogPropertyActionFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 从Action参数中获取值 string? propValue = context.ActionArguments["myvalue"] as string; // 同样用using包裹,确保动作执行完后清理属性 using (LogContext.PushProperty("MyProperty", propValue ?? "N/A")) { // 执行后续的动作和过滤器 await next(); } } } // 在Program.cs里注册过滤器 builder.Services.AddControllers(options => { options.Filters.Add<SerilogPropertyActionFilter>(); });
这种方式的局限性是:中间件阶段的日志(比如请求入口的日志)不会包含这个属性,因为过滤器是在路由匹配完成后才执行的。
为什么你之前的写法有问题?
你之前在OnActionExecuting里直接调用LogContext.PushProperty,但没有处理返回的IDisposable:
- ASP.NET Core会复用线程,不Dispose的话,这个属性可能会被带到后续的请求中,导致日志污染
OnActionExecuting的执行时机晚于很多中间件,中间件阶段的日志不会包含这个属性- 一旦请求抛出异常,可能导致Dispose逻辑被跳过,进一步加剧属性泄漏
关键注意点
- 必须在Serilog配置中添加
.Enrich.FromLogContext(),否则LogContext里的属性根本不会被添加到日志事件中 - 永远要用
using包裹LogContext.PushProperty的结果,确保作用域结束后属性被清理 - 中间件的注册位置直接影响能否获取到路由数据,一定要放在
UseRouting之后
内容的提问来源于stack exchange,提问作者Will Dean
相关产品推荐
相关产品推荐

