serilog / serilog-aspnetcore

Serilog integration for ASP.NET Core
Apache License 2.0
1.29k stars 203 forks source link

How to use scoped service in UseSerilog #355

Closed TheBr00m closed 5 months ago

TheBr00m commented 6 months ago

I'm struggling resolving a scoped service for Serilog configuration within an ASP.NET Core 8 application. In the example below the AddHangfire configuration works fine with the same service, probably because it runs when it is required and not during startup. UseSerilog runs immediately during startup and it seems the services are not configured/built yet. There is no obvious/visible error but it does not work either. Without using the service, the configuration worked perfectly.

It might be related to #84 but none of the related issues helped me figure it out. I cannot get the service directly from the service collection since the service is scoped.

The service simply provides values used for configuration. There might be alternative routes to resolve the values and use them for Serilog configuration. Please note that secrets are not to be stored in configuration files and the like, hence the custom service.

Program.cs:

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<ISecretManager, SecretManager>();

builder.Host.UseSerilog((ctx, sp, config) => {
  using var scope = sp.CreateScope();
  var secretManager = scope.ServiceProvider.GetRequiredService<ISecretManager>();
  var secret = secretManager.Get("secretName");
  // use secret in configuration
  (...)
}, preserveStaticLogger: true);

builder.Services.AddHangfire((sp, config) => {
  using var scope = sp.CreateScope();
  var secretManager = scope.ServiceProvider.GetRequiredService<ISecretManager>();
  var secret = secretManager.Get("secretName");
  // use secret in configuration
  (...)
});

(...)

Happy Holidays!


Update

For now I have created a new service collection for the sole purpose of providing services for the startup phase. The examples show only one (scoped) service. If that would be the case I could simply new up the service without dependency injection and dispose it after start-up. In fact the service depends on other services through dependency injection.

Would this be a valid use case for the BuildServiceProvider method? Usage is discouraged because multiple copies of singleton services may be created. Does it matter for the short lifetime of the service provider?

Besides the overhead during start-up, does it have any implications on the application (services)?

var startUpServices = new ServiceCollection();
startUpServices.AddScoped<ISecretManager, SecretManager>();
using var startUpServiceProvider = startUpServices.BuildServiceProvider();
var startUpSecretManager = startUpServiceProvider.GetRequiredService<ISecretManager>();
var secret = startUpSecretManager.Get("secretName");
nblumhardt commented 6 months ago

Hi! I think this is an IoC usage challenge rather than something we can address here - if your sink requires a scoped service you'll need to figure out how to wire that up through your container, perhaps using something like injected factories, or the kind of approach you're discussing in the update. Out of scope for this repo/project, though. Good luck with it all the same!