找回密码
 立即注册
首页 业界区 业界 [WPF]在WPF中使用ObservableCollections显示Microsoft.E ...

[WPF]在WPF中使用ObservableCollections显示Microsoft.Extensions.Logging的日志信息

梁宁 2025-7-21 11:50:51
背景

先前一段时间用RichTextBox实现了Microsoft.Extension.Logger的日志显示。虽然是用RichTextBox总感觉哪里不对劲,想要添加过滤显得非常复杂。最近了解并学习了ObservableCollection这个库(有点火星救援了啊),遂想到了一个更好的实现方式。
引入ObservableCollections库


  • 在包管理中引入ObservableCollections库
可观察的日志


  • 首先定义一个Log实体LogMessage。为了后面更好实现Filiter功能,添加LogLevel,EventId等属性。
  • 在应用程序运行期间,需要有个存储LogMessage的地方,这里定义接口ILogMessageHolder, 使用ObservableFixedSizeRingBuffer做容器(环形数组,可以使用指定大小的size),整个Observable Logs就是这个ObservableCollections库里的容器。
  1. public struct LogMessage
  2. {
  3.     public LogLevel LogLevel { get; set;}
  4.     public EventId EventId { get; set;}
  5.     public string Category { get; set;}
  6.     public DateTime Time { get; set;}
  7.     public string Message { get; set;}
  8. }
  9. public interface ILoggerMessageHolder
  10. {
  11.     public ObservableFixedSizeRingBuffer<LogMessage> LogMessages {get;}
  12. }
复制代码
实现Logger等其他东西


  • 实现LogMessageProcessor, 这里依然参照ConsoleLoggerProcessor的实现。(有个疑问,直接往LogMessage容器里添加新项,性能能开销应该不大,这里还需要使用工作线程来执行Enqueue操作吗?)
  1. internal class LogMessageProcessor
  2. {
  3.     //... 省略字段和其余方法实现
  4.     public LogMessageProcessor(ILogMessageHolder logMessageHolder, LoggerQueueFullMode fullMode, int maxQueueLength)
  5.     {
  6.         _logMessageHolder = logMessageHolder;
  7.         _messageQueue = new();
  8.         FullMode = fullMode;
  9.         MaxQueueLength = maxQueueLength;
  10.         _outputThread = new Thread(ProcessMessageQueue)
  11.         {
  12.             IsBackground = true,
  13.             Name = "LogMessage queue processing thread"
  14.         };
  15.         _outputThread.Start();
  16.     }
  17.     //将 WriteMessage 中写入Console的部分修改为往LogMessage容器里添加LogMessage
  18.     internal void WriteMessage(LogMessage message)
  19.     {
  20.         try
  21.         {
  22.             _logMessageHolder.LogMessages.AddLast(message);
  23.         }
  24.         catch
  25.         {
  26.             CompleteAdding();
  27.         }
  28.     }
  29. }
复制代码

  • 实现Logger。其实这里的LogFormatter属性可要可不要,因为这里的Formatter只对TState做格式化,可以直接做成一个内部的格式化器。当然,这里使用LogFormatter方便后期直接在配置中直接替换实现
  1. internal class Logger : ILogger
  2. {
  3.     private readonly string _category;
  4.     private readonly LogMessageProcessor _processor;
  5.     private StringWriter? t_stringWriter;
  6.     internal IExternalScopeProvider ScopeProvider { get; set; }
  7.     public Logger(string category, LogMessageProcessor processor, LogFormatter formatter,IExternalScopeProvider scopeProvider)
  8.     {
  9.         _category = category;
  10.         _processor = processor;
  11.         Formatter = formatter;
  12.         ScopeProvider = scopeProvider;
  13.     }
  14.     public LogFormatter Formatter { get; set; }
  15.     public IDisposable? BeginScope<TState>(TState state) where TState : notnull
  16.     {
  17.         return ScopeProvider.Push(state) ?? NullScope.Instance;
  18.     }
  19.     public bool IsEnabled(LogLevel logLevel)
  20.     {
  21.         return true;
  22.     }
  23.     public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
  24.     {
  25.         t_stringWriter ??= new StringWriter();
  26.         var entry = new LogEntry<TState>(logLevel, _category, eventId, state, exception, formatter);
  27.         Formatter.Write(entry, t_stringWriter);
  28.         var sb = t_stringWriter.GetStringBuilder();
  29.         var computedString = sb.ToString();
  30.         sb.Clear();
  31.         _processor.WriteMessage(new LogMessage()
  32.         {
  33.             Time = DateTime.Now,
  34.             Id = eventId,
  35.             Level = logLevel,
  36.             Category = _category,
  37.             Message = computedString,
  38.         });
  39.     }
  40. }
复制代码

  • 实现LoggerProvider和LoggingBuilderExtension
  1. internal class LoggerProvider : ILoggerProvider
  2. {
  3.     private readonly LogFormatter _formatter;
  4.     private readonly ConcurrentDictionary<string, Logger> _loggers = [];
  5.     private readonly LogMessageProcessor _processor;
  6.     public LoggerProvider(ILogMessageHolder holder, LogFormatter formatter)
  7.     {
  8.         _formatter = formatter;
  9.         _processor = new LogMessageProcessor(holder, LoggerQueueFullMode.Wait, 2500);
  10.     }
  11.     public ILogger CreateLogger(string categoryName)
  12.     {
  13.         return _loggers.GetOrAdd(categoryName, new Logger(categoryName, _processor, _formatter));
  14.     }
  15.     public void Dispose()
  16.     {
  17.         _processor.Dispose();
  18.     }
  19. }
  20. public static class LoggingBuilderExtension
  21. {
  22.     public static ILoggingBuilder AddObservableLogs(this ILoggingBuilder builder)
  23.     {
  24.         builder.Services.AddSingleton<ILoggerProvider, LoggerProvider>();
  25.         builder.Services.AddSingleton<IMfgLoggerProvider, LoggerProvider>();
  26.         builder.Services.AddTransient<LogFormatter, SimpleLogFormatter>();
  27.         builder.Services.AddSingleton<ILogMessageHolder, LogMessageHolder>();
  28.         return builder;
  29.     }
  30. }
复制代码

  • 至此,Logger的核心已经完成,接下来是在View中显示Log
简单实现LogViewer


  • 创建LogWindow,使用ItemsControl显示LogMessage。
    实际上,LogMessage的Formatter,是下文的LogMessageConverter。
  1. public class LogMessageConverter : IValueConverter
  2. {
  3. public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  4. {
  5.     if (value is not LogMessage message)
  6.     {
  7.         return "";
  8.     }
  9.     return $"{message.Time:yyyy-MM-dd HH:mm:ss,fff} {GetLogLevelString(message.LogLevel)}: {message.Category} [{message.EventId}]\r\n{message.Message}";
  10. }
  11. public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  12. {
  13.     throw new NotImplementedException();
  14. }
  15. private static string GetLogLevelString(LogLevel logLevel) => logLevel switch
  16. {
  17.     LogLevel.Trace => "trce",
  18.     LogLevel.Debug => "dbug",
  19.     LogLevel.Information => "info",
  20.     LogLevel.Warning => "warn",
  21.     LogLevel.Error => "fail",
  22.     LogLevel.Critical => "crit",
  23.     _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
  24. };
  25. }
复制代码
  1. <ItemsControl ItemsSource="{Binding LogMessages}">
  2.     <ItemsControl.ItemTemplate>
  3.         <DataTemplate>
  4.             
  5.             <TextBlock Text="{Binding ., Converter={StaticResource LogMessageConverter}}"></TextBlock>
  6.         </DataTemplate>
  7.     </ItemsControl.ItemTemplate>
  8. </ItemsControl>
复制代码

  • 创建LogWindow的DataContext
    这里就涉及到ObservableCollections库的知识了,最后绑定到View上的是LogMessages属性。如果想对LogMessages进行过滤,比如选取LogLevel.Information等,使用_viewList的AttachFiliter就好啦。
  1. public class LogWindowViewModel
  2. {
  3.     private readonly ISynchronizedView<LogMessage, LogMessage> _viewList;
  4.     private readonly ILogMessageHolder _logMessageHolder;
  5.       
  6.     public LogWindowViewModel(ILogMessageHolder logMessageHolder)
  7.     {
  8.         _logMessageHolder = logMessageHolder;
  9.         _viewList = logMessageHolder.LogMessages.CreateView(x => x);
  10.         LogMessages = _viewList.ToNotifyCollectionChanged();
  11.     }
  12.     public INotifyCollectionChangedSynchronizedViewList<LogMessage> LogMessages { get; }
  13. }
复制代码
结尾

使用ObservableCollections可以非常简单的将LogMessage显示到某个UI上,并且LogMessages的生命周期是跟随应用程序,随时可以打开查看应用程序的日志。由于ObservableCollections是纯C#实现,理论上是可以在Avalonia中使用的。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册