带有 Serilog 的 Mastransit

Mastransit with Serilog

地铁版本:7.2.2 | Serilog版本:2.10.0 | Serilog.Asp.net核心版本4.1.0 | Asp.net核心5.0。
在配置总线之前,我在我的应用程序中设置了以下配置。

LogContext.ConfigureCurrentLogContext(loggerFactory);

虽然,根据这个document我不需要设置这个配置。

If you are using the new .AddMassTransit() configuration, combined with .AddBus(), then ILoggerFactory is automatically configured for you. In this case, the statement above is not required.

现在, 我的 Serilog 配置:

"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} - {ApplicationName} - {SourceContext} - {Level:u3} - {CorrelationId} - {UserId} - {ClientVersion} => {Message:lj}{NewLine}{Exception}",

在我的 Http 服务中使用此模板,我看到了日志,例如:

2021-12-29 12:35:36.096 - xxxx- Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker - INF - fcf97692-7af4-492c-9d1b-cf7456d1d371(corrId)- 11111(userId)- 1.2.1(ClientVer) , etc

但是当登录消费者时,没有记录我的 Serilog 属性(CorrelationId、UserId、ClientVersion 等)。

2021-12-29 12:35:36.096 - xxxx- Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker - INF - - - , etc

我需要其他配置来解决这个问题吗?
当向消费者抛出异常时,我还想推送我的 Serilog 属性。此时它不是日志。如您所见:

2022-01-01 13:07:24.736 - xxx - MassTransit.ReceiveTransport - ERR - - - => R-FAULT "rabbitmq://localhost/xxx" "c8100000-568d-0050-fe74-08d9cd0a4d36" xxxx


样本:
我在 MassTransit 发布过滤器中推送了我的属性:

public class IntegrationEventPublishFilter<T> : IFilter<PublishContext<T>> where T : class
{
    private readonly ICurrentRequest _currentRequest;
    private readonly ICurrentUser _currentUser;


    private readonly ILogger _logger;

    public IntegrationEventPublishFilter(ICurrentRequest currentRequest, ILogger<IntegrationEventPublishFilter<T>> logger,
        ICurrentUser currentUser)
    {
        _currentRequest = currentRequest;
        _logger = logger;
        _currentUser = currentUser;
    }


    public Task Send(PublishContext<T> context, IPipe<PublishContext<T>> next)
    {

        LogContext.PushProperty("UserId", _currentUser.UserId);
        LogContext.PushProperty("ClientVersion", _currentRequest.ClientVersion);
        LogContext.PushProperty("CorrelationId", _currentRequest.ReuqestId);
        return next.Send(context);
    }

    public void Probe(ProbeContext context)
    {
    }
}

Serilog 设置 Json 文件:

{  "Serilog": {
"Using": [
  "Serilog.Sinks.Async"
],
"MinimumLevel": {
  "Default": "Information",
},
"Properties": {
  "ApplicationName": "xxx
},
"WriteTo": [
  {
    "Name": "Async",
    "Args": {
      "configure": [
        {
          "Name": "File",
          "Args": {
            "path": "..\..\Logs\log.log",
            "rollingInterval": "Hour",
            "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} - {ApplicationName} - {SourceContext} - {Level:u3} - {CorrelationId} - {UserId} - {ClientVersion} => {Message:lj}{NewLine}{Exception}",
          }
        }

      ]
    }
  },
  {
    "Name": "Debug"
  }
]  }}

程序文件:

   public static IHostBuilder CreateHostBuilder(string[] args)
    {
        IConfigurationRoot configurationRoot = null;

        return Host.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((HostBuilderContext context, IConfigurationBuilder builder) =>
            {
                var environment = context.HostingEnvironment;
                var environmentName = environment.IsProduction() ? "" : $".{environment.EnvironmentName}";

                builder.Sources.Clear();
                builder.SetBasePath(Environment.CurrentDirectory)
                    .AddJsonFile($"appsettings{environmentName}.json")
                    .AddJsonFile($"serilogsettings{environmentName}.json")
                    
                configurationRoot = builder.Build();
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>()
                    .UseSerilog((hostingContext, loggerConfiguration) =>
                    {
                        loggerConfiguration
                             .Enrich.FromLogContext()
                             .Enrich.With<BaseExceptionItemsEnricher>()
                             .ReadFrom.Configuration(configurationRoot, "Serilog")
                    });
            });
    }

Serilog 丰富过滤器(在启动时注册并在 http 请求中正确工作。):

public class LogEnrichmentFilter : IActionFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

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

    public void OnActionExecuting(ActionExecutingContext context)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        httpContext.Request.Headers.TryGetValue("Request-Id", out StringValues requestIds);
        var userId = httpContext.User.Claims.FirstOrDefault(x => x.Type == "UserId")?.Value;
        httpContext.Request.Headers.TryGetValue("X-Client-Version", out StringValues clientVersions);

        LogContext.PushProperty("UserId", userId);
        LogContext.PushProperty("CorrelationId", requestIds.FirstOrDefault());
        LogContext.PushProperty("ClientVersion", clientVersions.FirstOrDefault());
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

}

我的消费者:

public class MyConsumer : 
   IConsumer<myEvent>
{

    private readonly ILogger _logger;

    public MyConsumer (
        ILogger<MyConsumer> logger)
    {
      
        _logger = logger;
    }

    public async Task Consume(ConsumeContext<MyEvent> context)
    {
        

        _logger.LogInformation("LOGGING TEST"); } } }
         

记录结果:

2021-12-29 12:35:38.183 - xxx - xxx.MyConsumer - INF -  -  -  => LOGGING TEST

如您所见,不要记录我推送的属性(correlationId、userId 等)。

最后,我解决了这个问题。 我添加了两个过滤器:PublishContext 过滤器和 ConsumeContext 过滤器。

在 PublishContext 过滤器中,我将 Serilog 属性设置为 MT headers:

 public class IntegrationEventPublishFilter<T> : IFilter<PublishContext<T>> where T : class
{
    private readonly ICurrentRequest _currentRequest;
    private readonly ICurrentUser _currentUser;

    public IntegrationEventPublishFilter(ICurrentRequest currentRequest, ICurrentUser currentUser)
    {
        _currentRequest = currentRequest;
        _currentUser = currentUser;
    }

    public Task Send(PublishContext<T> context, IPipe<PublishContext<T>> next)
    {

        context.Headers.Set("Language", _currentRequest.Language);
        context.Headers.Set("UId", _currentUser.UserId);
        context.Headers.Set("CorrelationId", _currentRequest.ReuqestId);
        context.Headers.Set("ClientVersion", _currentRequest.ClientVersion);

        return next.Send(context);
    }

    public void Probe(ProbeContext context)
    {
    }
}

并且,在 ConsumeContext 过滤器中,我得到了 MT headers 并推送到 Serilog LogContext:

    public class IntegrationEventConsumeFilter<T> : IFilter<ConsumeContext<T>> where T : class
{
    public Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
    {

        var acceptLanguage = context.Headers.Get<string>("Language");
        CultureInfo.CurrentUICulture = string.IsNullOrWhiteSpace(acceptLanguage) ?
            new CultureInfo("fa-IR") : new CultureInfo(acceptLanguage);


        context.Headers.TryGetHeader("UId", out object userId);
        context.Headers.TryGetHeader("ClientVersion", out object clientVersion);
        context.Headers.TryGetHeader("CorrelationId", out object correlationId);

        LogContext.PushProperty("UId", userId);
        LogContext.PushProperty("ClientVersion", clientVersion);
        LogContext.PushProperty("CorrelationId", correlationId);

        return next.Send(context);
    }

    public void Probe(ProbeContext context)
    {
    }
}

}