[WPF] 在RichTextBox中输出Microsoft.Extension.Logging库的日志消息

背景

微软的日志库一般是输出到控制台的,但是在WPF中并不能直接使用控制台,需要AllocConsole。
但是这种做法个人觉得不太安全(一关闭控制台整个程序就退出了?)。这时候就需要一个更加友好的方式输出日志。

问题

那如何将日志的内容显示到RichTextBox中?

实现LoggerProcessor

  • 这里参照官方的ConsoleLoggerProcessor,但是需要有点区别。
public class RichTextBoxLoggerProcessor:IDisposable {     ///...其他实现请参照Microsoft.Extension.Logging的源码     private readonly RichTextBoxDocumentStorage _storage;     private readonly Thread _outputThread;      /// 这个构造函数传入RichTextBoxDocumentStorage,用于显示单条日志记录     public RichTextBoxLoggerProcessor(RichTextBoxDocumentStorage storage,       LoggerQueueFullMode fullMode, int maxQueueLength)     {         _storage = storage;         _messageQueue = new();         FullMode = fullMode;         MaxQueueLength = maxQueueLength;         _outputThread = new Thread(ProcessMessageQueue)         {             IsBackground = true,             Name = "RichTextBox logger queue processing thread"         };         _outputThread.Start();     }      ///改写WriteMessage方法,熟悉FlowDocument的兄弟应该都知道Paragraph是什么吧     public void WriteMessage(Paragraph message)     {         try         {             //发送回FlowDocument所在的线程后添加Paragraph             _storage.Document?.Dispatcher.BeginInvoke(() =>             {                 _storage.Document.Blocks.Add(message);             });         }         catch         {             CompleteAdding();         }     }      //同理改写EnqueMessage方法和Enqueue等方法     public void EnqueMessage(Paragraph message)     {         //...具体逻辑请参阅github源码     }      public bool Enqueue(Paragraph message)     {         //...     }       public bool TryDequeue(out Paragraph entry)      {         //...      } }  public class RichTextBoxDocumentStorage {     ///因为要使用到DI,所以创建一个类来存放FlowDocument;     public FlowDocument? Document{ get; set; } } 

实现RichTextBoxLogger

  • 这里继承ILogger接口
public class RichTextBoxLogger:ILogger {     private string _category;     private RichTextBoxLoggerProcessor _processor;      public RichTextBoxLogger(string category, RichTextBoxLoggerProcessor processor, RichTextBoxFormatter formatter)     {         _category = category;         _processor = processor;         Formatter = formatter;     }      //LogEntry格式化器     public RichTextBoxFormatter Formatter { get; set; }      public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)     {         var logEntry = new LogEntry<TState>(logLevel, _category, eventId, state, exception, formatter);          //paragraph 需要在主线程创建         App.Current.Dispatcher.BeginInvoke(() =>         {             var message = Formatter.Write(in logEntry);             if (message is null)             {               return;             }             _processor.EnqueMessage(message);       });   } }  public abstract class RichTextBoxFormatter {     protected RichTextBoxFormatter(string name)     {         Name = name;     }      public string Name { get; }      public abstract Paragraph? Write<TState>(in LogEntry<TState> logEntry); } 

创建LoggerProvider

public class RichTextBoxLoggerProvider: ILoggerProvider {     private readonly RichTextBoxFormatter _formatter;     private readonly ConcurrentDictionary<string,RichTextBoxLogger> _loggers = [];     private readonly RichTextBoxLoggerProcessor _processor;     public RichTextBoxLoggerProvider(RichTextBoxDocumentStorage storage, RichTextBoxFormatter formatter)     {         _formatter = formatter;         _processor = new RichTextBoxLoggerProcessor(storage, LoggerQueueFullMode.Wait, 2500);         _formatter = formatter;     }      public ILogger CreateLogger(string categoryName)     {         return _loggers.GetOrAdd(categoryName, new RichTextBoxLogger(categoryName, _processor, _formatter));     } } 

创建真正的LogViewer

  • 这里使用的是Window来展现日志
public class LogViewer : Window {     public LogViewer(RichTextBoxDocumentStorage storage)     {         InitializeComponent();         if(storage.Document is null)         {             //确保FlowDocument是在主线程上创建的             App.Current.Dispatcher.Invoke(()=>{                 _storage.Document =  new FlowDocument() { TextAlignment = System.Windows.TextAlignment.Left };              });         }         logPresenter.Document = storage.Document;     } } 

注册服务

public static class RichTextBoxLoggingExtension  {     public static ILoggingBuilder AddRichTextBoxLogger(this ILoggingBuilder builder)     {         builder.Services.AddSingleton<RichTextBoxDocumentStorage>();         //格式化的实现就不写了,按自己的喜好来写写格式化器;这里是参照的SimpleConsoleFormatter实现的         builder.Services.AddSingleton<RichTextBoxFormatter, SimpleRichTextBoxFormatter>();         builder.Services.AddSingleton<ILoggerProvider,RichTextBoxLoggerProvider>();         return builder;     } } 

具体使用

  • 任意位置使用ServiceProvider唤起LogViewer即可
public class SomeClass {     public void OpenLogViewer()     {         App.Current.Services.GetRequiredService<LogViewer>().Show();     } } 

结尾

这里只是实现了个简单的输出,还有好多好多功能没有实现。
不喜欢写太长的解释说明,感觉好麻烦。代码就是最好的说明(
看哪天心血来潮了,做个nuget包吧。

发表评论

评论已关闭。

相关文章