65.9K
CodeProject 正在变化。 阅读更多。
Home

LowLevelDesign.NLog.Ext 和 NLog 的 ETW 目标

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2014年4月18日

MIT

3分钟阅读

viewsIcon

10483

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 等工具中具有更有意义的名称和特征

capture-perfview2

capture-wpa2

与第一个目标生成的事件相比

capture-perfview1

capture-wpa1

在检查上述输出之后,应该毫无疑问您应该使用哪个目标。 我推荐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 目标)

Capture

如果您正在寻找有关如何使用其他工具的信息,或者您想了解更多关于 ETW 追踪的信息,我推荐 Vance Morisson 的博客。 如果您碰巧懂波兰语,也可以看看 Michal 的帖子

此帖子中的所有源代码都可以在我的 Codeplex 页面上下载,我的 Nuget 包正在这里等着您。 :)


归档于: CodeProject, 使用 NLog 进行日志记录
© . All rights reserved.