Skip to content

Commit

Permalink
Added EtwEventCounter NLog target (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
snakefoot authored May 9, 2024
1 parent 813d444 commit 4baaea0
Show file tree
Hide file tree
Showing 12 changed files with 301 additions and 17 deletions.
4 changes: 3 additions & 1 deletion NLog.Etw.Tests/EtwEventSourceTargetTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Diagnostics.Tracing;
#if NETFRAMEWORK
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
using NLog.Config;
Expand Down Expand Up @@ -176,3 +177,4 @@ public void Writing_Exception_To_Etw()
}
}

#endif
4 changes: 3 additions & 1 deletion NLog.Etw.Tests/EtwExtendedTargetTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Diagnostics.Tracing;
#if NETFRAMEWORK
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
using NLog.Config;
Expand Down Expand Up @@ -113,3 +114,4 @@ public void Writing_Message_To_Etw()
}
}

#endif
5 changes: 4 additions & 1 deletion NLog.Etw.Tests/EtwSimpleTargetTest.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -117,4 +118,6 @@ public void Writing_Message_To_Etw()
Assert.Equal(expectedEvents, collectedEvents);
}
}
}
}

#endif
74 changes: 74 additions & 0 deletions NLog.Etw.Tests/EventSourceEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#if NETFRAMEWORK
using Microsoft.Diagnostics.Tracing;
#else
using System.Diagnostics.Tracing;
#endif
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace NLog.Etw.Tests
{
internal sealed class EventSourceEnumerator : EventListener
{
public ConcurrentDictionary<string, List<string>> Events { get; } = new ConcurrentDictionary<string, List<string>>();

public EventSourceEnumerator()
{
EventSourceCreated += OnEventSourceCreated;
EventWritten += OnEventWritten;
}

#if NETFRAMEWORK
protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
OnEventWritten(this, eventData);
}
#endif

private void OnEventWritten(object sender, EventWrittenEventArgs e)
{
var events = Events[e.EventSource.Name];
if (events != null)
{
var message = e.Message;
if (string.IsNullOrEmpty(message) && e.Payload?.Count > 0)
{
for (int i = 0; i < e.Payload?.Count; ++i)
{
if (!string.IsNullOrEmpty(message))
message += "|";

if (e.Payload[i] is IEnumerable complexPayload && !(complexPayload is string))
{
foreach (var item in complexPayload)
message += item.ToString() + " ,";
}
else
{
message += e.Payload[i].ToString();
}
}
}
events.Add(message);
}
}

private void OnEventSourceCreated(object sender, EventSourceCreatedEventArgs e)
{
if (e.EventSource is null)
return;

if (!Events.ContainsKey(e.EventSource.Name))
{
var args = new Dictionary<string, string> { ["EventCounterIntervalSec"] = "1" };
EnableEvents(e.EventSource, EventLevel.LogAlways, EventKeywords.All, args);
Events.TryAdd(e.EventSource.Name, new List<string>());
}
}
}
}
68 changes: 68 additions & 0 deletions NLog.Etw.Tests/EventSourceEnumeratorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;
using Xunit;

namespace NLog.Etw.Tests
{
public class EventSourceEnumeratorTest
{
[Fact]
public void EtwEventSourceTargetTest()
{
// setup NLog configuration
var loggingConfiguration = new LoggingConfiguration();
var etwTarget = new EtwEventSourceTarget() { ProviderName = nameof(EventSourceEnumeratorTest), Layout = Layout.FromString("${uppercase:${level}}|${message}") };
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, etwTarget));
loggingConfiguration.AddTarget("etw", etwTarget);
LogManager.Configuration = loggingConfiguration;

using (var eventSourceEnumerator = new EventSourceEnumerator())
{
// send events to session
var logger = LogManager.GetLogger("A");
logger.Debug("test-debug");
logger.Info("test-info");
logger.Warn("test-warn");
logger.Error("test-error");
logger.Fatal("test-fatal");

Assert.NotEmpty(eventSourceEnumerator.Events);
Assert.True(eventSourceEnumerator.Events.ContainsKey(nameof(EventSourceEnumeratorTest)));
Assert.NotEmpty(eventSourceEnumerator.Events[nameof(EventSourceEnumeratorTest)]);
Assert.Contains("test-fatal", eventSourceEnumerator.Events[nameof(EventSourceEnumeratorTest)].LastOrDefault());
}
}

[Fact]
public void EtwEventCounterTargetTest()
{
// setup NLog configuration
var loggingConfiguration = new LoggingConfiguration();
var etwTarget = new EtwEventCounterTarget() { ProviderName = nameof(EventSourceEnumeratorTest), CounterName = nameof(EtwEventCounterTargetTest) };
loggingConfiguration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, etwTarget));
loggingConfiguration.AddTarget("etw", etwTarget);
LogManager.Configuration = loggingConfiguration;

using (var eventSourceEnumerator = new EventSourceEnumerator())
{
// send events to session
var logger = LogManager.GetLogger("A");
logger.Debug("test-debug");
logger.Info("test-info");
logger.Warn("test-warn");
logger.Error("test-error");
logger.Fatal("test-fatal");

System.Threading.Thread.Sleep(10000);

Assert.NotEmpty(eventSourceEnumerator.Events);
Assert.True(eventSourceEnumerator.Events.ContainsKey(nameof(EventSourceEnumeratorTest)));
Assert.NotEmpty(eventSourceEnumerator.Events[nameof(EventSourceEnumeratorTest)]);
}
}
}
}
2 changes: 1 addition & 1 deletion NLog.Etw.Tests/NLog.Etw.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452</TargetFrameworks>
<TargetFrameworks>net452;net6.0</TargetFrameworks>
<OutputType>Library</OutputType>
<IsPackable>false</IsPackable>
<DebugType>Full</DebugType>
Expand Down
5 changes: 4 additions & 1 deletion NLog.Etw.Tests/NLogEtwEventData.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
#if NETFRAMEWORK
using System;
using Microsoft.Diagnostics.Tracing;

namespace NLog.Etw.Tests
Expand Down Expand Up @@ -57,3 +58,5 @@ public override string ToString()
}
}
}

#endif
94 changes: 94 additions & 0 deletions NLog.Etw/EtwEventCounterTarget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#if !NET46
using System;
using System.Collections.Concurrent;
#if NET45
using Microsoft.Diagnostics.Tracing;
#else
using System.Diagnostics.Tracing;
#endif
using NLog.Config;
using NLog.Layouts;
using NLog.Targets;

namespace NLog.Etw
{
/// <summary>
/// NLog Target to ETW EventCounter. Supporting dynamic EventSource name
/// </summary>
[Target("EtwEventCounter")]
public class EtwEventCounterTarget : Target
{
private static readonly ConcurrentDictionary<string, EventCounter> EventCounters = new ConcurrentDictionary<string, EventCounter>(StringComparer.OrdinalIgnoreCase);

private EventCounter _eventCounter;

/// <summary>
/// Name used for the <see cref="EventSource" />-contructor
/// </summary>
[RequiredParameter]
public Layout ProviderName { get; set; }

/// <summary>
/// Name used for the <see cref="EventCounter" />-contructor
/// </summary>
[RequiredParameter]
public Layout CounterName { get; set; }

/// <summary>
/// The value by which to increment the counter.
/// </summary>
public Layout<int> MetricValue { get; set; } = 1;

/// <inheritdoc/>
protected override void InitializeTarget()
{
var providerName = RenderLogEvent(ProviderName, LogEventInfo.CreateNullEvent())?.Trim();
if (string.IsNullOrEmpty(providerName))
{
throw new NLogConfigurationException("EtwEventCounterTarget - ProviderName must be configured");
}

var counterName = RenderLogEvent(CounterName, LogEventInfo.CreateNullEvent())?.Trim();
if (string.IsNullOrEmpty(counterName))
{
throw new NLogConfigurationException("EtwEventCounterTarget - CounterName must be configured");
}

if (!EtwEventSourceTarget.EventSources.TryGetValue(providerName, out var eventSource))
{
eventSource = new EventSource(providerName, EventSourceSettings.EtwSelfDescribingEventFormat);
if (eventSource.ConstructionException != null)
{
NLog.Common.InternalLogger.Error("EtwEventCounterTarget(Name={0}): EventSource({1}) constructor threw exception: {2}", Name, providerName, eventSource.ConstructionException);
}
if (!EtwEventSourceTarget.EventSources.TryAdd(providerName, eventSource))
{
eventSource = EtwEventSourceTarget.EventSources[providerName];
}
}

if (!EventCounters.TryGetValue(counterName, out _eventCounter))
{
_eventCounter = new EventCounter(counterName, eventSource);
if (!EventCounters.TryAdd(counterName, _eventCounter))
{
_eventCounter = EventCounters[counterName];
}
}

base.InitializeTarget();
}

/// <summary>
/// Write LogEvent to ETW.
/// </summary>
/// <param name="logEvent">event to be written.</param>
protected override void Write(LogEventInfo logEvent)
{
int metricValue = RenderLogEvent(MetricValue, logEvent);
_eventCounter?.WriteMetric(metricValue);
}
}
}

#endif
4 changes: 2 additions & 2 deletions NLog.Etw/EtwEventSourceTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ namespace NLog.Targets
[Target("EtwEventSource")]
public class EtwEventSourceTarget : TargetWithLayout
{
private static readonly ConcurrentDictionary<string, EventSource> EventSources = new ConcurrentDictionary<string, EventSource>(System.StringComparer.OrdinalIgnoreCase);
internal static readonly ConcurrentDictionary<string, EventSource> EventSources = new ConcurrentDictionary<string, EventSource>(System.StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Name used for the EventSouce contructor
/// Name used for the <see cref="EventSource" />-contructor
/// </summary>
[RequiredParameter]
public Layout ProviderName { get; set; }
Expand Down
22 changes: 17 additions & 5 deletions NLog.Etw/NLog.Etw.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net45;net46;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net45;net46;net471;netstandard2.0</TargetFrameworks>

<Description>NLog target for Windows Event Tracing - ETW</Description>
<Authors>NLog</Authors>
Expand All @@ -11,8 +11,7 @@

<PackageTags>NLog;ETW;tracing;windows;logging;log</PackageTags>
<PackageReleaseNotes>
- Updated to NLog v5.2.2
- Added package README.md
- Added new EtwEventCounter-Target for writing to ETW EventCounter using WriteMetric

See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW
</PackageReleaseNotes>
Expand All @@ -31,8 +30,9 @@ See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW
<IsPackable>true</IsPackable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnableTrimAnalyzer>true</EnableTrimAnalyzer>
<IsTrimmable>true</IsTrimmable>
<EnableTrimAnalyzer Condition=" '$(TargetFramework)' == 'netstandard2.0' ">true</EnableTrimAnalyzer>
<IsTrimmable Condition=" '$(TargetFramework)' == 'netstandard2.0' ">true</IsTrimmable>
<TrimMode>copyused</TrimMode>
<!-- EmbedUntrackedSources for deterministic build -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
</PropertyGroup>
Expand All @@ -49,6 +49,12 @@ See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW
<DebugType Condition=" '$(Configuration)' == 'Debug' ">Full</DebugType>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net471' ">
<Title>NLog.Etw .NET Framework 4.7.1</Title>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<DebugType Condition=" '$(Configuration)' == 'Debug' ">Full</DebugType>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<Title>NLog.Etw .NET Standard 2.0</Title>
</PropertyGroup>
Expand All @@ -70,6 +76,12 @@ See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW
<Reference Include="System.Core" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net471' ">
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Diagnostics.Tracing" />
</ItemGroup>

<PropertyGroup>
<AssemblyTitle>$(Title)</AssemblyTitle>
</PropertyGroup>
Expand Down
Loading

0 comments on commit 4baaea0

Please sign in to comment.