14

I'm using the .NET Core 2.1 HostBuilder class to set up and run a GRPC server and am having trouble getting SeriLog properly configured so that it is used by the .NET Core logging pipeline as well as available (via dependency injection) elsewhere in my app.

class Program
{
    private static async Task Main(string[] args)
    {
        var hostBuilder = new HostBuilder()
            .ConfigureServices((hostContext, services) =>
            {
                services.AddSingleton<ILogger>(BuildLogger);

                // other services here 
            })
            .ConfigureLogging((hostContext, loggingBuilder) =>
                loggingBuilder.AddSerilog(dispose: true));

        await hostBuilder.RunConsoleAsync();
    }

    private static ILogger BuildLogger(IServiceProvider provider)
    {

        // create a (global) logger
        Log.Logger = new LoggerConfiguration() 
            ...
            .CreateLogger();

        return Log.Logger;
    }
}

The problem is that I need the call to loggingBuilder.AddSerilog() to use the singleton ILogger that was registered with the DI services configuration a few lines above.

I realize I could directly call BuildLogger() to get the ILogger instance and register that instance with the DI service configuration, but it seems like I shouldn't have to. What I'm looking for is a way, from within the .ConfigureLogging() method to access a ServiceProvider instance so I can get the registered ILogger maybe like

serviceProvider.GetRequiredService<ILogger>();

and pass that to the AddSerilog() call. Any ideas?

C. Augusto Proiete
  • 24,684
  • 2
  • 63
  • 91
Mr. T
  • 3,892
  • 3
  • 29
  • 48
  • I think it's best to use ILogger or the ILoggerFactory. I am not a big fan of ILoggerFactory but to me still better than injecting the ILogger manually. First price is ILogger. – Thulani Chivandikwa May 02 '19 at 20:55
  • ILogger is not registered by default while ILogger is and I think it is fine that way. Sadly it is not very obvious and people tend to find out the hard way. – Thulani Chivandikwa May 02 '19 at 21:02
  • @ThulaniChivandikwa `ILogger` is part of Microsoft.Extensions.Logging, not SeriLog. – Mr. T May 03 '19 at 14:22
  • my answer was specifically referring to Microsoft.Extensions.Logging. By default what can be resolved are the ILogger and ILoggerFactory implementations. I was pointing out that manually adding ILogger (again Microsoft.Extensions.Logging) is not a good idea. – Thulani Chivandikwa May 04 '19 at 17:17
  • @ThulaniChivandikwa and my question was specifically about `SeriLog.ILogger`... – Mr. T May 07 '19 at 19:57
  • ahh yes, my mistake made an incorrect assumption. – Thulani Chivandikwa May 08 '19 at 07:26

5 Answers5

12

Try the new package now available in Serilog - https://github.com/serilog/serilog-extensions-hosting.

  public static IHost BuildHost(string[] args) =>
    new HostBuilder()
        .ConfigureServices(services => services.AddSingleton<IHostedService, PrintTimeService>())
        .UseSerilog() // <- Add this line
        .Build();
merbla
  • 537
  • 6
  • 14
  • 3
    Thanks for the pointer - that package really is brand new. But what I'm looking for is a way to register the `ILogger` instance in `ConfigureServices()` and then access it from the DI framework in the `UseSerilog()` call. The default behavior relies on the static global `Log.Logger` instance which defeats the whole purpose of dependency injection. – Mr. T Jun 07 '18 at 14:05
9

What I'm looking for is a way, from within the .ConfigureLogging() method to access a ServiceProvider instance so I can get the registered ILogger

You can access the ServiceProvider from within the ConfigureLogging() method via ILoggingBuilder.Services.BuildServiceProvider(). Like so:

//...

private static async Task Main(string[] args)
{
    var hostBuilder = new HostBuilder()
        .ConfigureServices((hostContext, services) =>
        {
            services.AddSingleton<ILogger>(BuildLogger);

            // other services here 
        })
        .ConfigureLogging((hostContext, loggingBuilder) =>
            loggingBuilder.AddSerilog(
                loggingBuilder
                    .Services.BuildServiceProvider().GetRequiredService<ILogger>(),
                dispose: true));

    await hostBuilder.RunConsoleAsync();
}

...//
6

Here is a sample that shows how to do this including using appsettings.json for configuring serilog and how to get the logging using ILogger without having to manually inject it as the marked answer shows and how you can also you IOptions:

    public class Settings
{
    public string Sample { get; set; }
}

public class Service : IHostedService
{
    private readonly ILogger<Service> _logger;
    private Settings _settings;
    public Service(ILogger<Service> logger,
        Settings settings)
    {
        _logger = logger;
        _settings = settings;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        var host = new HostBuilder()
                   .ConfigureHostConfiguration(builder =>
                   {
                       builder.AddJsonFile("hostsettings.json", optional: true);
                   })
                   .ConfigureAppConfiguration((hostContext, builder) =>
                   {
                       builder.AddJsonFile("appsettings.json");
                       builder.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true);
                   })
                   .ConfigureLogging((hostContext, builder) =>
                   {
                       Log.Logger = new LoggerConfiguration()
                                    .ReadFrom.Configuration(hostContext.Configuration).CreateLogger();
                       builder.AddConfiguration(hostContext.Configuration.GetSection("Logging"));
                       builder.AddSerilog(dispose: true);
                   })
                   .ConfigureServices((hostContext, services) =>
                   {
                       var settings = hostContext.Configuration.GetSection("Configuration").Get<Settings>();
                       services.AddSingleton(settings);

                       services.AddHostedService<Service>();
                       services.AddLogging();
                       services.AddOptions();
                   })
                   .Build();

        using (host)
        {
            await host.StartAsync();
            await host.WaitForShutdownAsync();
        }
    }
}
Thulani Chivandikwa
  • 3,402
  • 30
  • 33
4

According to documentation on the repo for .NET Core 2.0+, call AddSerilog() on the provided loggingBuilder and make sure to configure Serilog first:

//...

private static async Task Main(string[] args) {

    Log.Logger = new LoggerConfiguration()
        //...
        .CreateLogger();

    var hostBuilder = new HostBuilder()
        .ConfigureServices((hostContext, services) => {        
            services.AddLogging(loggingBuilder =>
                loggingBuilder.AddSerilog(dispose: true));

            // other services here 
        });

    await hostBuilder.RunConsoleAsync();
}

//...
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • 1
    Thanks but I'm looking for a way to get the correct ILogger instance from the DI framework rather than having to use the global Log.Logger instance. – Mr. T Jun 07 '18 at 14:02
  • @Mr.T I think you are not understanding what the extension method does. It does in fact include the `ILogger` into the service collection so that it can be used for DI. That is its sole purpose. It integrates into the DI framework's log factory. They only mention the `Log.Logger` because it is the underlying provider and would still need to be configured first for everything to operate correctly. – Nkosi Jun 07 '18 at 18:46
  • @Mr.T for example refer to source code. https://github.com/serilog/serilog-extensions-hosting/blob/dev/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs – Nkosi Jun 07 '18 at 18:47
0

some changes in .Net6

here is my program.cs

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateLogger(); 
builder.Host.UseSerilog( Log.Logger);
var app = builder.Build();