在软件开发过程中,日志记录是一个不可或缺的工具,无论是用于概念验证(Proof of Concept, POC)代码的编写、特定场景的测试,还是调试代码。在草图或POC中,使用强大的扩展方法Dump是一种简单且足够有效的方式,甚至比传统的日志记录更好。然而,当涉及到调试已经成为大型解决方案一部分的代码,并且使用传统日志记录时,不能总是用LinqPad的工具来替代它。
背景
在日志记录方面,主要使用NLog。很多时候,需要在调用LinqPad的代码附近查看日志结果。在网上搜索时,偶然发现了一些代码片段,它们创建了一个能够将日志记录到LinqPad的Results面板的控制台日志记录器。首先,创建了一个库,以避免在草图中包含这些片段。然后,意识到想将这个输出作为一个新的目标添加,而不是替换所有现有的目标。后来,想看到彩色的日志条目,而不是纯文本。这就是这个库诞生的方式。
使用代码
如果对整个代码感兴趣,请随时访问GitHub仓库:。如果想使用这个日志记录器,需要将对库的引用添加到查询中。如果有Developer/Premium版,可以直接从NuGet安装它,对于所有其他版本,需要从GitHub下载发布包,或者自己构建它。别忘了还要添加NLog和System.Reactive.Core库。
可以使用默认设置的日志记录器,这很可能满足大部分需求。库的入口点是Nlog4LinqPad静态类,它包含两个方法:传统的LogToConsoleResults和更高级的LogToHtmlResults。两种方法的所有参数都是可选的,但得益于C# 7.2中的新特性,可以在不打扰其他参数的情况下覆盖它们的默认值。
两种方法都会将目标添加到现有配置中,或者在尚未创建任何配置的情况下创建一个新配置。注意:默认情况下,LinqPad会保持其AppDomain中加载的类型,由于NLog使用静态类,重复运行同一个草图会产生奇怪的结果。完成后按Ctrl+Shift+F5卸载进程。
控制台日志记录器
控制台日志记录器只有两个参数:最小日志级别(默认为Trace)和一个根据NLog规范的布局格式字符串(默认会显示时间戳、级别、消息和任何异常)。这个日志记录器将使用默认的结果面板进行日志记录,因此输出将与其他所有转储或写入控制台的内容混合在一起。让尝试一下:
HTML日志记录器
HTML日志记录器稍微复杂一些。让从默认值开始:
如所见,对于默认和暗色主题,默认样式是不同的。请注意,默认情况下,日志被写入一个名为"Log window"的新面板。这个面板是一个WebBrowser控件,就像Results面板一样,但它使用简化的DOM,并且(目前)没有设计上的长度限制。计划引入滚动日志视图功能,以真正支持无限日志记录。
解决方案使用Reactive Extensions来提高性能。条目被缓冲,并且文档每秒钟或当队列有200个元素时才更新一次。之后,浏览器窗口会滚动到底部。
C# observer = queue
.Buffer(TimeSpan.FromSeconds(
1
),
200
)
.ObserveOn(browser)
.Where(x => x.Count > 0)
.Subscribe(strings =>
{
browser.SuspendLayout();
foreach
(
var
text
in
strings
)
{
var
e = browser.Document.CreateElement(
"
div"
);
e.InnerHtml = text;
browser.Document.Body.InsertAdjacentElement
(HtmlElementInsertionOrientation.BeforeEnd, e);
}
browser.ResumeLayout();
browser.Document.Body.ScrollIntoView(
false
);
});
主题与浏览器同步观察,因此UI线程。Rx提供的这种优雅方式,摆脱了调用!
静态方法有几个参数,所有参数都是可选的,默认值:
样式
有一个名为TargetStyling的类,可以为各种场景保存CSS样式。让以默认布局为例:"${date:format=HH\\:mm\\:ss}${level}${message}${exception:format=tostring}"。这是日志条目行的模式。每个用${...}标记的布局元素将代表该行中的一个项目。这两个的样式对应物总是会有的。所有其他应用的样式将取决于条目的内容。
HTML
<div class="lqph-row lqph-levelname">
<span class="lqph-item lqph-date">..
</span>
<span class="lqph-item lqph-level">..
</span>
<span class="lqph-item lqph-message">..
</span>
<span class="lqph-item lqph-exception">..
</span>
</div>
item、row和log level类在类中有直接的属性对应物。但还有一个名为Classes的属性,这是一个字典,可以用它为每个NLog渲染器添加样式等等。只是为了说明,这些在默认样式中使用,从而产生了上面的视图:
C# Classes = {
{
"date",
"width: 100px;"
},
{
"level",
"width: 70px; text-transform: uppercase; font-weight: bold;"
},
{
"exception:empty",
"display:none;"
},
{
"exception",
"display:block; padding: 10px;"
}
}
有趣的点
首先,使用了默认的Results面板,但在检查了LinqPad的代码和生成的html代码后,意识到它不是日志使用的最佳选择,因为它有不需要的功能的开销,并且有限制来支持这些。然后,发现了这段代码,在0.5版本中使用了它,但不幸的是,它被证明不适合多线程场景,也不适合高负载,因为它使用了与默认面板相同的写入器,它强加了限制和开销。在这一点上,放弃了这个方向,并实现了上面的基于Rx的方法。