Home > Software design >  Logger becomes disposed between Startup and ConfigureServices
Logger becomes disposed between Startup and ConfigureServices

Time:06-30

I've created a logger which should log anything during start of application run. And I wanted it to persist between Startup and ConfigureServices. I store it in the property similar to configuration

Here is the code


public Startup(IConfiguration configuration)
{
    Configuration = configuration;

    using var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Error);
        builder.AddEventLog(s =>
        {
            s.LogName = "MyLogName";
            s.SourceName = "MySource";
        });

        // add some other persisting logger
    });
    StartupLogger = loggerFactory.CreateLogger<Startup>();
}

public IConfiguration Configuration { get; }

public ILogger StartupLogger { get; } // Logger exists here when breakpoint is in ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
        
    StartupLogger.LogError("Failed to do something"); // <-- throws exception
}

This is the error I get. Looks like inner logger gets disposed in the process

Message: "An error occurred while writing to logger(s). (Cannot access a disposed object. Object name: 'EventLogInternal'.

Full stack

'StartupLogger.LogError("Failed to do something")' threw an exception of type 'System.AggregateException'
Data: {System.Collections.ListDictionaryInternal}
HResult: -2146233088
HelpLink: null
InnerException: {"Cannot access a disposed object.\r\nObject name: 'EventLogInternal'."}
InnerExceptions: Count = 1
Message: "An error occurred while writing to logger(s). (Cannot access a disposed object.\r\nObject name: 'EventLogInternal'.)"
Source: "Microsoft.Extensions.Logging"
StackTrace: " at Microsoft.Extensions.Logging.Logger.ThrowLoggingError(List`1 exceptions)\r\n at Microsoft.Extensions.Logging.Logger.Log[TState](LogLevel logLevel, EventId eventId, TState state, Exception exception, Func`3 formatter)\r\n at Microsoft.Extensions.Logging.Logger`1.Microsoft.Extensions.Logging.ILogger.Log[TState](LogLevel logLevel, EventId eventId, TState state, Exception exception, Func`3 formatter)\r\n at Microsoft.Extensions.Logging.LoggerExtensions.Log(ILogger logger, LogLevel logLevel, EventId eventId, Exception exception, String message, Object[] args)\r\n at Microsoft.Extensions.Logging.LoggerExtensions.LogError(ILogger logger, String message, Object[] args)"
TargetSite: {Void ThrowLoggingError(System.Collections.Generic.List`1[System.Exception])}

Surely I have a wrong approach here. Appreciate some guidance.

CodePudding user response:

Change the using declaration using var loggerFactory to the ordinary one - it leads to the disposal of the loggerFactory at the end of scope (Startup constructor in this case):

public Startup(IConfiguration configuration)
{
    Configuration = configuration;

    var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Error);
        builder.AddEventLog(s =>
        {
            s.LogName = "MyLogName";
            s.SourceName = "MySource";
        });

        // add some other persisting logger
    });
    StartupLogger = loggerFactory.CreateLogger<Startup>();
}

Also note that Startup Configure method supports dependency injection and if it feasible for you to move your initialization logic there - you can just inject ILogger<Startup> into this method:

public Startup(IConfiguration configuration)
{
    // ...
    public void Configure(IApplicationBuilder app, , IWebHostEnvironment env, ILogger<Startup> logger)
    {
        logger.LogError("Failed to do something"); 
    }
}

CodePudding user response:

Do you really need to log within ConfigureServices? Or do you need to log the options you are updating?

For anything that follows the Options Pattern, (and all built in services do) you can configure the options in a lambda, injecting other services;

services.AddMvc();
services.AddOptions<MvcOptions>()
    .Configure<ILogger<Startup>>((options, logger) => {
        options.something = X;
        logger.LogInformation($"... {X} ...");
    });

Note that the services.AddMvc(options => {...}); extension method is internally doing something very similar.

Or perform configuration via dedicated services;

services.AddTransient<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();

public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>{
    private readonly ILogger<ConfigureMvcOptions> logger;
    public ConfigureMvcOptions(ILogger<ConfigureMvcOptions> logger){
        ...
    }
    
    public void Configure(MvcOptions config)
    {
        options.something = X;
        logger.LogInformation($"... {X} ...");
    }
}

Obviously this is less flexible that just creating a startup logger. As in all the above cases, the configuration methods are only called on first use, once the generic host is starting.

  • Related