26

We have common BL classes in a ASP.NET Core application that get in the ctor:

Microsoft.Extensions.Logging.ILogger<Foo>

In ASP.NET Core, the internal infrastructure of ASP.NET handles getting the ILogger via the LoggerFactory.

We now want to reuse these BL classes in a console application (for async jobs), how do we setup AutoFac and Serilog to inject Microsoft.Extensions.Logging.ILogger<T> in environment that LoggerFactory doesn't exists?

Tseng
  • 61,549
  • 15
  • 193
  • 205
gdoron
  • 147,333
  • 58
  • 291
  • 367
  • What do you mean by "Environment that LoggerFactory doesn't exist"? LoggerFactory is not part of ASP.NET Core its part of the Microsoft.Extensions.Logging package, see https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging/LoggerFactory.cs – Tseng Jan 01 '17 at 14:42

3 Answers3

60

Microsoft.Extensions.Logging (see source) is not part of ASP.NET Core and can run independently of it. All you need to do is to register the ILoggerFactory and ILogger<> interface.

The ILoggerFactory is used by Logger<T> to instantiate the actual logger.

When using the Logging extension in console applications, its recommended still to use the IServiceCollection, as this allows you to use the IServiceCollection extension methods to register all packages which support this pattern.

var services = new ServiceCollection();
services.AddLogging();

// Initialize Autofac
var builder = new ContainerBuilder();
// Use the Populate method to register services which were registered
// to IServiceCollection
builder.Populate(services);

// Build the final container
IContainer container = builder.Build();

This is the recommended approach, as you won't have to think in detail which classes need to registered for libraries which have Microsoft.Extensions.DependencyInjection integration support.

But of course you can also register it manually, but when a change happens to the Microsoft.Extensions.Logging library (new dependency added), you won't get it and first have to figure out or dig into the source code to find the error.

builder.RegisterType<LoggerFactory>()
    .As<ILoggerFactory>()
    .SingleInstance();
builder.RegisterGeneric(typeof(Logger<>))
    .As(typeof(ILogger<>))
    .SingleInstance();

All that remains is to register the logger types after the container has been built or before your application starts:

var loggerFactory = container.Resolve<ILoggerFactory>();
loggerFactory.AddConsole()
    .AddSerilog();

and in your services inject ILogger<MyService> as usual.

spottedmahn
  • 14,823
  • 13
  • 108
  • 178
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 12
    The generic logger needs to be registered using Autofac as such `builder.RegisterGeneric(typeof(Logger<>)).As(typeof(ILogger<>)).SingleInstance();` – Ruskin Aug 08 '17 at 23:47
  • 1
    This did not work for me until I added the line that Ruskin commented above. Thanks! – RyanOC Aug 21 '18 at 12:55
  • 1
    @RyanOC: Then you likely didn't call `services.AddLogging();` since thats the one which registers the `Logger`/`ILogger` instances – Tseng Aug 21 '18 at 12:57
  • @Tseng, you are correct. I was able to get it working with your answer alone after spending some more time on it. Thanks! – RyanOC Aug 21 '18 at 18:40
  • 1
    I edited the answer to have the correct code. Thank you @Ruskin for the correction. – KevM Aug 23 '18 at 12:47
  • How to set LoggerConfiguration? – kenazs Jun 23 '22 at 10:18
  • @Ruskin, why does the generic logger have to be registered with `SingleInstance()`? – Thomas Barnekow Aug 09 '22 at 11:53
  • @ThomasBarnekow Cause you also have singleton services. You can't inject scoped loggers into singleton services obviously. Transient would work though but then each service would get its own instance of it so extra allocations – Tseng Aug 09 '22 at 17:58
  • I've used this to AddSerilog: builder.RegisterType() .As() .OnActivating(args => args.Instance.AddSerilog()) .SingleInstance(); – Ricardo Rodrigues Jun 28 '23 at 09:23
4

I would recommend a slightly different approach if you want to register ir manually direct on Autofac:

private static void ConfigureLogging(ILoggingBuilder log)
{
    log.ClearProviders();
    log.SetMinimumLevel(LogLevel.Error);
    log.AddConsole();
}

private static void ConfigureContainer(ContainerBuilder builder)
{
    builder.Register(handler => LoggerFactory.Create(ConfigureLogging))
        .As<ILoggerFactory>()
        .SingleInstance()
        .AutoActivate();

    builder.RegisterGeneric(typeof(Logger<>))
        .As(typeof(ILogger<>))
        .SingleInstance();
    // other registrations
}

And this on you main startup code:

var containerBuilder = new ContainerBuilder();
ConfigureContainer(containerBuilder);

var container = containerBuilder.Build();
var serviceProvider = new AutofacServiceProvider(container);
// you can use either the built container or set the serviceProvider onto the library you are using.
Marcos Junior
  • 209
  • 2
  • 9
0

In Console app Main method

var builder = new ConfigurationBuilder()
             .SetBasePath(Directory.GetCurrentDirectory())
             .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
             .AddEnvironmentVariables();
        IConfigurationRoot configuration = builder.Build();

        var serviceProvider = new ServiceCollection()
                .AddDbContext<MyDbContext>(optionns => optionns.UseSqlServer(configuration.GetConnectionString("connectionString")))
                .AddSingleton(typeof(ILogger<>), typeof(Logger<>))
                .AddLogging() 
                .BuildServiceProvider();

        MyDbContext _context = serviceProvider.GetService<MyDbContext>();
        var _logger = serviceProvider.GetService<ILogger<YourClass>>();
Arun Solanki
  • 174
  • 11