4

I'm in the process of writing a custom ConfigurationSource/ConfigurationProvider to access configuration stored in a Spring Cloud Config Server.

I want to be able to write errors and trace information to the log but so far haven't found a way to get an ILogger<T> this early in the application lifecycle.

I know about this answer but LoggerFactory.AddConsole is deprecated. The only implementation of the proposed alternative ILoggingBuilder is internal. Copying the really simple implementation wouldn't help me either because I don't have access to an IServiceProvider.

Any other ideas?


Update

Not much to it but that would be the outline to my problem. It's an extract from Program.CreateWebHostBuilder(string[]) of an ASP.NET Core web app.

IWebHostBuilder builder = WebHost.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((webHostBuilderContext, configurationBuilder) =>
    {
        ILogger<MyCustomConfigSource> logger = ???;
        configurationBuilder.Add(new MyCustomConfigSource(logger));
    });
Pang
  • 9,564
  • 146
  • 81
  • 122
Sebastian Weber
  • 6,766
  • 2
  • 30
  • 49
  • Some reference https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-2.1#custom-configuration-provider – Nkosi Mar 04 '19 at 13:45
  • 1
    Take a look at the remarks here https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.hosting.webhostbuilder.configureappconfiguration?view=aspnetcore-2.2#remarks – Nkosi Mar 04 '19 at 14:40
  • I had a similar problem and ended up writing custom method inside ConfigurationProvider that takes in ServiceProvider after it is initiated. https://stackoverflow.com/questions/64556896/how-to-access-di-inside-custom-configurationprovider/64590126#64590126 – doorman Oct 29 '20 at 11:25

1 Answers1

6

What I ended up doing was to write an ILogger<T> implementation that caches log entries and is able to replay them to a real logger as soon as one is available. Doesn't help when your system is unable to start-up.

public class ReplayLogger : ILogger
{
    private readonly List<Action<ILogger>> logs;
    private readonly Stack<IDisposable> scopes;

    public ReplayLogger()
    {
        this.logs = new List<Action<ILogger>>();
        this.scopes = new Stack<IDisposable>();
    }

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception exception,
        Func<TState, Exception, string> formatter)
    {
        this.logs.Add(logger => logger.Log(logLevel, eventId, state, exception, formatter));
    }

    public bool IsEnabled(LogLevel logLevel) => true;

    public IDisposable BeginScope<TState>(TState state)
    {
        this.logs.Add(logger =>
        {
            IDisposable scope = logger.BeginScope(state);

            this.scopes.Push(scope);
        });

        return new Scope(this);
    }

    public void Replay(ILogger logger)
    {
        Guard.NotNull(logger, nameof(logger));

        foreach (Action<ILogger> action in this.logs)
        {
            action(logger);
        }
    }

    private class Scope : IDisposable
    {
        private readonly ReplayLogger logger;

        public Scope(ReplayLogger logger)
        {
            this.logger = logger;
        }

        public void Dispose()
        {
            this.logger.logs.Add(_ => this.logger.scopes.Pop());
        }
    }
}
Sebastian Weber
  • 6,766
  • 2
  • 30
  • 49
  • It looks like you're missing a `.Dispose()` following `.Pop()`. – Miral May 01 '23 at 03:45
  • @Miral that might well be the case. since then we moved on to [Serilog's bootstrap logger](https://nblumhardt.com/2020/10/bootstrap-logger/) which has a much smoother integration then our solution – Sebastian Weber May 24 '23 at 13:53