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(); }
关键注意事项
IRequestContext必须注册为Scoped:确保每个请求都有独立的实例,避免多请求属性冲突:services.AddScoped<IRequestContext, RequestContextImplementation>();- 数据库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
相关产品推荐
相关产品推荐

