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.