background
Microsoft's log library is generally output to the console, but it cannot directly use the console in WPF, and AllocConsole is required.
But I personally think this approach is not very safe (the entire program is quit as soon as the console is closed?). At this time, a more friendly way to output logs is needed.
question
So how do you display the content of the log into a RichTextBox?
Implement LoggerProcessor
- Here we refer to the official ConsoleLoggerProcessor, but there is a little difference.
public class RichTextBoxLoggerProcessor:IDisposable
{
///... Please refer to the source code for other implementations
private readonly RichTextBoxDocumentStorage _storage;
private readonly Thread _outputThread;
/// This constructor is passed into RichTextBoxDocumentStorage, which is used to display a single log record
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();
}
///Rewrite the WriteMessage method, and brothers who are familiar with FlowDocument should know what Paragraph is
public void WriteMessage(Paragraph message)
{
try
{
//Send back to the thread where the FlowDocument is located and add Paragraph
_storage.Document?.(() =>
{
_storage.(message);
});
}
catch
{
CompleteAdding();
}
}
//Rewrite the EnqueMessage method and Enqueue method similar to the same
public void EnqueMessage(Paragraph message)
{
//...For specific logic, please refer to the github source code
}
public bool Enqueue(Paragraph message)
{
//...
}
public bool TryDequeue(out Paragraph entry)
{
//...
}
}
public class RichTextBoxDocumentStorage
{
///Because you want to use DI, create a class to store the FlowDocument;
public FlowDocument? Document{ get; set; }
}
Implement RichTextBoxLogger
- Here is the ILogger interface
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 Formatizer
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 needs to be created in the main thread
(() =>
{
var message = (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);
}
Create 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, , 2500);
_formatter = formatter;
}
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, new RichTextBoxLogger(categoryName, _processor, _formatter));
}
}
Create a real LogViewer
- Window is used here to display logs
public class LogViewer: Window
{
public LogViewer(RichTextBoxDocumentStorage storage)
{
InitializeComponent();
if( is null)
{
//Make sure the FlowDocument is created on the main thread
(()=>{
_storage.Document = new FlowDocument() { TextAlignment = };
});
}
= ;
}
}
Register Service
public static class RichTextBoxLoggingExtension
{
public static ILoggingBuilder AddRichTextBoxLogger(this ILoggingBuilder builder)
{
<RichTextBoxDocumentStorage>();
//I won't write the formatting implementation, write the formatter according to your own preferences; here is the SimpleConsoleFormatter implementation referenced here
<RichTextBoxFormatter, SimpleRichTextBoxFormatter>();
<ILoggerProvider,RichTextBoxLoggerProvider>();
return builder;
}
}
Specific use
- Use ServiceProvider to call LogViewer anywhere
public class SomeClass
{
public void OpenLogViewer()
{
<LogViewer>().Show();
}
}
Ending
This is just a simple output, and there are many functions that have not been implemented.
I don't like writing too long explanations, it feels so troublesome. The code is the best explanation (
Let’s see if you have a whim on one day, make a nuget package.