背景
微软的日志库一般是输出到控制台的,但是在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包吧。