aybe
aybe

Reputation: 16652

How to output Microsoft.Extensions.Logging to a ListBox or anything else?

I would like to output all of the logging in a WPF application to a ListBox or anything else. The general idea would be to have an ObservableCollection<T> that the ListBox would be bound to.

However I don't have any idea how I could plug this feature to the following startup sequence.

public partial class App
{
    private IHost _host = null!;

    protected override async void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var hostBuilder = new HostBuilder()
                .ConfigureHostConfiguration(builder =>
                {
                })
                .ConfigureAppConfiguration((context, builder) =>
                {
                    builder
                        .SetBasePath(context.HostingEnvironment.ContentRootPath)
                        .AddJsonFile("AppSettings.json", false)
                        .AddCommandLine(e.Args);
                })
                .ConfigureServices((context, collection) =>
                {
                    collection
                        .Configure<AppSettings>(context.Configuration.GetSection(nameof(AppSettings)))
                        .AddSingleton<MainWindow>()
                        .AddTransient<MainViewModel>();
                })
                .ConfigureLogging((context, builder) =>
                {
                    builder.AddDebug();
                })
            ;

        _host = hostBuilder.Build();

        await _host.StartAsync();

        var window = _host.Services.GetRequiredService<MainWindow>();

        window.Show();
    }

    protected override void OnExit(ExitEventArgs e)
    {
        base.OnExit(e);

        using (_host)
        {
            _host.StopAsync(TimeSpan.FromSeconds(5.0d));
        }
    }
}

I suppose it should be pluggable as an extension method, pretty much like builder.AddDebug but that's about it.

Question:

How can I extend Microsoft.Extensions.Logging to output logging to a ListBox for instance?

Upvotes: 2

Views: 805

Answers (1)

aybe
aybe

Reputation: 16652

We have a beginning of an answer thanks to @StephenCleary and @PMF comments:

The custom ILogger:

public sealed class MyCustomLogger : ILogger
{
    public MyCustomLogger(string category, ObservableCollection<string> collection)
    {
        Category   = category;
        Collection = collection;
    }

    private string Category { get; }

    private ObservableCollection<string> Collection { get; }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        var message = formatter(state, exception);

        message = $"{Category}: {message}";

        Collection.Add(message);
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return new NullScope();
    }

    private class NullScope : IDisposable
    {
        public void Dispose()
        {
        }
    }
}

The custom ILoggerProvider:

public sealed class MyCustomLoggerProvider : ILoggerProvider, IObservableLoggerProvider
{
    public MyCustomLoggerProvider()
    {
        Entries = Collection;
    }

    public ObservableCollection<string> Collection { get; } = new();

    public void Dispose()
    {
    }

    public ILogger CreateLogger(string categoryName)
    {
        return new MyCustomLogger(categoryName, Collection);
    }

    public ObservableCollection<string>? Entries { get; }
}

The extension method to plug the logger in ConfigureLogging:

public static class MyCustomLoggerExtensions
{
    public static ILoggingBuilder AddCustomLogger(this ILoggingBuilder builder)
    {
        builder.AddConfiguration();

        builder.Services.TryAddEnumerable(
            ServiceDescriptor.Singleton<ILoggerProvider, MyCustomLoggerProvider>()
        );

        return builder;
    }
}

Now I've added this interface to the provider:

public interface IObservableLoggerProvider
{
    ObservableCollection<string>? Entries { get; }
}

In the view model I look for that provider with that interface:

public sealed class MainViewModel : ObservableRecipient
{
    public MainViewModel(IServiceProvider serviceProvider)
    {
        logger.LogInformation("Log from MainViewModel 1");

        var service = serviceProvider.GetService<ILoggerProvider>();
        if (service is IObservableLoggerProvider provider)
        {
            Entries = provider.Entries;
        }
    }

    public ObservableCollection<string>? Entries { get; }
}

Whenever I click that button to log something, the ListBox actualizes itself:

enter image description here

Thanks guys, both of your suggestions worked perfectly! 😃

Upvotes: 3

Related Questions