2

According to this answer we cannot resolve a logger to log messages or exceptions from the ConfigureServices method in Startup. This is a problem for me because I have code in ConfigureServices which is written to throw exceptions if required settings are missing. Is there any other way that I can log that exception?

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62

2 Answers2

1

Exceptions inside ConfigureServices method can be thrown from two different sources:

  1. From the ConfigureServices method code itself.
  2. From Factory methods passed to the DI container.

Example:

services.AddScoped<IDependency>(sp =>
{
   // Any Exception from here
});

I pointed these two scenarios because I have found that they resolve using different approaches.

Scenario 1: Same approach used in this answer Answer1 (using that startup class, maybe adjusting how the exception is handled inside Configure method)

Scenario 2: This scenario is even harder because the try/catch used in scenario 1 doesn't catch exceptions thrown from Factory methods. But in those cases we have access to the ServiceProvider so we can handle exception inside each factory method with something like this.

services.AddScoped<IService>(sp =>
    {
        var httpContext = sp.GetService<IHttpContextAccessor>().HttpContext;
        var logger = sp.GetService<ILogger<IService>>();                
        try
        {
            throw new Exception("Text Exception");
        }
        catch (Exception ex)
        {
            // log
            logger.LogError(ex, ex.Message);
            // status error
            httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
            httpContext.Response.ContentType = "application/json";
            await httpContext.Response.WriteAsync(ex.Message);
            return null;
        }                
    });
Lazaro Gonzalez
  • 121
  • 1
  • 6
-1

Yes, there is a way to log an exception thrown from ConfigureServices. It's just a bit of hack. Maybe it's not a terrible hack. You decide.

ConfigureServices runs before Configure. We can inject a logger into Configure. So the hack is to hang on to the exception we throw in ConfigureServices, then log and rethrow it from the Configure method.

It's easiest to see it all at once, so here's a sample Startup:

public class Startup
{
    private Exception _configureServicesException;

    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        try
        {
            // Configure all of your services
        }
        catch (Exception ex)
        {
            _configureServicesException = ex;
        }
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
    {
        try
        {
            if (_configureServicesException != null)
            {
                ExceptionDispatchInfo.Capture(_configureServicesException).Throw();
            }

            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
        }
        catch (Exception ex)
        {
            logger.LogError(
                                ex,
                                "Exception thrown when starting application in {Environment} environment",
                                env.EnvironmentName);
            throw;
        }
    }
}

Here's what's been added:

  • private Exception _configureServicesException; is a field for storing an exception thrown from ConfigureServices.
  • If ConfigureServices throws an exception, we store it in that field and continue without rethrowing it.
  • We've added an ILogger argument to Configure so that the runtime will inject a logger. (More on that in a moment.)
  • When the runtime calls Configure, then if an exception was thrown from ConfigureServices, we'll rethrow it. In this example we'll catch, log, and rethrow any exception thrown in Configure, including the one that we captured from ConfigureServices.

All of this assumes that we've configured logging before this point. How to that is detailed in this Microsoft documentation - "Capture logs within ASP.NET Core startup code".

Reproducing the example from that page:

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .ConfigureLogging((context, builder) =>
                {
                    // Providing an instrumentation key is required if you're using the
                    // standalone Microsoft.Extensions.Logging.ApplicationInsights package,
                    // or when you need to capture logs during application startup, for example
                    // in the Program.cs or Startup.cs itself.
                    builder.AddApplicationInsights(
                        context.Configuration["APPINSIGHTS_CONNECTIONSTRING"]);

                    // Capture all log-level entries from Program
                    builder.AddFilter<ApplicationInsightsLoggerProvider>(
                        typeof(Program).FullName, LogLevel.Trace);

                    // Capture all log-level entries from Startup
                    builder.AddFilter<ApplicationInsightsLoggerProvider>(
                        typeof(Startup).FullName, LogLevel.Trace);
                });

...the key detail being that we're configuring logging so that the runtime can inject a logger when we call

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • 1
    Are you not able to capture exception on `Build` or host `Run*` and log via an external logger at the program level? This is an interesting approach/hack you have here. – Nkosi Jun 29 '21 at 20:23
  • I'd have to see exactly what you're referring to. Perhaps you could supply a different answer. I'm not thrilled to death with mine which is why I call it a hack. But it's easy to read and understand. There's exactly one point in the application where I need to log before logging has been configured. – Scott Hannen Jun 29 '21 at 20:44