diff --git a/src/OpenTelemetry.Exporter.Geneva/.publicApi/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.Geneva/.publicApi/PublicAPI.Unshipped.txt index 1ebc2da8c6..47d044db99 100644 --- a/src/OpenTelemetry.Exporter.Geneva/.publicApi/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.Geneva/.publicApi/PublicAPI.Unshipped.txt @@ -1,4 +1,6 @@ +OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.IncludeTraceStateForSpan.get -> bool +OpenTelemetry.Exporter.Geneva.GenevaExporterOptions.IncludeTraceStateForSpan.set -> void static Microsoft.Extensions.Logging.GenevaLoggingExtensions.AddGenevaLogExporter(this OpenTelemetry.Logs.LoggerProviderBuilder builder) -> OpenTelemetry.Logs.LoggerProviderBuilder static Microsoft.Extensions.Logging.GenevaLoggingExtensions.AddGenevaLogExporter(this OpenTelemetry.Logs.LoggerProviderBuilder builder, string name, System.Action configureExporter) -> OpenTelemetry.Logs.LoggerProviderBuilder static Microsoft.Extensions.Logging.GenevaLoggingExtensions.AddGenevaLogExporter(this OpenTelemetry.Logs.LoggerProviderBuilder builder, System.Action configureExporter) -> OpenTelemetry.Logs.LoggerProviderBuilder -static OpenTelemetry.Exporter.Geneva.GenevaExporterHelperExtensions.AddGenevaTraceExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder \ No newline at end of file +static OpenTelemetry.Exporter.Geneva.GenevaExporterHelperExtensions.AddGenevaTraceExporter(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md index 9afb8423cd..77503d13fb 100644 --- a/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Geneva/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Update GenevaTraceExporter to export `activity.TraceStateString` as the value + for Part B `traceState` field for Spans when the `IncludeTraceStateForSpan` + option is set to `true`. This is an opt-in feature and the default value is `false`. + Note that this is for Spans only and not for LogRecord. + ([#1850](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1850)) + ## 1.9.0-rc.1 Released 2024-Jun-12 diff --git a/src/OpenTelemetry.Exporter.Geneva/GenevaExporterOptions.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterOptions.cs index 9069182880..1f9bfa0aaf 100644 --- a/src/OpenTelemetry.Exporter.Geneva/GenevaExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterOptions.cs @@ -25,6 +25,8 @@ public class GenevaExporterOptions public EventNameExportMode EventNameExportMode { get; set; } + public bool IncludeTraceStateForSpan { get; set; } + public IReadOnlyDictionary TableNameMappings { get => this._tableNameMappings; diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs index 94b7f86bf9..c205c8a0c2 100644 --- a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs @@ -91,6 +91,8 @@ public MsgPackTraceExporter(GenevaExporterOptions options) #endif } + this.m_shouldIncludeTraceState = options.IncludeTraceStateForSpan; + var buffer = new byte[BUFFER_SIZE]; var cursor = 0; @@ -243,6 +245,17 @@ internal int SerializeActivity(Activity activity) cntFields += 1; } + if (this.m_shouldIncludeTraceState) + { + var traceStateString = activity.TraceStateString; + if (!string.IsNullOrEmpty(traceStateString)) + { + cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "traceState"); + cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, traceStateString); + cntFields += 1; + } + } + var linkEnumerator = activity.EnumerateLinks(); if (linkEnumerator.MoveNext()) { @@ -453,5 +466,7 @@ public void Dispose() private readonly HashSet m_dedicatedFields; #endif + private readonly bool m_shouldIncludeTraceState; + private bool isDisposed; } diff --git a/src/OpenTelemetry.Exporter.Geneva/README.md b/src/OpenTelemetry.Exporter.Geneva/README.md index 049bb6663c..286fa63dfd 100644 --- a/src/OpenTelemetry.Exporter.Geneva/README.md +++ b/src/OpenTelemetry.Exporter.Geneva/README.md @@ -98,6 +98,13 @@ A list of fields which should be stored as individual table columns. This is a collection of fields that will be applied to all the Logs and Traces sent through this exporter. +#### `IncludeTraceStateForSpan` (optional) + +Export `activity.TraceStateString` as the value for Part B `traceState` field for +Spans when the `IncludeTraceStateForSpan` option is set to `true`. +This is an opt-in feature and the default value is `false`. +Note that this is for Spans only and not for LogRecord. + #### `TableNameMappings` (optional) This defines the mapping for the table name used to store Logs and Traces. diff --git a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs index 95e4d15b82..ce63e57fab 100644 --- a/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/TLDExporter/TldTraceExporter.cs @@ -43,6 +43,7 @@ internal sealed class TldTraceExporter : TldExporter, IDisposable private readonly byte partAFieldsCount = 3; // At least three fields: time, ext_dt_traceId, ext_dt_spanId private readonly HashSet m_customFields; private readonly Tuple repeatedPartAFields; + private readonly bool m_shouldIncludeTraceState; private readonly EventProvider eventProvider; @@ -114,6 +115,8 @@ public TldTraceExporter(GenevaExporterOptions options) this.repeatedPartAFields = eb.GetRawFields(); } } + + this.m_shouldIncludeTraceState = options.IncludeTraceStateForSpan; } public ExportResult Export(in Batch batch) @@ -205,6 +208,16 @@ internal void SerializeActivity(Activity activity) partBFieldsCount++; } + if (this.m_shouldIncludeTraceState) + { + var traceStateString = activity.TraceStateString; + if (!string.IsNullOrEmpty(traceStateString)) + { + eb.AddCountedAnsiString("traceState", traceStateString, Encoding.UTF8); + partBFieldsCount++; + } + } + var linkEnumerator = activity.EnumerateLinks(); if (linkEnumerator.MoveNext()) { diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs index e9cc04eb8e..e7ee2668f5 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs @@ -195,11 +195,15 @@ public void GenevaTraceExporter_Success_Windows() } [Theory] - [InlineData(false, false)] - [InlineData(false, true)] - [InlineData(true, false)] - [InlineData(true, true)] - public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping, bool hasCustomFields) + [InlineData(false, false, false)] + [InlineData(false, true, false)] + [InlineData(true, false, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(false, true, true)] + [InlineData(true, false, true)] + [InlineData(true, true, true)] + public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping, bool hasCustomFields, bool includeTraceState) { string path = string.Empty; Socket server = null; @@ -241,6 +245,11 @@ public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping, exporterOptions.CustomFields = new string[] { "clientRequestId" }; } + if (includeTraceState) + { + exporterOptions.IncludeTraceStateForSpan = true; + } + using var exporter = new MsgPackTraceExporter(exporterOptions); #if NET8_0_OR_GREATER var dedicatedFields = typeof(MsgPackTraceExporter).GetField("m_dedicatedFields", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(exporter) as FrozenSet; @@ -278,6 +287,11 @@ public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping, var linkedtraceId2 = ActivityTraceId.CreateFromString("e8ea7e9ac72de94e91fabc613f9686a2".AsSpan()); var linkedSpanId2 = ActivitySpanId.CreateFromString("888915b6286b9c02".AsSpan()); + if (includeTraceState) + { + parentActivity.TraceStateString = "some=state"; + } + var links = new[] { new ActivityLink(new ActivityContext( @@ -719,6 +733,15 @@ private void AssertFluentdForwardModeForActivity(GenevaExporterOptions exporterO Assert.Equal(activity.ParentSpanId.ToHexString(), mapping["parentId"]); } + if (!exporterOptions.IncludeTraceStateForSpan || string.IsNullOrEmpty(activity.TraceStateString)) + { + Assert.False(mapping.ContainsKey("traceState")); + } + else + { + Assert.Equal(activity.TraceStateString, mapping["traceState"]); + } + #region Assert Activity Links if (activity.Links.Any()) {