You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 18:47:48