serilog / serilog-extensions-hosting

Serilog logging for Microsoft.Extensions.Hosting
Apache License 2.0
139 stars 30 forks source link

UseSerilog Infinite loop: ReadFrom.Services + KinesisFirehoseSink #48

Open edmacdonald opened 3 years ago

edmacdonald commented 3 years ago

If you use the Two-stage initialization from here: https://github.com/serilog/serilog-aspnetcore#two-stage-initialization

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog((context, services, configuration) => configuration
                    .ReadFrom.Configuration(context.Configuration)
                    .ReadFrom.Services(services)
                    .Enrich.FromLogContext()
                    .WriteTo.Console())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

and configure a KinesisFirehoseSink like so...

public void ConfigureServices(IServiceCollection services)
{
    services.AddAWSService<IAmazonKinesisFirehose>();
    services.AddSingleton(new KinesisFirehoseSinkOptions("LoggingStream"));
    services.AddTransient<ILogEventSink, KinesisFirehoseSink>();
}

You end up in an infinite loop because the IAmazonKinesisFirehose factory is looking for an ILoggerFactory.

b-mcbride commented 2 years ago

I am experiencing a similar issue when using .AddAzureClients from the Microsoft.Extensions.Azure NuGet package. Did you figure out how to work around this?

edmacdonald commented 2 years ago

I just ditched the two-stage init. I came to the conclusion it's not that important to me and creates more issues than it solves. Initialization changes infrequently, errors outside development are rare, and when they do happen I make sure they fail spectacularly and if need be I can look at a file log on the host. I don't need them sent to kinesis.

IvaskevychYuriy commented 1 year ago

I believe I bump into the same issue - custom service that needs ILogger<T> dependency:

Sample

using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<FooService>();

builder.Host.UseSerilog((context, services, configuration) =>
{
    var service = services.GetRequiredService<FooService>();
});

builder.Build().Run();

public class FooService
{
    private readonly ILogger<FooService> _logger;

    public FooService(ILogger<FooService> logger)
        => _logger = logger;
}

Setup

NET 6, web application scaffolded by VS, the following packages added:

Issue

This gets stuck in an infinite loop like OP described. In me specific case thread dies after 252 iterations and the app never starts...

Shouldn't a bootstrap logger be used initially in such case - this would've solved the isssue I believe?

Addional info

The same exact thing happens when using pre-net6 configuration via GenericHost:

using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
    .UseSerilog((context, services, configuration) =>
    {
        var service =   services.GetRequiredService<FooService>();
    })
    .Build()
    .Run();

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<FooService>();
    }

    public void Configure(IApplicationBuilder app) { }
}

Workaround (kinda)

Well, this is far far from ideal, but I've come up with builing GenericHost first, resolving services from there and then building another GenericHost this time with .UseSerilog() and use built ServiceProvider there.

Note: this solution is for pre-net6 Program.cs but I could imagine a similar approach could be taken with WebApplicationBuilder Note: this causes a noticable performace hit on application startup, so use with caution

// ...

var serviceProvider = Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
    .Build()
    .Services;

Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>())
    .UseSerilog((context, _, configuration) =>
    {
        var service = serviceProvider.GetRequiredService<FooService>();
    })
    .Build()
    .Run();

// ...