Serilog and .NET Core 2.1 HostBuilder Configuration
Asked Answered
C

5

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?

Champignon answered 6/6, 2018 at 20:1 Comment(6)
I think it's best to use ILogger<T> 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<T>.Kerakerala
ILogger is not registered by default while ILogger<T> is and I think it is fine that way. Sadly it is not very obvious and people tend to find out the hard way.Kerakerala
@ThulaniChivandikwa ILogger<T> is part of Microsoft.Extensions.Logging, not SeriLog.Champignon
my answer was specifically referring to Microsoft.Extensions.Logging. By default what can be resolved are the ILogger<T> and ILoggerFactory implementations. I was pointing out that manually adding ILogger (again Microsoft.Extensions.Logging) is not a good idea.Kerakerala
@ThulaniChivandikwa and my question was specifically about SeriLog.ILogger...Champignon
ahh yes, my mistake made an incorrect assumption.Kerakerala
K
10

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();
}

...//
Kaminsky answered 24/6, 2018 at 18:33 Comment(0)
R
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();
Rainband answered 6/6, 2018 at 22:18 Comment(1)
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.Champignon
K
10

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();
}

...//
Kaminsky answered 24/6, 2018 at 18:33 Comment(0)
K
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();
        }
    }
}
Kerakerala answered 2/5, 2019 at 20:51 Comment(1)
This one worked best for my use case with a new Worker service and Serilog, Thank youPhotoflash
C
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();
}

//...
Cuckoo answered 6/6, 2018 at 20:19 Comment(3)
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.Champignon
@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.Cuckoo
@Mr.T for example refer to source code. github.com/serilog/serilog-extensions-hosting/blob/dev/src/…Cuckoo
C
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();
Caius answered 10/12, 2021 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.