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

Serilog依赖注入统一添加自定义请求上下文属性问题咨询

解决方案:自动将IRequestContext属性注入Serilog日志

核心问题分析

你遇到的两个问题本质是同一个:没有让Serilog在全局范围内自动关联当前请求的IRequestContext实例属性,导致普通日志调用无法携带这些自定义字段。下面提供两种可行的落地方案:


方案一:请求入口层批量注入LogContext(推荐,轻量易维护)

利用Serilog的LogContext.PushProperties在请求生命周期起点批量注入属性,覆盖整个请求流程的所有日志输出。

1. API端实现(中间件)

创建请求上下文日志中间件,在请求开始时注入所有IRequestContext属性:

public class RequestContextLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestContextLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, IRequestContext requestContext)
    {
        // 批量推送属性到LogContext,using确保请求结束后自动清理
        using (LogContext.PushProperties(new[]
        {
            new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_REQUESTID, requestContext.RequestId),
            new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_USERID, requestContext.UserId),
            new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_CUSTOMERID, requestContext.CustomerId),
            new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_HOSTID, requestContext.HostId)
        }))
        {
            await _next(context);
        }
    }
}

注意注册顺序:在Program.cs中将此中间件放在异常处理、路由之前:

app.UseMiddleware<RequestContextLoggingMiddleware>();
app.UseExceptionHandler("/error");
app.UseRouting();

2. gRPC微服务端实现(拦截器)

在gRPC拦截器的请求处理逻辑外层包裹LogContext.PushProperties:

public override async Task<TResponse> AsyncUnaryCall<TRequest, TResponse>(
    TRequest request,
    ServerCallContext context,
    UnaryServerMethod<TRequest, TResponse> continuation)
{
    using (LogContext.PushProperties(new[]
    {
        new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_REQUESTID, _requestContext.RequestId),
        new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_USERID, _requestContext.UserId),
        new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_CUSTOMERID, _requestContext.CustomerId),
        new KeyValuePair<string, object>(SerilogConstants.CustomColumns.REQUESTCONTEXT_HOSTID, _requestContext.HostId)
    }))
    {
        try
        {
            return await continuation(request, context);
        }
        catch (Exception ex)
        {
            // 无需再手动ForContext,LogContext已包含所有请求属性
            _logger
                .ForContext(SerilogConstants.CustomColumns.ERROR_STACKTRACE, ex.StackTrace)
                .ForContext(SerilogConstants.CustomColumns.LOGS_SOURCE_TYPE, (int)LogsSourceType.MicroserviceExceptionHandling)
                .Error(ex, ex.Message);
            throw;
        }
    }
}

方案二:自定义依赖注入式Enricher(全局自动注入)

如果需要更底层的全局注入,可实现支持依赖注入的Serilog Enricher,自动从当前请求上下文获取IRequestContext。

1. 实现自定义Enricher

public class RequestContextEnricher : ILogEventEnricher
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public RequestContextEnricher(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null) return;

        var requestContext = httpContext.RequestServices.GetService<IRequestContext>();
        if (requestContext == null) return;

        // 添加自定义属性到日志事件
        logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
            SerilogConstants.CustomColumns.REQUESTCONTEXT_REQUESTID, requestContext.RequestId));
        logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
            SerilogConstants.CustomColumns.REQUESTCONTEXT_USERID, requestContext.UserId));
        logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
            SerilogConstants.CustomColumns.REQUESTCONTEXT_CUSTOMERID, requestContext.CustomerId));
        logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
            SerilogConstants.CustomColumns.REQUESTCONTEXT_HOSTID, requestContext.HostId));
    }
}

2. 配置Serilog与依赖注入

public static void AddSerilog(this IServiceCollection services, IConfiguration configuration)
{
    // 注册HttpContextAccessor用于获取当前请求上下文
    services.AddHttpContextAccessor();
    
    var logger = new LoggerConfiguration()
        .ReadFrom.Configuration(configuration)
        .Enrich.FromLogContext()
        .Enrich.With(new UtcTimestampEnricher())
        // 注入自定义Enricher
        .Enrich.With<RequestContextEnricher>()
        .CreateLogger();

    services.AddSingleton<ILogger>(logger);
    services.AddSerilog();
}

关键注意事项

  1. IRequestContext必须注册为Scoped:确保每个请求都有独立的实例,避免多请求属性冲突:
    services.AddScoped<IRequestContext, RequestContextImplementation>();
    
  2. 数据库Sink配置验证:如果控制台能看到自定义属性但数据库为空,需检查Serilog数据库Sink的列配置(以MSSqlServer为例):
    "Serilog": {
      "WriteTo": [
        {
          "Name": "MSSqlServer",
          "Args": {
            "connectionString": "YourConnectionString",
            "tableName": "Logs",
            "columnOptionsSection": {
              "additionalColumns": [
                { "ColumnName": "RequestId", "DataType": "UniqueIdentifier" },
                { "ColumnName": "UserId", "DataType": "BigInt", "AllowNull": true },
                { "ColumnName": "CustomerId", "DataType": "BigInt", "AllowNull": true },
                { "ColumnName": "HostId", "DataType": "BigInt" }
              ]
            }
          }
        }
      ]
    }
    

内容的提问来源于stack exchange,提问作者Alexander

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 11:42:57