如何在使用 .NET 6 的新的最小托管模型的集成测试中使 Serilog 保持沉默

问题描述 投票:0回答:1

我有一个 .NET 6 Web API 项目,其中包含一些 API 端点的现有集成测试。该项目使用 Serilog 进行日志记录,到目前为止一切都很好。

我将代码迁移到新的最小托管模型,并在此过程中删除了 Startup 类。我修复了集成测试以使用新模型,到目前为止一切都在运行。我遇到的唯一问题是,集成测试现在垃圾邮件日志语句。

对于 Serilog,我有两阶段设置,这就是 Program.cs 的样子:

public partial class Program
{
  public static string ApplicationVersion => typeof(Program).Assembly
                                              .GetCustomAttribute<AssemblyInformationalVersionAttribute>()
                                              .InformationalVersion;

  /// <summary>
  /// Hack to prevent duplicate logger initialization when integration tests run in parallel.
  /// </summary>
  public static bool IsIntegrationTestRun = false;

  public static int Main(string[] args)
  {
    var env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production";

    if (!IsIntegrationTestRun)
    {
      // extra logger only for app startup
      Log.Logger = new LoggerConfiguration()
        .Enrich.FromLogContext()
        .WriteTo.Console()
        .CreateBootstrapLogger();
    }

    try
    {
      Log.Information("Starting <my application> v{version} in env {env}.", ApplicationVersion, env);

      var builder = WebApplication.CreateBuilder(args);

      builder.Configuration.AddJsonFile("appsettings.Local.json", true, true);

      // Actual logger for dependency injection
      builder.Host.UseSerilog((ctx, lc) =>
      {
        lc.ReadFrom.Configuration(ctx.Configuration);
      });

      // ...
      
      var app = builder.Build();

      // ...

      using (IServiceScope scope = app.Services.CreateScope())
      {
        var dataContext = scope.ServiceProvider.GetRequiredService<DataContext>();
        dataContext.Database.Migrate();
      }

      app.UseSerilogRequestLogging(c =>
      {
        c.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
        {
          diagnosticContext.Set("Host", httpContext.Request.Host.ToString());
          diagnosticContext.Set("UserAgent", httpContext.Request.Headers["User-Agent"]);
        };
        c.GetLevel = LogLevelHelper.GetRequestLevel;
      });

      // ...

      app.Run();

      return 0;
    }
    catch (Exception ex)
    {
      Log.Fatal(ex, "Host terminated unexpectedly.");

      return 1;
    }
    finally
    {
      Log.CloseAndFlush();
    }
  }
}

这是我的 WebApplicationFactory:

[CollectionDefinition("WebApplicationFactory")]
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
  where TStartup : class
{
  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
    // Somewhat hacky but prevents duplicate logger initialization when integration tests run in parallel.
    Program.IsIntegrationTestRun = true;

    builder.ConfigureAppConfiguration((context, builder) =>
    {
      // Load custom appsettings for Test
      builder.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.Test.json"));

      // optional load personal settings included in gitignore
      builder.AddJsonFile(Path.Combine(Directory.GetCurrentDirectory(), "appsettings.LocalTest.json"), true);
      builder.AddEnvironmentVariables();
    });
    
    // builder.ConfigureLogging(lb => lb.ClearProviders());
    Log.Logger = new LoggerConfiguration().MinimumLevel.Fatal().CreateLogger();
    
    // ...
  }
}

这样使用:

[Collection("WebApplicationFactory")]
public class SomeTests : IClassFixture<CustomWebApplicationFactory<Program>>
{
  private readonly CustomWebApplicationFactory<Program> _factory;

  public SomeTests(CustomWebApplicationFactory<Program> factory)
  {
    _factory = factory;
  }

  [Fact]
  public async Task Get_Some_ReturnsSomething()
  {
    // setup ...

    HttpClient client = _factory.CreateClient();
    client.DefaultRequestHeaders.Add("Authorization", RequestHelper.GetBearerAuthenticationHeaderValue(user));
    RequestHelper.AddStrangeHeader(client, user.StrangeKey);
    HttpResponseMessage response = await client.GetAsync("/api/some");

    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    var res = await RequestHelper.DeserializeResponse<List<SomeModel>>(response);
    Assert.Equal(SomeCount, res.Count);
  }
}

正如你所看到的,我已经扩展了 appsettings.json 模式,以使用本地 gitignored 文件进行本地开发(以防止存储库中的机密)和一个额外的

appsettings.Test.json
(另一个 git 忽略
appsettings.LocalTest.json
以及用于测试的额外设置)就像不同的数据库连接)。

当我运行集成测试控制台时,控制台中充斥着日志语句。奇怪的是,似乎并没有记录所有内容,例如我看不到任何请求日志。但我可以多次看到数据库迁移的日志,如下所示:

[09:57:38 INF  Microsoft.EntityFrameworkCore.Migrations] Applying migration '20210224073743_InitialSchema'

或者这个

[09:57:40 DBG lJty8ESu24x-MY6n4EYr Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler] Successfully validated the token.
.

我尝试了很多方法,例如将最小日志级别设置为

Fatal
或直接用新的记录器替换Log.Logger。

应用程序本身使用注入的 ILogger 而不是静态 Log.Logger。谁能指导我如何解决这个问题或者我下一步可以尝试什么?

日志记录似乎尊重我的

appsettings.Test.json
文件中的设置,当我降低调试的最低级别时,我可以看到在测试运行中打印更多日志。但是为什么即使我将最低级别设置为“致命”,也会记录迁移消息?

c# asp.net-core integration-testing serilog .net-6.0
1个回答
7
投票

我想我已经成功做到了这一点。在您的

CustomWebApplicationFactory
中,将其放入您的
ConfigureWebHost
:

[CollectionDefinition("WebApplicationFactory")]
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
  where TStartup : class
{
  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
#pragma warning disable CS0618
       builder.UseSerilog((_, _) => { });
#pragma warning restore CS0618
       // ... other customizations
        
       base.ConfigureWebHost(builder);
  }
}

它会抱怨该方法已过时,但这对我有用,它停止调用我原来的 Serilog 配置,并且只是停止记录任何内容。 我相信如果您愿意的话也可以使用它来更改配置。

更新 2022-12-19:我刚刚找到了一种更干净的方法,即使没有 Serilog 也能工作:

[CollectionDefinition("WebApplicationFactory")]
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup>
  where TStartup : class
{
  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
       builder.ConfigureTestServices(services =>
       {
           services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
       });
       // ... other customizations

       base.ConfigureWebHost(builder);
  }
}

这让我所有的日志都安静了。

© www.soinside.com 2019 - 2024. All rights reserved.