From a65a6c480ae6332b374c2f030b8580ab94e19253 Mon Sep 17 00:00:00 2001 From: Dmitrii Zvorygin Date: Fri, 10 Jul 2026 10:20:16 -0600 Subject: [PATCH 1/6] Fix ETW metadata to allow .Net EventSource subscriptions --- .../Internal/Transports/EtwDataTransport.cs | 27 ++++++-- .../EtwCollection.cs | 17 +++++ .../Transports/EtwDataTransportTests.cs | 67 +++++++++++++++++++ 3 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs create mode 100644 test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs index 8e4dc39034..6d9986d5f4 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs @@ -20,7 +20,7 @@ public EtwDataTransport(string providerName) public void Send(byte[] data, int size) { - this.eventSource.SendEvent((int)EtwEventSource.EtwEventId.TraceEvent, data, size); + this.eventSource.SendEvent(data, size); } public bool IsEnabled() @@ -54,8 +54,19 @@ public enum EtwEventId #pragma warning disable CA1822 // Mark members as static + /// + /// Dummy _data_ field is present so that when is called, the event has at least one + /// parameter with and single data object, it matches the runtime metadata + /// in ETW manifest. + /// + /// + /// Dummy placeholder for reflection-based ETW manifest generation. + /// Even though de-facto it's byte[] payload, byte[] when unwrapped to ETW manifest generation + /// is augmented with size metadata, and becomes two fields. + /// Geneva treats this only single parameter as messagepack raw blob and doesn't use manifest at all. + /// [Event((int)EtwEventId.TraceEvent, Version = 1, Level = EventLevel.Informational)] - public void InformationalEvent() + public void InformationalEvent(byte[] data) { } @@ -65,14 +76,16 @@ public void InformationalEvent() #if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "WriteEventCore is safe when eventData object is a primitive type, which it is in this case.")] #endif - public unsafe void SendEvent(int eventId, byte[] data, int size) + public unsafe void SendEvent(byte[] data, int size) { - var dataDesc = stackalloc EventData[1]; + EventData* dataDesc = stackalloc EventData[2]; fixed (byte* ptr = data) { - dataDesc[0].DataPointer = (IntPtr)ptr; - dataDesc[0].Size = size; - this.WriteEventCore(eventId, 1, dataDesc); + dataDesc[0].DataPointer = (IntPtr)(&size); + dataDesc[0].Size = 4; + dataDesc[1].DataPointer = (IntPtr)ptr; + dataDesc[1].Size = size; + this.WriteEventCore((int)EtwEventId.TraceEvent, 2, dataDesc); } } } diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs new file mode 100644 index 0000000000..ce140cdddf --- /dev/null +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs @@ -0,0 +1,17 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.Exporter.Geneva.Tests; + +/// +/// Collection definition for ETW tests. +/// +/// Since ETW is a system-global(not even process-global) resource, these tests shouldn't be run in parallel. +/// +[CollectionDefinition(Name, DisableParallelization = true)] +#pragma warning disable CA1711 // Identifiers should not have incorrect suffix +public sealed class EtwCollection +#pragma warning restore CA1711 // Identifiers should not have incorrect suffix +{ + public const string Name = "Etw collection"; +} diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs new file mode 100644 index 0000000000..1f7983c396 --- /dev/null +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs @@ -0,0 +1,67 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics.Tracing; +using System.Text; +using OpenTelemetry.Exporter.Geneva.Transports; + +namespace OpenTelemetry.Exporter.Geneva.Tests.Internal.Transports; + +[Collection(EtwCollection.Name)] +public class EtwDataTransportTests +{ + [Fact] + public void TestEtwMessageRoundtrip() + { + var randomProviderName = "x" + Guid.NewGuid().ToString("N"); + using var listener = new TestEventSourceListener(randomProviderName); + using var transport = new EtwDataTransport(randomProviderName); + byte[] payload = [1, 2, 3, 4]; + + transport.Send(payload, payload.Length); + + Assert.Single(listener.Events); + var theEvent = listener.Events[0]; + Assert.Single(theEvent.Payload); + + Assert.Equal(payload, (byte[])theEvent.Payload[0]); + } + + /// + /// Test event source listener. + /// + private sealed class TestEventSourceListener : EventListener + { + /// + /// Event source name to automatically attach to. + /// + private readonly string eventSourceName; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the event source to attach to. + public TestEventSourceListener(string eventSourceName) + { + this.eventSourceName = eventSourceName; + } + + /// + /// Gets events emitted by ETW. + /// + public List Events { get; } = new(); + + /// + protected override void OnEventSourceCreated(EventSource eventSource) + { + base.OnEventSourceCreated(eventSource); + if (string.Equals(eventSource.Name, this.eventSourceName, StringComparison.Ordinal)) + { + this.EnableEvents(eventSource, EventLevel.LogAlways); + } + } + + /// + protected override void OnEventWritten(EventWrittenEventArgs eventData) => this.Events.Add(eventData); + } +} From 560e630c137477f3d33c038149812c5bd8429007 Mon Sep 17 00:00:00 2001 From: Dmitrii Zvorygin Date: Fri, 10 Jul 2026 10:40:28 -0600 Subject: [PATCH 2/6] Fix comments --- .../Internal/Transports/EtwDataTransport.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs index 6d9986d5f4..4a53bb8cd3 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs @@ -61,17 +61,22 @@ public enum EtwEventId /// /// /// Dummy placeholder for reflection-based ETW manifest generation. - /// Even though de-facto it's byte[] payload, byte[] when unwrapped to ETW manifest generation - /// is augmented with size metadata, and becomes two fields. - /// Geneva treats this only single parameter as messagepack raw blob and doesn't use manifest at all. + /// In the ETW manifest for .Net byte[] payload is always prepender with synthetic length field. /// [Event((int)EtwEventId.TraceEvent, Version = 1, Level = EventLevel.Informational)] public void InformationalEvent(byte[] data) { } - #pragma warning restore CA1822 // Mark members as static + /// + /// Writes given raw data to ETW buffer. + /// Two fields are written to conform .Net notation - length, and data. + /// Separate length fields isn't explicitly needed, since raw ETW payload always comes with buffer length data, + /// but convention for .Net ETW libraries require that field. + /// + /// Buffer with data to be sent. + /// How many bytes of that buffer to send. [NonEvent] #if NET [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "WriteEventCore is safe when eventData object is a primitive type, which it is in this case.")] From f2cdc0b43881bc4304eb1240f3f7a0308d606f73 Mon Sep 17 00:00:00 2001 From: Dmitry Zvorygin Date: Tue, 14 Jul 2026 17:18:03 -0600 Subject: [PATCH 3/6] Fix comments and build warnings --- src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md | 5 +++++ .../Internal/Transports/EtwDataTransport.cs | 11 ++++++----- .../EtwCollection.cs | 4 ++-- .../Internal/Transports/EtwDataTransportTests.cs | 6 ++++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index b17bff0405..c329a3ee27 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +* Updated ETW manifest and payload in `EtwDataTransport` + Added synthetic `byte[] field` to method signature and `int size` to payload so + that runtime-generated .Net ETW manifest matches actual payload + ([#4729](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4729) + * Updated OpenTelemetry core component version(s) to `1.17.0`. ([#4773](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4773)) diff --git a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs index 4a53bb8cd3..63963d95c9 100644 --- a/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs +++ b/src/OpenTelemetry.Exporter.Geneva/Internal/Transports/EtwDataTransport.cs @@ -61,7 +61,8 @@ public enum EtwEventId /// /// /// Dummy placeholder for reflection-based ETW manifest generation. - /// In the ETW manifest for .Net byte[] payload is always prepender with synthetic length field. + /// In the ETW manifest for .NET, byte[] payload is always prepended with a synthetic length + /// field. /// [Event((int)EtwEventId.TraceEvent, Version = 1, Level = EventLevel.Informational)] public void InformationalEvent(byte[] data) @@ -71,9 +72,9 @@ public void InformationalEvent(byte[] data) /// /// Writes given raw data to ETW buffer. - /// Two fields are written to conform .Net notation - length, and data. - /// Separate length fields isn't explicitly needed, since raw ETW payload always comes with buffer length data, - /// but convention for .Net ETW libraries require that field. + /// Two fields are written to conform to .NET notation: length and data. + /// A separate length field isn't explicitly needed, since raw ETW payload already comes with buffer length + /// data, but the convention for .NET ETW libraries requires that field. /// /// Buffer with data to be sent. /// How many bytes of that buffer to send. @@ -83,7 +84,7 @@ public void InformationalEvent(byte[] data) #endif public unsafe void SendEvent(byte[] data, int size) { - EventData* dataDesc = stackalloc EventData[2]; + var dataDesc = stackalloc EventData[2]; fixed (byte* ptr = data) { dataDesc[0].DataPointer = (IntPtr)(&size); diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs index ce140cdddf..50b2feed3b 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs @@ -5,8 +5,8 @@ namespace OpenTelemetry.Exporter.Geneva.Tests; /// /// Collection definition for ETW tests. -/// -/// Since ETW is a system-global(not even process-global) resource, these tests shouldn't be run in parallel. +/// +/// Since ETW is a system-global (not even process-global) resource, these tests shouldn't be run in parallel. /// [CollectionDefinition(Name, DisableParallelization = true)] #pragma warning disable CA1711 // Identifiers should not have incorrect suffix diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs index 1f7983c396..cd79d1a0f4 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Tracing; -using System.Text; using OpenTelemetry.Exporter.Geneva.Transports; namespace OpenTelemetry.Exporter.Geneva.Tests.Internal.Transports; @@ -22,9 +21,12 @@ public void TestEtwMessageRoundtrip() Assert.Single(listener.Events); var theEvent = listener.Events[0]; + Assert.NotNull(theEvent.Payload); Assert.Single(theEvent.Payload); - Assert.Equal(payload, (byte[])theEvent.Payload[0]); + var rawPayload = theEvent.Payload[0] as byte[]; + Assert.NotNull(rawPayload); + Assert.Equal(payload, rawPayload); } /// From ce4116bdc1b806b6010c60bfd925a759a9210867 Mon Sep 17 00:00:00 2001 From: Dmitry Zvorygin Date: Thu, 16 Jul 2026 12:58:10 -0600 Subject: [PATCH 4/6] Remove UTF8-BOM --- test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs | 2 +- .../Internal/Transports/EtwDataTransportTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs index 50b2feed3b..61fdd87978 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs @@ -1,4 +1,4 @@ -// Copyright The OpenTelemetry Authors +// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 namespace OpenTelemetry.Exporter.Geneva.Tests; diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs index cd79d1a0f4..ded0e27382 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/Internal/Transports/EtwDataTransportTests.cs @@ -1,4 +1,4 @@ -// Copyright The OpenTelemetry Authors +// Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics.Tracing; From 23da510b93561b7d298820f08d790ecb4edfe3eb Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Fri, 17 Jul 2026 09:12:54 +0100 Subject: [PATCH 5/6] [Geneva] Fix CHANGELOG entry Re-apply previous suggestion that was force-pushed over and fix sorting. --- src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index c329a3ee27..9a6ae58355 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,14 +2,14 @@ ## Unreleased -* Updated ETW manifest and payload in `EtwDataTransport` - Added synthetic `byte[] field` to method signature and `int size` to payload so - that runtime-generated .Net ETW manifest matches actual payload - ([#4729](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4729) - * Updated OpenTelemetry core component version(s) to `1.17.0`. ([#4773](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4773)) +* Updated ETW manifest and payload in `EtwDataTransport` + with synthetic payload so that the runtime-generated .NET + ETW manifest matches the actual payload. + ([#4729](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/4729) + ## 1.16.0 Released 2026-Jul-09 From 8cd3566e2fc1ab6f1e498916b8968b63b49f4d6a Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Fri, 17 Jul 2026 09:13:52 +0100 Subject: [PATCH 6/6] [Geneva] Update constant Fix capitalisation. --- test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs index 61fdd87978..5d1e4d463a 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/EtwCollection.cs @@ -13,5 +13,5 @@ namespace OpenTelemetry.Exporter.Geneva.Tests; public sealed class EtwCollection #pragma warning restore CA1711 // Identifiers should not have incorrect suffix { - public const string Name = "Etw collection"; + public const string Name = "ETW collection"; }