[WPF]在WPF中使用ObservableCollections显示Microsoft.Extensions.Logging的日志信息
在WPF中使用ObservableCollections显示Microsoft.Extensions.Logging的日志信息
背景
先前一段时间用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))
};
}
<!--省略Window的命名空间和其余属性-->
<ItemsControl ItemsSource="{Binding LogMessages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--如果想要显示日志等级的颜色,需要其他TextBlock和添加trigger-->
<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中使用的。
[WPF]在WPF中使用ObservableCollections显示Microsoft.Extensions.Logging的日志信息的更多相关文章
- asp.net core 2.0 Microsoft.Extensions.Logging 文本文件日志扩展
asp.net core微软官方为日志提供了原生支持,有如下实现 Console Debug EventLog AzureAppServices TraceSource EventSource 并且在 ...
- 微软日志工厂 Microsoft.Extensions.Logging 中增加 log4net 的日志输出
前提: 需要nuget Microsoft.Extensions.Logging.Log4Net.AspNetCore 2.2.6: 描述:解决 .net core 微软日志工厂 Micros ...
- Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例
本文目录 1. Net下日志记录 2. NLog的使用 2.1 添加nuget引用NLog.Web.AspNetCore 2.2 配置文件设置 2.3 依赖配置及调用 ...
- microsoft.extensions.logging日志组件拓展(保存文本文件)
Microsoft.Extensions.Logging 日志组件拓展 文件文本日志 文件文本日志UI插件 自定义介质日志 Microsoft.Extensions.Logging.File文件文本日 ...
- 将日志(Microsoft.Extensions.Logging)添加到.NET Core控制台应用程序
在.NET Core项目中,日志记录是通过依赖项注入进行管理的. 尽管这对于ASP.NET项目效果很好,但在启动Startup.cs中的新项目时,所有这些都会自动创建,而在控制台应用程序中则需要一些配 ...
- Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activate 'xxxxx.Controllers.xxxxController'.
Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activa ...
- WPF Prism8.0中注册Nlog日志服务
无论是Nlog还是Serilog, 它们都提供了如何快速在各类应用程序当中的快速使用方法. 尽管,你现在无论是在WPF或者ASP.NET Core当中, 都可以使用ServiceCollection来 ...
- MvvmLight + Microsoft.Extensions.DependencyInjection + WpfApp(.NetCore3.1)
git clone MvvmLight失败,破网络, 就没有直接修改源码的方式来使用了 Nuget安装MvvmLightLibsStd10 使用GalaSoft.MvvmLight.Command命名 ...
- 使用诊断工具观察 Microsoft.Extensions.DependencyInjection 2.x 版本的内存占用
目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...
- Microsoft.Extensions.DependencyInjection 之二:使用诊断工具观察内存占用
目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...
随机推荐
- Java的"伪泛型"变"真泛型"后,会对性能有帮助吗?
泛型存在于Java源代码中,在编译为字节码文件之前都会进行泛型擦除(type erasure),因此,Java的泛型完全由Javac等编译器在编译期提供支持,可以理解为Java的一颗语法糖,这种方式实 ...
- K8s新手系列之Pod的基本存储
概念 官方文档:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-volume-storage/ 卷:h ...
- 10个让你成为CSS画家的技巧,不容错过
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- VS Code上配置python虚拟环境
1.首先在Vs Code的terminal中输入: py -3 -m venv .venv .venv\scripts\activate 2.一般报错如下: 3.解决方法: 第一步:以管理员身份运行p ...
- Web前端入门第 58 问:JavaScript 运算符 == 和 === 有什么区别?
运算符 JavaScript 运算符是真的多,尤其是 ES6 之后还在不停的加运算符,其他编程语言看 JS 就像怪物一样,各种骚操作不断~~ 运算符分类 1.算术运算符 算术运算符的作用就是用来基础计 ...
- 用 AI 实现一个 GBK/GB2312 转 UTF-8 工具:轻松解决文本编码转换难题(附完整源码)
用 AI 实现一个 GBK/GB2312 转 UTF-8 工具:轻松解决文本编码转换难题 在处理历史文件或与不同系统交互时,我们经常会遇到 GBK 或 GB2312 编码的文本文件.虽然现在 UTF- ...
- 服务器接口附件限制【1M】解决办法
一.业务场景: 在后端与手机小程序端接口传附件时,发现经过云服务器的接口交互,附件超过1M就会有如下提示: <html> <head><title> ...
- CentOS 7.* 安装 python3.8.2 python3.10.2 步骤
CentOS 7系列 安装 python3.8.2 步骤 1.在python官网下载linux源码包 地址:https://www.python.org/ftp/python/3.8.3/Python ...
- CF1928G Vlad and Trouble at MIT
CF1928G Vlad and Trouble at MIT Vlad and Trouble at MIT Problem MIT的学生宿舍可以用一棵有\(n\)个顶点的树来表示,每个顶点代表一个 ...
- MQ的学习记录~
MQ是一个消息中间件,是为了解决发送者和接收者处理速度不匹配的问题而产生的,有队列和主题两种. 队列:一对一. 主题:一对多.例如有100人订阅了我的公众号,当我在公众号上发布新的文章时100人都能收 ...