LowLevelDesign.NLog.Ext 和 NLog 的 ETW 目标





4.00/5 (2投票s)
LowLevelDesign.NLog.Ext 和 NLog 的 ETW 目标
我非常喜欢 NLog 库,并且经常在我的项目中使用它。 不久前,我写了一篇文章,其中向您展示了我首选的调试和生产配置。前几天,我介绍了一个简单的程序集版本布局渲染器。 今天,我想告诉您,所有这些好东西;)都可以在我全新的LowLevelDesign.NLog.Ext Nuget 包中找到。
此外,您可能会在其中找到两个 ETW NLog 目标。 ETW(Windows 事件追踪)是一种非常有效的日志记录方式,并且其内核支持使其成为详细/追踪/调试日志的绝佳选择。 此外,如果您在性能分析中使用 Windows Performance Toolkit,则提供您自己的 ETW 消息将帮助您将系统事件与应用程序中的方法相关联。 ETW 基础设施是高度可定制的(查看Semantic Logging Application Block,了解您的日志可能的外观以及它们可能被如何使用:)。
我们的第一个 ETW NLog 目标非常简单,基于System.Diagnostics.Eventing
中的EventProvider
using NLog;
using NLog.Targets;
using System;
using System.Diagnostics.Eventing;
namespace LowLevelDesign.NLog.Ext
{
[Target("EventTracing")]
public sealed class NLogEtwTarget : TargetWithLayout
{
private EventProvider provider;
private Guid providerId = Guid.NewGuid();
/// <summary>
/// A provider guid that will be used in ETW tracing.
/// </summary>
public String ProviderId {
get { return providerId.ToString(); }
set {
providerId = Guid.Parse(value);
}
}
protected override void InitializeTarget() {
base.InitializeTarget();
// we will create an EventProvider for ETW
try {
provider = new EventProvider(providerId);
} catch (PlatformNotSupportedException) {
// sorry :(
}
}
protected override void Write(LogEventInfo logEvent) {
if (provider == null || !provider.IsEnabled()) {
return;
}
byte t;
if (logEvent.Level == LogLevel.Debug || logEvent.Level == LogLevel.Trace) {
t = 5;
} else if (logEvent.Level == LogLevel.Info) {
t = 4;
} else if (logEvent.Level == LogLevel.Warn) {
t = 3;
} else if (logEvent.Level == LogLevel.Error) {
t = 2;
} else if (logEvent.Level == LogLevel.Fatal) {
t = 1;
} else {
t = 5; // let it be verbose
}
provider.WriteMessageEvent(this.Layout.Render(logEvent), t, 0);
}
protected override void CloseTarget() {
base.CloseTarget();
provider.Dispose();
}
}
}
第二个构建在 Microsoft.Diagnostics.Tracing
Nuget 包之上 (作者是Vance Morrison)。 从 .NET 4.5 开始,EventSource
类在框架中可用,但是如果您希望您的代码也能与 .NET 4.0 一起使用(像我一样),则需要使用 Nuget 包。 扩展 ETW 目标的代码如下
using Microsoft.Diagnostics.Tracing;
using NLog;
using NLog.Targets;
using System;
namespace LowLevelDesign.NLog.Ext
{
[Target("ExtendedEventTracing")]
public sealed class NLogEtwExtendedTarget : TargetWithLayout
{
[EventSource(Name = "LowLevelDesign-NLogEtwSource")]
public sealed class EtwLogger : EventSource
{
[Event(1, Level = EventLevel.Verbose)]
public void Verbose(String LoggerName, String Message) {
WriteEvent(1, LoggerName, Message);
}
[Event(2, Level = EventLevel.Informational)]
public void Info(String LoggerName, String Message) {
WriteEvent(2, LoggerName, Message);
}
[Event(3, Level = EventLevel.Warning)]
public void Warn(String LoggerName, String Message) {
WriteEvent(3, LoggerName, Message);
}
[Event(4, Level = EventLevel.Error)]
public void Error(String LoggerName, String Message) {
WriteEvent(4, LoggerName, Message);
}
[Event(5, Level = EventLevel.Critical)]
public void Critical(String LoggerName, String Message) {
WriteEvent(5, LoggerName, Message);
}
public readonly static EtwLogger Log = new EtwLogger();
}
protected override void Write(LogEventInfo logEvent)
{
if (!EtwLogger.Log.IsEnabled())
{
return;
}
if (logEvent.Level == LogLevel.Debug || logEvent.Level == LogLevel.Trace) {
EtwLogger.Log.Verbose(logEvent.LoggerName, Layout.Render(logEvent));
} else if (logEvent.Level == LogLevel.Info) {
EtwLogger.Log.Info(logEvent.LoggerName, Layout.Render(logEvent));
} else if (logEvent.Level == LogLevel.Warn) {
EtwLogger.Log.Warn(logEvent.LoggerName, Layout.Render(logEvent));
} else if (logEvent.Level == LogLevel.Error) {
EtwLogger.Log.Error(logEvent.LoggerName, Layout.Render(logEvent));
} else if (logEvent.Level == LogLevel.Fatal) {
EtwLogger.Log.Critical(logEvent.LoggerName, Layout.Render(logEvent));
}
}
}
}
它们之间最大的区别在于,第二个与 ETW 基础设施的集成更好。 感谢动态清单生成,来自第二个目标的事件在 PerfView 或 WPA 等工具中具有更有意义的名称和特征
与第一个目标生成的事件相比
在检查上述输出之后,应该毫无疑问您应该使用哪个目标。 我推荐NLogEtwExtendedTarget
,除非您需要控制 ETW 提供程序的 GUID。 无法更改 LowLevelDesign
-NLogEtwSource
的 GUID(EventSources
使用明确定义的公共机制 (RFC 4122) 将名称转换为 GUID),因此使用此目标的应用程序的日志始终具有相同的提供程序。 我不认为这是一个大问题,因为它们仍然可以通过进程 ID 或名称来区分。
在上面的屏幕截图中,还有一件事应该引起您的注意。 您是否注意到我们事件的活动 ID? 是的,它们在那里,并且很容易启用它们。 我们过去常常在活动范围的开头调用 Trace.CorrelationManager.ActivityId = newActivityId;
,现在我们需要再调用一个调用:EventSource.SetCurrentThreadActivityId(newActivityId, out prevActivityId);
。 我的测试应用程序如下所示
using System;
using System.Diagnostics;
using NLog;
using LowLevelDesign.NLog.Ext;
using Microsoft.Diagnostics.Tracing;
public static class TestNLog
{
private static readonly Logger logger = LogManager.GetLogger("TestLogger");
public static void Main(String[] args) {
Guid prevActivityId;
Guid newActivityId = Guid.NewGuid();
Trace.CorrelationManager.ActivityId = newActivityId;
EventSource.SetCurrentThreadActivityId(newActivityId, out prevActivityId);
Console.WriteLine("Trace source logging");
logger.Info("Start");
Console.WriteLine("In the middle of tracing");
logger.ErrorException("Error occurred", new Exception("TestException"));
logger.Info("End");
EventSource.SetCurrentThreadActivityId(prevActivityId);
Trace.CorrelationManager.ActivityId = prevActivityId;
}
}
更新: @Scooletz 他的评论启发我添加了一个用于范围追踪的快捷方式。 新添加的类名为 EtwContextScope
,可在该软件包的 1.2.1 版本中使用。 使用它的上述 Main
方法将如下所示
public static void Main(String[] args)
{
using (new EtwContextScope())
{
Console.WriteLine("Trace source logging");
logger.Info("Start");
Console.WriteLine("In the middle of tracing");
logger.ErrorException("Error occurred", new Exception("TestException"));
logger.Info("End");
}
}
及其配置文件
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
</startup>
<nlog internalLogToConsole="true"
internalLogLevel="Debug" throwExceptions="true">
<extensions>
<add prefix="lld" assembly="LowLevelDesign.NLog.Ext" />
</extensions>
<targets>
<target name="console" type="ColoredConsole"
layout="${longdate}|${uppercase:${level}}|${message}$
{onexception:|Exception occurred\:${exception:format=tostring}}" />
<target name="etw" type="lld.EventTracing"
providerId="ff1d574a-58a1-45f1-ae5e-040cf8d3fae2"
layout="${longdate}|${uppercase:${level}}|${message}$
{onexception:|Exception occurred\:${exception:format=tostring}}" />
<target name="eetw" type="lld.ExtendedEventTracing"
layout="${longdate}|${uppercase:${level}}|${message}$
{onexception:|Exception occurred\:${exception:format=tostring}}" />
</targets>
<rules>
<logger name="TestLogger" minlevel="Debug" writeTo="console" />
<logger name="TestLogger" minlevel="Debug" writeTo="etw" />
<logger name="TestLogger" minlevel="Debug" writeTo="eetw" />
</rules>
</nlog>
</configuration>
由于 ETW 事件是通过内核缓冲区传输的,因此您需要使用特殊的工具或库来收集它们。 您可以借助前面提到的 Semantic Logging Block 创建自己的解决方案,或者尝试使用 PerfView 或 WPR 等工具。 下面的屏幕截图显示了一个 PerfView 捕获窗口,其中启用了来自我的测试应用程序的提供程序(请注意,我们只能通过其名称来引用扩展的 ETW 目标)
如果您正在寻找有关如何使用其他工具的信息,或者您想了解更多关于 ETW 追踪的信息,我推荐 Vance Morisson 的博客。 如果您碰巧懂波兰语,也可以看看 Michal 的帖子。
此帖子中的所有源代码都可以在我的 Codeplex 页面上下载,我的 Nuget 包正在这里等着您。 :)
归档于: CodeProject, 使用 NLog 进行日志记录
