What is the proper way to implement Serilog Contextual logger injection for Autofac in Clean Architecture?

Viewed 1747

To the point, I'm trying to implement Serilog Contextual logger injection for Autofac in my Clean Architecture SPA core application.

Project Structure:

enter image description here

enter image description here

In my project, Autofac is in Infrastructure CL project like below:

Reference code:

public static class ContainerSetup
{
    public static IServiceProvider InitializeWeb(Assembly webAssembly, IServiceCollection services) =>
        new AutofacServiceProvider(BaseAutofacInitialization(setupAction =>
        {
            setupAction.Populate(services);
            setupAction.RegisterAssemblyTypes(webAssembly).AsImplementedInterfaces();
        }));

    public static Autofac.IContainer BaseAutofacInitialization(Action<ContainerBuilder> setupAction = null)
    {
        var builder = new ContainerBuilder();

        var coreAssembly = Assembly.GetAssembly(typeof(BaseEntity));
        var infrastructureAssembly = Assembly.GetAssembly(typeof(P2PRepository));
        builder.RegisterAssemblyTypes(coreAssembly, infrastructureAssembly).AsImplementedInterfaces();

        setupAction?.Invoke(builder);
        return builder.Build();
    }
}

and register Autofac container in Startup class like below:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    services.AddDefaultIdentity<ApplicationUser>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
        
    return ContainerSetup.InitializeWeb(Assembly.GetExecutingAssembly(), services);
}

What I've tried yet?

I've successfully implement Serilog in my web project

public static int Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Debug()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
        .Enrich.FromLogContext()
        .WriteTo.Console()
        .WriteTo.File(
            @"D:\home\LogFiles\Application\myapp.txt",
            rollingInterval: RollingInterval.Day,
            outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
            )
        .CreateLogger();

        try
        {
            Log.Information("Starting web host");
            CreateWebHostBuilder(args).Build().Run();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Host terminated unexpectedly");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .UseSerilog();
}

after implementation above serilog and its dependencies, I've found that Serilog is giving implementation for Autofac also. So, I'm confused where should I used actual implementation of Serilog either in web project or Infrastructure Project? Should I revert back all code from my program.cs file and implement Serilog and install it dependencies in Infrastructure project by using below code?

First install Serilog Dependency:

Install-Package AutofacSerilogIntegration

Then when configuring the Autofac container, call RegisterLogger():

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

var builder = new ContainerBuilder();

var coreAssembly = Assembly.GetAssembly(typeof(BaseEntity));
var infrastructureAssembly = Assembly.GetAssembly(typeof(P2PRepository));
builder.RegisterAssemblyTypes(coreAssembly, infrastructureAssembly).AsImplementedInterfaces();

setupAction?.Invoke(builder);
builder.RegisterLogger(); // Here register serilog
return builder.Build();

Simple question is:

What is the proper way to implement SerilogAutofac integration in my Clean project?

2 Answers

What is the proper way to implement SerilogAutofac integration in my Clean project?

This is opinion based question meaning there is no single answer to this.

In my opinion, keep in mind that to implement Serilog Contextual logger injection for Autofac, you are doing two things:

  1. Adding Serilog
  2. Adding contextual loggers using Autofac

The code in Main is for the first purpose as per the instruction. And remember, this is purely Serilog to integrate with ASP.Net Core. So, by far, no Autofac involved. That, if you remove Autofac dependency from your projects, your application should still be using Serilog.

The code in ContainerSetup is where you introduced Autofac to ASP.NET Core, and from that on, ASP.NET Core knows to use Autofac as the DI container. If you want to specifically configure contextual loggers using Autofac, then ContainerSetup is the correct place to do so.

Having that said, to help decide where to configure the loggers, ask yourself a simple question:

Do you want Serilog regardless of Autofac?

If yes, then do not initialize Serilog loggers in your Autofac specific types.

Recommended way to wire-upAutofac with ASP.NET Core 2.1 and 2.2 is to use:

services.AddAutofac();

Code examples in official documentation state that method where you return IServiceProvider:

is the old, not recommended way, and is NOT SUPPORTED in ASP.NET Core 3.0+.

After Autofac is registered with services, you just have to use method in your Startup class to register dependences:

public void ConfigureContainer(ContainerBuilder builder)
{
    // Register your own things directly with Autofac, like:
    builder.RegisterModule(new MyApplicationModule());
}

Link to documentation is here.

Once Autofac is wired up correctly, it is matter of taste/opinion how you register Serilog, because dependency resolver will resolve all registrations correctly (Autofac and IServiceCollection). I prefer to keep registrations via built in IServiceCollection, wherever possible, because they are the standard and more familiar approach. So I would keep UseSerilog() version.

Update: on 2nd thought, you have to go for UseSerilog(), because that is what registers Serilog implementation of ASP.NET ILoggerFactory which should be used for logging. On the other side, builder.RegisterLogger(); only registers Serilog's ILogger with Autofac. It does not wire up anything to MVC.

Regarding your InitializeWeb method in CL project, I would be mindful when mixing ASP.NET Core and data layer dependencies in one DLL. If CL project can potentially evolve in reusable Nuget package for multiple projects, having hard-coded version of ASP.NET Core in dll can be a problem.

Related