diff --git a/NLog.Etw.Tests/EtwEventSourceTargetTests.cs b/NLog.Etw.Tests/EtwEventSourceTargetTests.cs index dffb349..c15c021 100644 --- a/NLog.Etw.Tests/EtwEventSourceTargetTests.cs +++ b/NLog.Etw.Tests/EtwEventSourceTargetTests.cs @@ -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; @@ -176,3 +177,4 @@ public void Writing_Exception_To_Etw() } } +#endif \ No newline at end of file diff --git a/NLog.Etw.Tests/EtwExtendedTargetTest.cs b/NLog.Etw.Tests/EtwExtendedTargetTest.cs index bebd4cd..5c43e60 100644 --- a/NLog.Etw.Tests/EtwExtendedTargetTest.cs +++ b/NLog.Etw.Tests/EtwExtendedTargetTest.cs @@ -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; @@ -113,3 +114,4 @@ public void Writing_Message_To_Etw() } } +#endif \ No newline at end of file diff --git a/NLog.Etw.Tests/EtwSimpleTargetTest.cs b/NLog.Etw.Tests/EtwSimpleTargetTest.cs index 72bf221..149c82c 100644 --- a/NLog.Etw.Tests/EtwSimpleTargetTest.cs +++ b/NLog.Etw.Tests/EtwSimpleTargetTest.cs @@ -1,3 +1,4 @@ +#if NETFRAMEWORK using System; using System.Collections.Generic; using System.IO; @@ -117,4 +118,6 @@ public void Writing_Message_To_Etw() Assert.Equal(expectedEvents, collectedEvents); } } -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/NLog.Etw.Tests/EventSourceEnumerator.cs b/NLog.Etw.Tests/EventSourceEnumerator.cs new file mode 100644 index 0000000..a516aef --- /dev/null +++ b/NLog.Etw.Tests/EventSourceEnumerator.cs @@ -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> Events { get; } = new ConcurrentDictionary>(); + + 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 { ["EventCounterIntervalSec"] = "1" }; + EnableEvents(e.EventSource, EventLevel.LogAlways, EventKeywords.All, args); + Events.TryAdd(e.EventSource.Name, new List()); + } + } + } +} diff --git a/NLog.Etw.Tests/EventSourceEnumeratorTest.cs b/NLog.Etw.Tests/EventSourceEnumeratorTest.cs new file mode 100644 index 0000000..d829274 --- /dev/null +++ b/NLog.Etw.Tests/EventSourceEnumeratorTest.cs @@ -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)]); + } + } + } +} diff --git a/NLog.Etw.Tests/NLog.Etw.Tests.csproj b/NLog.Etw.Tests/NLog.Etw.Tests.csproj index ab69cb0..e32e3c5 100644 --- a/NLog.Etw.Tests/NLog.Etw.Tests.csproj +++ b/NLog.Etw.Tests/NLog.Etw.Tests.csproj @@ -1,6 +1,6 @@  - net452 + net452;net6.0 Library false Full diff --git a/NLog.Etw.Tests/NLogEtwEventData.cs b/NLog.Etw.Tests/NLogEtwEventData.cs index 487b289..b9c00cd 100644 --- a/NLog.Etw.Tests/NLogEtwEventData.cs +++ b/NLog.Etw.Tests/NLogEtwEventData.cs @@ -1,4 +1,5 @@ -using System; +#if NETFRAMEWORK +using System; using Microsoft.Diagnostics.Tracing; namespace NLog.Etw.Tests @@ -57,3 +58,5 @@ public override string ToString() } } } + +#endif \ No newline at end of file diff --git a/NLog.Etw/EtwEventCounterTarget.cs b/NLog.Etw/EtwEventCounterTarget.cs new file mode 100644 index 0000000..a9c2020 --- /dev/null +++ b/NLog.Etw/EtwEventCounterTarget.cs @@ -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 +{ + /// + /// NLog Target to ETW EventCounter. Supporting dynamic EventSource name + /// + [Target("EtwEventCounter")] + public class EtwEventCounterTarget : Target + { + private static readonly ConcurrentDictionary EventCounters = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private EventCounter _eventCounter; + + /// + /// Name used for the -contructor + /// + [RequiredParameter] + public Layout ProviderName { get; set; } + + /// + /// Name used for the -contructor + /// + [RequiredParameter] + public Layout CounterName { get; set; } + + /// + /// The value by which to increment the counter. + /// + public Layout MetricValue { get; set; } = 1; + + /// + 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(); + } + + /// + /// Write LogEvent to ETW. + /// + /// event to be written. + protected override void Write(LogEventInfo logEvent) + { + int metricValue = RenderLogEvent(MetricValue, logEvent); + _eventCounter?.WriteMetric(metricValue); + } + } +} + +#endif \ No newline at end of file diff --git a/NLog.Etw/EtwEventSourceTarget.cs b/NLog.Etw/EtwEventSourceTarget.cs index 7886031..8d2dbfb 100644 --- a/NLog.Etw/EtwEventSourceTarget.cs +++ b/NLog.Etw/EtwEventSourceTarget.cs @@ -16,10 +16,10 @@ namespace NLog.Targets [Target("EtwEventSource")] public class EtwEventSourceTarget : TargetWithLayout { - private static readonly ConcurrentDictionary EventSources = new ConcurrentDictionary(System.StringComparer.OrdinalIgnoreCase); + internal static readonly ConcurrentDictionary EventSources = new ConcurrentDictionary(System.StringComparer.OrdinalIgnoreCase); /// - /// Name used for the EventSouce contructor + /// Name used for the -contructor /// [RequiredParameter] public Layout ProviderName { get; set; } diff --git a/NLog.Etw/NLog.Etw.csproj b/NLog.Etw/NLog.Etw.csproj index 6daccad..b320554 100644 --- a/NLog.Etw/NLog.Etw.csproj +++ b/NLog.Etw/NLog.Etw.csproj @@ -1,7 +1,7 @@  - net45;net46;netstandard2.0 + net45;net46;net471;netstandard2.0 NLog target for Windows Event Tracing - ETW NLog @@ -11,8 +11,7 @@ NLog;ETW;tracing;windows;logging;log -- 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 @@ -31,8 +30,9 @@ See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW true true true - true - true + true + true + copyused true @@ -49,6 +49,12 @@ See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW Full + + NLog.Etw .NET Framework 4.7.1 + true + Full + + NLog.Etw .NET Standard 2.0 @@ -70,6 +76,12 @@ See https://github.com/NLog/NLog.Etw for documentation of NLog target for ETW + + + + + + $(Title) diff --git a/README.md b/README.md index 3dbc6bc..cfb85a0 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,14 @@ Example of `NLog.config`-file that writes to ETW: ```xml + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance""> - + + + + + + + + + + + + + + ``` \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index 4c065bb..888e66d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -2,7 +2,7 @@ # creates NuGet package at \artifacts dotnet --version -$versionPrefix = "5.2.2" # Also update version for minor versions in appveyor.yml +$versionPrefix = "5.2.4" # Also update version for minor versions in appveyor.yml $versionSuffix = "" $versionFile = $versionPrefix + "." + ${env:APPVEYOR_BUILD_NUMBER} if ($env:APPVEYOR_PULL_REQUEST_NUMBER) {