diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs index 79ddd09b638b..7ea10270f586 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs @@ -56,6 +56,13 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc } TraceHelper.AddActivityLinksToProperties(activity, ref activityTagsProcessor.UnMappedTags); + + // Add StatusDescription as a custom property if present + if (!string.IsNullOrEmpty(activity.StatusDescription)) + { + TraceHelper.AddKvpToDictionary(Properties, "otel.status_description", activity.StatusDescription!); + } + TraceHelper.AddPropertiesToTelemetry(Properties, ref activityTagsProcessor.UnMappedTags); } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RequestData.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RequestData.cs index aabb02b9a06a..4b9b184c15f2 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RequestData.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RequestData.cs @@ -47,6 +47,12 @@ public RequestData(int version, Activity activity, ref ActivityTagsProcessor act TraceHelper.AddActivityLinksToProperties(activity, ref activityTagsProcessor.UnMappedTags); } + // Add StatusDescription as a custom property if present + if (!string.IsNullOrEmpty(activity.StatusDescription)) + { + TraceHelper.AddKvpToDictionary(Properties, "otel.status_description", activity.StatusDescription!); + } + TraceHelper.AddPropertiesToTelemetry(Properties, ref activityTagsProcessor.UnMappedTags); } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs index a9c4d239d75a..e0bf8abaecf7 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs @@ -266,5 +266,61 @@ public void VerifyAllDependenciesSetTargetViaServerAddressAndPort() Assert.Equal("unitTestAddress:unitTestPort", remoteDependencyData.Target); } + + [Fact] + public void ValidateRemoteDependencyDataWithStatusDescription() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Client, + parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), + startTime: DateTime.UtcNow); + + Assert.NotNull(activity); + activity.Stop(); + + var httpUrl = "https://www.foo.bar/search"; + var statusDescription = "Dependency call failed due to network error"; + activity.SetStatus(ActivityStatusCode.Error, statusDescription); + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "503"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor); + + Assert.False(remoteDependencyData.Success); + Assert.True(remoteDependencyData.Properties.ContainsKey("otel.status_description")); + Assert.Equal(statusDescription, remoteDependencyData.Properties["otel.status_description"]); + } + + [Fact] + public void ValidateRemoteDependencyDataWithoutStatusDescription() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Client, + parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), + startTime: DateTime.UtcNow); + + Assert.NotNull(activity); + activity.Stop(); + + var httpUrl = "https://www.foo.bar/search"; + activity.SetStatus(ActivityStatusCode.Error); // No description + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "503"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor); + + Assert.False(remoteDependencyData.Success); + Assert.False(remoteDependencyData.Properties.ContainsKey("otel.status_description")); + } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataTests.cs index 51d61aaa7368..68669d40abaf 100755 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataTests.cs @@ -230,6 +230,60 @@ public void RequestDataTimeSinceEnqueuedInvalidEmqueuedTime() Assert.False(requestData.Measurements.TryGetValue("timeSinceEnqueued", out var timeInQueue)); } + [Fact] + public void ValidateRequestDataWithStatusDescription() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Server, + parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), + startTime: DateTime.UtcNow); + Assert.NotNull(activity); + activity.Stop(); + + var httpUrl = "https://www.foo.bar/search"; + var statusDescription = "Request failed due to timeout"; + activity.SetStatus(ActivityStatusCode.Error, statusDescription); + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "500"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + + Assert.False(requestData.Success); + Assert.True(requestData.Properties.ContainsKey("otel.status_description")); + Assert.Equal(statusDescription, requestData.Properties["otel.status_description"]); + } + + [Fact] + public void ValidateRequestDataWithoutStatusDescription() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Server, + parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), + startTime: DateTime.UtcNow); + Assert.NotNull(activity); + activity.Stop(); + + var httpUrl = "https://www.foo.bar/search"; + activity.SetStatus(ActivityStatusCode.Error); // No description + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "500"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + + Assert.False(requestData.Success); + Assert.False(requestData.Properties.ContainsKey("otel.status_description")); + } + private ActivityLink AddActivityLink(long enqueuedTime) { ActivityTagsCollection tags = new ActivityTagsCollection