背景

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

[WPF] 在RichTextBox中输出Microsoft.Extension.Logging库的日志消息的更多相关文章

  1. Laravel 中输出 SQL 语句的到 log 日志

    在 AppServiceProvider.php 中的 boot 方法中添加如下代码 即可 public function boot() { //数据库监听 DB::listen(function ( ...

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

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

  3. python的logging库

    logging库 简介 logging库提供日志打印功能. 值得一提的是,不仅能打印到日志文件,还能打印到控制台. 日志级别 logging一共分为5个级别,从低到高依次为:  DEBUG<IN ...

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

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

  5. WPF中, 启用添加到RichTextBox中的控件

    原文:WPF中, 启用添加到RichTextBox中的控件   WPF中, 启用添加到RichTextBox中的控件                                           ...

  6. 年度巨献-WPF项目开发过程中WPF小知识点汇总(原创+摘抄)

    WPF中Style的使用 Styel在英文中解释为”样式“,在Web开发中,css为层叠样式表,自从.net3.0推出WPF以来,WPF也有样式一说,通过设置样式,使其WPF控件外观更加美化同时减少了 ...

  7. WindowsXamlHost:在 WPF 中使用 UWP 控件库中的控件

    在 WindowsXamlHost:在 WPF 中使用 UWP 的控件(Windows Community Toolkit) 一文中,我们说到了在 WPF 中引入简单的 UWP 控件以及相关的注意事项 ...

  8. How do I duplicate a resource reference in code behind in WPF?如何在WPF后台代码中中复制引用的资源?

    原文 https://stackoverflow.com/questions/28240528/how-do-i-duplicate-a-resource-reference-in-code-behi ...

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

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

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

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

随机推荐

  1. Index - 此处的诗

    虚构往事 正篇   嗯--本来发过两篇,但深愧于仓促的处理和并未完善的细节设定,隐藏了.   大概会是一个中篇的科幻故事,世界设定已经完善了(Shaya 可以作证!),但近期可能没有精力动笔. 番外 ...

  2. Diary & Solution Set - 多校度假

    目录 \(\mathscr{Summary}\sim6.14\) \(\mathscr{Contest}\) \((3/3)\) \(\mathscr{A}.\) 区间第 \(k\) 小 \(\mat ...

  3. CDS标准视图:一次性账户的客户行项目 I_ONETIMEACCOUNTCUSTOMER

    视图名称:一次性账户的客户行项目 视图类型:基础 视图代码: 点击查看代码 @EndUserText.label: 'One-Time Account Data for Customer Items' ...

  4. 微服务实战系列(八)-网关springcloud gateway自定义规则-copy

    1. 场景描述 先说明下项目中使用的网关是:springcloud gateway, 因需要给各个网关服务系统提供自定义配置路由规则,实时生效,不用重启网关(重启风险大),目前已实现:动态加载自定义路 ...

  5. Spring Security 基于JWT的单点登陆(SSO)开发及原理解析

    JDK1.8Spring boot 2.xSpring Security 5.x 单点登录(Single Sign On),简称为SSO,是目前比较流行的企业业务整合的解决方案之一. SSO的定义是在 ...

  6. 解决StringBuilder readline阻塞问题

    readline之所以会阻塞socket流没有结束符 阻塞场景:read() 没有读取到任何数据 readLine() 没有读取到结束符或者换行符 可以用ready判断通道中数据是否读完,读完返回fa ...

  7. react生命周期-渲染阶段

    import React, { Component } from "react"; export default class Shengming extends Component ...

  8. 浅谈云主机在VPC中进行迁移的使用场景和操作方法

    本文分享自天翼云开发者社区<浅谈云主机在VPC中进行迁移的使用场景和操作方法>,作者:刘****雪 一.客户经常遇到的网络迁移问题 客户在天翼云购买一台云主机并且部署完成想要的应用后,发现 ...

  9. Linux 服务器防火墙开放端口命令(iptables、firewalld和ufw)

    本文主要介绍Linux中,Centos.Ubuntu和Debian开放防火墙端口的命令(iptables.firewalld和ufw)方法. 1.Centos中开放端口 1.systemctl sta ...

  10. 探索sqlmap在WebSocket安全测试中的应用

    探索sqlmap在WebSocket安全测试中的应用 WebSocket与HTTP的区别 WebSocket,对于初次接触的人来说,往往会引发一个疑问:既然我们已经有了广泛使用的HTTP协议,为何还需 ...