Reputation: 16652
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
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:
Thanks guys, both of your suggestions worked perfectly! 😃
Upvotes: 3