diff --git a/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md b/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md index 4a54261cd43..4ff60447049 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Jaeger/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Added support for Activity Status and StatusDescription which were + added to Activity from version 6.0. To maintain backward + compatibility, the exporter falls back to checking status inside + the tag "otel.status_code". + ([#3073](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3073)) + ## 1.2.0-rc3 Released 2022-Mar-04 diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs index d2c2d0876cc..33a3f8ee332 100644 --- a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs @@ -54,6 +54,50 @@ public static JaegerSpan ToJaegerSpan(this Activity activity) activity.EnumerateTags(ref jaegerTags); + if (activity.Status != ActivityStatusCode.Unset) + { + if (activity.Status == ActivityStatusCode.Ok) + { + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag(SpanAttributeConstants.StatusCodeKey, JaegerTagType.STRING, vStr: "OK")); + } + else + { + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag(SpanAttributeConstants.StatusCodeKey, JaegerTagType.STRING, vStr: "ERROR")); + + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag(JaegerErrorFlagTagName, JaegerTagType.BOOL, vBool: true)); + + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag(SpanAttributeConstants.StatusDescriptionKey, JaegerTagType.STRING, vStr: activity.StatusDescription ?? string.Empty)); + } + } + else if (jaegerTags.StatusCode.HasValue && jaegerTags.StatusCode != StatusCode.Unset) + { + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag( + SpanAttributeConstants.StatusCodeKey, + JaegerTagType.STRING, + vStr: StatusHelper.GetTagValueForStatusCode(jaegerTags.StatusCode.Value))); + + if (jaegerTags.StatusCode == StatusCode.Error) + { + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag(JaegerErrorFlagTagName, JaegerTagType.BOOL, vBool: true)); + + PooledList.Add( + ref jaegerTags.Tags, + new JaegerTag(SpanAttributeConstants.StatusDescriptionKey, JaegerTagType.STRING, vStr: jaegerTags.StatusDescription ?? string.Empty)); + } + } + string peerServiceName = null; if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { @@ -252,44 +296,6 @@ private static void ProcessJaegerTagArray(ref PooledList tags, KeyVal } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ProcessJaegerTag(ref TagEnumerationState state, string key, JaegerTag jaegerTag) - { - if (jaegerTag.VStr != null) - { - PeerServiceResolver.InspectTag(ref state, key, jaegerTag.VStr); - - if (key == SpanAttributeConstants.StatusCodeKey) - { - StatusCode? statusCode = StatusHelper.GetStatusCodeForTagValue(jaegerTag.VStr); - if (statusCode == StatusCode.Error) - { - // Error flag: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#error-flag - PooledList.Add(ref state.Tags, new JaegerTag(JaegerErrorFlagTagName, JaegerTagType.BOOL, vBool: true)); - } - else if (!statusCode.HasValue || statusCode == StatusCode.Unset) - { - // Unset Status is not sent: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/jaeger.md#status - return; - } - - // Normalize status since it is user-driven. - jaegerTag = new JaegerTag(key, JaegerTagType.STRING, vStr: StatusHelper.GetTagValueForStatusCode(statusCode.Value)); - } - else if (key == JaegerErrorFlagTagName) - { - // Ignore `error` tag if it exists, it will be added based on StatusCode + StatusDescription. - return; - } - } - else if (jaegerTag.VLong.HasValue) - { - PeerServiceResolver.InspectTag(ref state, key, jaegerTag.VLong.Value); - } - - PooledList.Add(ref state.Tags, jaegerTag); - } - private struct TagEnumerationState : IActivityEnumerator>, PeerServiceResolver.IPeerServiceState { public PooledList Tags; @@ -304,6 +310,10 @@ private struct TagEnumerationState : IActivityEnumerator activityTag) { if (activityTag.Value is Array) @@ -312,7 +322,30 @@ public bool ForEach(KeyValuePair activityTag) } else if (activityTag.Value != null) { - ProcessJaegerTag(ref this, activityTag.Key, activityTag.ToJaegerTag()); + var key = activityTag.Key; + var jaegerTag = activityTag.ToJaegerTag(); + if (jaegerTag.VStr != null) + { + PeerServiceResolver.InspectTag(ref this, key, jaegerTag.VStr); + + if (key == SpanAttributeConstants.StatusCodeKey) + { + StatusCode? statusCode = StatusHelper.GetStatusCodeForTagValue(jaegerTag.VStr); + this.StatusCode = statusCode; + return true; + } + else if (key == SpanAttributeConstants.StatusDescriptionKey) + { + this.StatusDescription = jaegerTag.VStr; + return true; + } + } + else if (jaegerTag.VLong.HasValue) + { + PeerServiceResolver.InspectTag(ref this, key, jaegerTag.VLong.Value); + } + + PooledList.Add(ref this.Tags, jaegerTag); } return true; diff --git a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs index 3da21d1d589..92714ec1212 100644 --- a/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs +++ b/test/OpenTelemetry.Exporter.Jaeger.Tests/Implementation/JaegerActivityConversionTest.cs @@ -434,15 +434,16 @@ public void JaegerActivityConverterTest_NullTagValueTest() } [Theory] - [InlineData(StatusCode.Unset, "unset")] - [InlineData(StatusCode.Ok, "Ok")] - [InlineData(StatusCode.Error, "ERROR")] - [InlineData(StatusCode.Unset, "iNvAlId")] - public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expectedStatusCode, string statusCodeTagValue) + [InlineData(StatusCode.Unset, "unset", "")] + [InlineData(StatusCode.Ok, "Ok", "")] + [InlineData(StatusCode.Error, "ERROR", "error description")] + [InlineData(StatusCode.Unset, "iNvAlId", "")] + public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expectedStatusCode, string statusCodeTagValue, string statusDescription) { // Arrange var activity = CreateTestActivity(); activity.SetTag(SpanAttributeConstants.StatusCodeKey, statusCodeTagValue); + activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, statusDescription); // Act var jaegerSpan = activity.ToJaegerSpan(); @@ -464,7 +465,14 @@ public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expected if (expectedStatusCode == StatusCode.Error) { - Assert.Contains(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName && t.VType == JaegerTagType.BOOL && (t.VBool ?? false)); + Assert.Contains( + jaegerSpan.Tags, t => + t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName && + t.VType == JaegerTagType.BOOL && (t.VBool ?? false)); + Assert.Contains( + jaegerSpan.Tags, t => + t.Key == SpanAttributeConstants.StatusDescriptionKey && + t.VType == JaegerTagType.STRING && t.VStr.Equals(statusDescription)); } else { @@ -472,6 +480,141 @@ public void JaegerActivityConverterTest_Status_ErrorFlagTest(StatusCode expected } } + [Theory] + [InlineData(ActivityStatusCode.Unset)] + [InlineData(ActivityStatusCode.Ok)] + [InlineData(ActivityStatusCode.Error)] + public void ToJaegerSpan_Activity_Status_And_StatusDescription_is_Set(ActivityStatusCode expectedStatusCode) + { + // Arrange + var activity = CreateTestActivity(); + activity.SetStatus(expectedStatusCode); + + // Act + var jaegerSpan = activity.ToJaegerSpan(); + + // Assert + if (expectedStatusCode == ActivityStatusCode.Unset) + { + Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == SpanAttributeConstants.StatusCodeKey); + } + else if (expectedStatusCode == ActivityStatusCode.Ok) + { + Assert.Equal("OK", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr); + } + + // expectedStatusCode is Error + else + { + Assert.Equal("ERROR", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr); + } + + if (expectedStatusCode == ActivityStatusCode.Error) + { + Assert.Contains( + jaegerSpan.Tags, t => + t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName && + t.VType == JaegerTagType.BOOL && (t.VBool ?? false)); + } + else + { + Assert.DoesNotContain( + jaegerSpan.Tags, t => + t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName); + } + } + + [Fact] + public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsOk() + { + // Arrange. + var activity = CreateTestActivity(); + const string TagDescriptionOnError = "Description when TagStatusCode is Error."; + activity.SetStatus(ActivityStatusCode.Ok); + activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); + activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError); + + // Enrich activity with additional tags. + activity.SetTag("myCustomTag", "myCustomTagValue"); + + // Act. + var jaegerSpan = activity.ToJaegerSpan(); + + // Assert. + Assert.Equal("OK", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr); + + Assert.Contains(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "OK"); + Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "ERROR"); + Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName); + Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == SpanAttributeConstants.StatusDescriptionKey && + t.VType == JaegerTagType.STRING && t.VStr.Equals(TagDescriptionOnError)); + + // Ensure additional Activity tags were being converted. + Assert.Contains(jaegerSpan.Tags, t => t.Key == "myCustomTag" && t.VStr == "myCustomTagValue"); + } + + [Fact] + public void ActivityStatus_Takes_precedence_Over_Status_Tags_ActivityStatusCodeIsError() + { + // Arrange. + var activity = CreateTestActivity(); + const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; + activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError); + activity.SetTag(SpanAttributeConstants.StatusCodeKey, "OK"); + + // Enrich activity with additional tags. + activity.SetTag("myCustomTag", "myCustomTagValue"); + + // Act. + var jaegerSpan = activity.ToJaegerSpan(); + + // Assert. + Assert.Contains(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "ERROR"); + Assert.Contains(jaegerSpan.Tags, t => t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName); + Assert.Contains(jaegerSpan.Tags, t => t.Key == SpanAttributeConstants.StatusDescriptionKey && + t.VType == JaegerTagType.STRING && t.VStr.Equals(StatusDescriptionOnError)); + + Assert.DoesNotContain(jaegerSpan.Tags, t => t.Key == "otel.status_code" && t.VStr == "OK"); + + // Ensure additional Activity tags were being converted. + Assert.Contains(jaegerSpan.Tags, t => t.Key == "myCustomTag" && t.VStr == "myCustomTagValue"); + } + + [Fact] + public void ActivityDescription_Takes_precedence_Over_Status_Tags_When_ActivityStatusCodeIsError() + { + // Arrange. + var activity = CreateTestActivity(); + + const string StatusDescriptionOnError = "Description when ActivityStatusCode is Error."; + const string TagDescriptionOnError = "Description when TagStatusCode is Error."; + activity.SetStatus(ActivityStatusCode.Error, StatusDescriptionOnError); + activity.SetTag(SpanAttributeConstants.StatusCodeKey, "ERROR"); + activity.SetTag(SpanAttributeConstants.StatusDescriptionKey, TagDescriptionOnError); + + // Enrich activity with additional tags. + activity.SetTag("myCustomTag", "myCustomTagValue"); + + // Act. + var jaegerSpan = activity.ToJaegerSpan(); + + // Assert. + Assert.Equal("ERROR", jaegerSpan.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).VStr); + + Assert.Contains( + jaegerSpan.Tags, t => + t.Key == JaegerActivityExtensions.JaegerErrorFlagTagName && + t.VType == JaegerTagType.BOOL && (t.VBool ?? false)); + + Assert.Contains( + jaegerSpan.Tags, t => + t.Key == SpanAttributeConstants.StatusDescriptionKey && + t.VType == JaegerTagType.STRING && t.VStr.Equals(StatusDescriptionOnError)); + + // Ensure additional Activity tags were being converted. + Assert.Contains(jaegerSpan.Tags, t => t.Key == "myCustomTag" && t.VStr == "myCustomTagValue"); + } + internal static Activity CreateTestActivity( bool setAttributes = true, Dictionary additionalAttributes = null,