在WPF中使用ObservableCollections显示Microsoft.Extensions.Logging的日志信息

背景

先前一段时间用RichTextBox实现了Microsoft.Extension.Logger的日志显示。虽然是用RichTextBox总感觉哪里不对劲,想要添加过滤显得非常复杂。最近了解并学习了ObservableCollection这个库(有点火星救援了啊),遂想到了一个更好的实现方式。

引入ObservableCollections库

  1. 在包管理中引入ObservableCollections库

可观察的日志

  1. 首先定义一个Log实体LogMessage。为了后面更好实现Filiter功能,添加LogLevel,EventId等属性。
  2. 在应用程序运行期间,需要有个存储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等其他东西

  1. 实现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();
}
}
}
  1. 实现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,
});
}
}
  1. 实现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

  1. 创建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>
  1. 创建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的日志信息的更多相关文章

  1. asp.net core 2.0 Microsoft.Extensions.Logging 文本文件日志扩展

    asp.net core微软官方为日志提供了原生支持,有如下实现 Console Debug EventLog AzureAppServices TraceSource EventSource 并且在 ...

  2. 微软日志工厂 Microsoft.Extensions.Logging 中增加 log4net 的日志输出

    前提: 需要nuget   Microsoft.Extensions.Logging.Log4Net.AspNetCore   2.2.6: 描述:解决 .net core 微软日志工厂 Micros ...

  3. Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例

    本文目录 1. Net下日志记录 2. NLog的使用     2.1 添加nuget引用NLog.Web.AspNetCore     2.2 配置文件设置     2.3 依赖配置及调用     ...

  4. microsoft.extensions.logging日志组件拓展(保存文本文件)

    Microsoft.Extensions.Logging 日志组件拓展 文件文本日志 文件文本日志UI插件 自定义介质日志 Microsoft.Extensions.Logging.File文件文本日 ...

  5. 将日志(Microsoft.Extensions.Logging)添加到.NET Core控制台应用程序

    在.NET Core项目中,日志记录是通过依赖项注入进行管理的. 尽管这对于ASP.NET项目效果很好,但在启动Startup.cs中的新项目时,所有这些都会自动创建,而在控制台应用程序中则需要一些配 ...

  6. 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 ...

  7. WPF Prism8.0中注册Nlog日志服务

    无论是Nlog还是Serilog, 它们都提供了如何快速在各类应用程序当中的快速使用方法. 尽管,你现在无论是在WPF或者ASP.NET Core当中, 都可以使用ServiceCollection来 ...

  8. MvvmLight + Microsoft.Extensions.DependencyInjection + WpfApp(.NetCore3.1)

    git clone MvvmLight失败,破网络, 就没有直接修改源码的方式来使用了 Nuget安装MvvmLightLibsStd10 使用GalaSoft.MvvmLight.Command命名 ...

  9. 使用诊断工具观察 Microsoft.Extensions.DependencyInjection 2.x 版本的内存占用

    目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...

  10. Microsoft.Extensions.DependencyInjection 之二:使用诊断工具观察内存占用

    目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...

随机推荐

  1. Java的"伪泛型"变"真泛型"后,会对性能有帮助吗?

    泛型存在于Java源代码中,在编译为字节码文件之前都会进行泛型擦除(type erasure),因此,Java的泛型完全由Javac等编译器在编译期提供支持,可以理解为Java的一颗语法糖,这种方式实 ...

  2. K8s新手系列之Pod的基本存储

    概念 官方文档:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-volume-storage/ 卷:h ...

  3. 10个让你成为CSS画家的技巧,不容错过

    @charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...

  4. VS Code上配置python虚拟环境

    1.首先在Vs Code的terminal中输入: py -3 -m venv .venv .venv\scripts\activate 2.一般报错如下: 3.解决方法: 第一步:以管理员身份运行p ...

  5. Web前端入门第 58 问:JavaScript 运算符 == 和 === 有什么区别?

    运算符 JavaScript 运算符是真的多,尤其是 ES6 之后还在不停的加运算符,其他编程语言看 JS 就像怪物一样,各种骚操作不断~~ 运算符分类 1.算术运算符 算术运算符的作用就是用来基础计 ...

  6. 用 AI 实现一个 GBK/GB2312 转 UTF-8 工具:轻松解决文本编码转换难题(附完整源码)

    用 AI 实现一个 GBK/GB2312 转 UTF-8 工具:轻松解决文本编码转换难题 在处理历史文件或与不同系统交互时,我们经常会遇到 GBK 或 GB2312 编码的文本文件.虽然现在 UTF- ...

  7. 服务器接口附件限制【1M】解决办法

     一.业务场景:         在后端与手机小程序端接口传附件时,发现经过云服务器的接口交互,附件超过1M就会有如下提示: <html> <head><title> ...

  8. 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 ...

  9. CF1928G Vlad and Trouble at MIT

    CF1928G Vlad and Trouble at MIT Vlad and Trouble at MIT Problem MIT的学生宿舍可以用一棵有\(n\)个顶点的树来表示,每个顶点代表一个 ...

  10. MQ的学习记录~

    MQ是一个消息中间件,是为了解决发送者和接收者处理速度不匹配的问题而产生的,有队列和主题两种. 队列:一对一. 主题:一对多.例如有100人订阅了我的公众号,当我在公众号上发布新的文章时100人都能收 ...