背景
先前一段时间用RichTextBox实现了Microsoft.Extension.Logger的日志显示。虽然是用RichTextBox总感觉哪里不对劲,想要添加过滤显得非常复杂。最近了解并学习了ObservableCollection这个库(有点火星救援了啊),遂想到了一个更好的实现方式。
引入ObservableCollections库
- 在包管理中引入ObservableCollections库
可观察的日志
- 首先定义一个Log实体LogMessage。为了后面更好实现Filiter功能,添加LogLevel,EventId等属性。
- 在应用程序运行期间,需要有个存储LogMessage的地方,这里定义接口ILogMessageHolder, 使用ObservableFixedSizeRingBuffer做容器(环形数组,可以使用指定大小的size),整个Observable Logs就是这个ObservableCollections库里的容器。
- public struct LogMessage
- {
- public LogLevel LogLevel { get; set;}
- public EventId EventId { get; set;}
- public string Category { get; set;}
- public DateTime Time { get; set;}
- public string Message { get; set;}
- }
- public interface ILoggerMessageHolder
- {
- public ObservableFixedSizeRingBuffer<LogMessage> LogMessages {get;}
- }
复制代码 实现Logger等其他东西
- 实现LogMessageProcessor, 这里依然参照ConsoleLoggerProcessor的实现。(有个疑问,直接往LogMessage容器里添加新项,性能能开销应该不大,这里还需要使用工作线程来执行Enqueue操作吗?)
- internal class LogMessageProcessor
- {
- //... 省略字段和其余方法实现
- public LogMessageProcessor(ILogMessageHolder logMessageHolder, LoggerQueueFullMode fullMode, int maxQueueLength)
- {
- _logMessageHolder = logMessageHolder;
- _messageQueue = new();
- FullMode = fullMode;
- MaxQueueLength = maxQueueLength;
- _outputThread = new Thread(ProcessMessageQueue)
- {
- IsBackground = true,
- Name = "LogMessage queue processing thread"
- };
- _outputThread.Start();
- }
- //将 WriteMessage 中写入Console的部分修改为往LogMessage容器里添加LogMessage
- internal void WriteMessage(LogMessage message)
- {
- try
- {
- _logMessageHolder.LogMessages.AddLast(message);
- }
- catch
- {
- CompleteAdding();
- }
- }
- }
复制代码
- 实现Logger。其实这里的LogFormatter属性可要可不要,因为这里的Formatter只对TState做格式化,可以直接做成一个内部的格式化器。当然,这里使用LogFormatter方便后期直接在配置中直接替换实现
- internal class Logger : ILogger
- {
- private readonly string _category;
- private readonly LogMessageProcessor _processor;
- private StringWriter? t_stringWriter;
- internal IExternalScopeProvider ScopeProvider { get; set; }
- public Logger(string category, LogMessageProcessor processor, LogFormatter formatter,IExternalScopeProvider scopeProvider)
- {
- _category = category;
- _processor = processor;
- Formatter = formatter;
- ScopeProvider = scopeProvider;
- }
- public LogFormatter Formatter { get; set; }
- public IDisposable? BeginScope<TState>(TState state) where TState : notnull
- {
- return ScopeProvider.Push(state) ?? NullScope.Instance;
- }
- public bool IsEnabled(LogLevel logLevel)
- {
- return true;
- }
- public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
- {
- t_stringWriter ??= new StringWriter();
- var entry = new LogEntry<TState>(logLevel, _category, eventId, state, exception, formatter);
- Formatter.Write(entry, t_stringWriter);
- var sb = t_stringWriter.GetStringBuilder();
- var computedString = sb.ToString();
- sb.Clear();
- _processor.WriteMessage(new LogMessage()
- {
- Time = DateTime.Now,
- Id = eventId,
- Level = logLevel,
- Category = _category,
- Message = computedString,
- });
- }
- }
复制代码
- 实现LoggerProvider和LoggingBuilderExtension
- internal class LoggerProvider : ILoggerProvider
- {
- private readonly LogFormatter _formatter;
- private readonly ConcurrentDictionary<string, Logger> _loggers = [];
- private readonly LogMessageProcessor _processor;
- public LoggerProvider(ILogMessageHolder holder, LogFormatter formatter)
- {
- _formatter = formatter;
- _processor = new LogMessageProcessor(holder, LoggerQueueFullMode.Wait, 2500);
- }
- public ILogger CreateLogger(string categoryName)
- {
- return _loggers.GetOrAdd(categoryName, new Logger(categoryName, _processor, _formatter));
- }
- public void Dispose()
- {
- _processor.Dispose();
- }
- }
- public static class LoggingBuilderExtension
- {
- public static ILoggingBuilder AddObservableLogs(this ILoggingBuilder builder)
- {
- builder.Services.AddSingleton<ILoggerProvider, LoggerProvider>();
- builder.Services.AddSingleton<IMfgLoggerProvider, LoggerProvider>();
- builder.Services.AddTransient<LogFormatter, SimpleLogFormatter>();
- builder.Services.AddSingleton<ILogMessageHolder, LogMessageHolder>();
- return builder;
- }
- }
复制代码
- 至此,Logger的核心已经完成,接下来是在View中显示Log
简单实现LogViewer
- 创建LogWindow,使用ItemsControl显示LogMessage。
实际上,LogMessage的Formatter,是下文的LogMessageConverter。
- public class LogMessageConverter : IValueConverter
- {
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if (value is not LogMessage message)
- {
- return "";
- }
- return $"{message.Time:yyyy-MM-dd HH:mm:ss,fff} {GetLogLevelString(message.LogLevel)}: {message.Category} [{message.EventId}]\r\n{message.Message}";
- }
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- private static string GetLogLevelString(LogLevel logLevel) => logLevel switch
- {
- LogLevel.Trace => "trce",
- LogLevel.Debug => "dbug",
- LogLevel.Information => "info",
- LogLevel.Warning => "warn",
- LogLevel.Error => "fail",
- LogLevel.Critical => "crit",
- _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
- };
- }
复制代码- <ItemsControl ItemsSource="{Binding LogMessages}">
- <ItemsControl.ItemTemplate>
- <DataTemplate>
-
- <TextBlock Text="{Binding ., Converter={StaticResource LogMessageConverter}}"></TextBlock>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
复制代码
- 创建LogWindow的DataContext
这里就涉及到ObservableCollections库的知识了,最后绑定到View上的是LogMessages属性。如果想对LogMessages进行过滤,比如选取LogLevel.Information等,使用_viewList的AttachFiliter就好啦。
- public class LogWindowViewModel
- {
- private readonly ISynchronizedView<LogMessage, LogMessage> _viewList;
- private readonly ILogMessageHolder _logMessageHolder;
-
- public LogWindowViewModel(ILogMessageHolder logMessageHolder)
- {
- _logMessageHolder = logMessageHolder;
- _viewList = logMessageHolder.LogMessages.CreateView(x => x);
- LogMessages = _viewList.ToNotifyCollectionChanged();
- }
- public INotifyCollectionChangedSynchronizedViewList<LogMessage> LogMessages { get; }
- }
复制代码 结尾
使用ObservableCollections可以非常简单的将LogMessage显示到某个UI上,并且LogMessages的生命周期是跟随应用程序,随时可以打开查看应用程序的日志。由于ObservableCollections是纯C#实现,理论上是可以在Avalonia中使用的。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |