From 7cd03f80a4b7fad24b25f9511418e9bcd1a910fa Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 18 Nov 2025 10:48:55 -0800 Subject: [PATCH 1/4] Support Microsoft Application Insights Override Attributes --- .../Models/RemoteDependencyData.cs | 53 +++++++++++++-- .../src/Customizations/Models/RequestData.cs | 34 ++++++++++ .../src/Internals/ActivityTagsProcessor.cs | 28 +++++++- .../src/Internals/SemanticConventions.cs | 64 +++++++++++++++++++ 4 files changed, 172 insertions(+), 7 deletions(-) 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..5d63f299d222 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 @@ -22,6 +22,7 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc activityTagsProcessor.activityType &= ~OperationType.V2; } + // Process based on operation type switch (activityTagsProcessor.activityType) { case OperationType.Http: @@ -38,6 +39,42 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc break; } + // Check for Microsoft override attributes only if present (avoids overhead for standalone OTel usage) + if (activityTagsProcessor.HasOverrideAttributes) + { + var overrideData = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyData)?.ToString(); + var overrideName = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyName)?.ToString(); + var overrideTarget = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyTarget)?.ToString(); + var overrideType = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyType)?.ToString(); + var overrideResultCode = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyResultCode)?.ToString(); + + // Apply overrides if present (these take precedence) + if (!string.IsNullOrEmpty(overrideData)) + { + Data = overrideData.Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength); + } + + if (!string.IsNullOrEmpty(overrideName)) + { + dependencyName = overrideName; + } + + if (!string.IsNullOrEmpty(overrideTarget)) + { + Target = overrideTarget.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength); + } + + if (!string.IsNullOrEmpty(overrideType)) + { + Type = overrideType.Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength); + } + + if (!string.IsNullOrEmpty(overrideResultCode)) + { + ResultCode = overrideResultCode.Truncate(SchemaConstants.RemoteDependencyData_ResultCode_MaxLength); + } + } + dependencyName ??= activity.DisplayName; Name = dependencyName?.Truncate(SchemaConstants.RemoteDependencyData_Name_MaxLength); Id = activity.Context.SpanId.ToHexString(); @@ -46,13 +83,17 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc : SchemaConstants.Duration_MaxValue; Success = activity.Status != ActivityStatusCode.Error; - if (activityTagsProcessor.AzureNamespace != null) - { - Type = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace); - } - else if (activity.Kind == ActivityKind.Internal) + // Only set Type from Azure namespace if not already set by override + if (string.IsNullOrEmpty(Type)) { - Type = "InProc"; + if (activityTagsProcessor.AzureNamespace != null) + { + Type = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace); + } + else if (activity.Kind == ActivityKind.Internal) + { + Type = "InProc"; + } } TraceHelper.AddActivityLinksToProperties(activity, 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..88ffe358d0fb 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 @@ -14,9 +14,11 @@ internal partial class RequestData public RequestData(int version, Activity activity, ref ActivityTagsProcessor activityTagsProcessor) : base(version) { string? responseCode = null; + string? requestName = null; Properties = new ChangeTrackingDictionary(); Measurements = new ChangeTrackingDictionary(); + // Process based on operation type switch (activityTagsProcessor.activityType) { case OperationType.Http | OperationType.V2: @@ -30,6 +32,38 @@ public RequestData(int version, Activity activity, ref ActivityTagsProcessor act break; } + // Check for Microsoft override attributes only if present (avoids overhead for standalone OTel usage) + if (activityTagsProcessor.HasOverrideAttributes) + { + var overrideUrl = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestUrl)?.ToString(); + var overrideName = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestName)?.ToString(); + var overrideSource = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestSource)?.ToString(); + var overrideResultCode = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestResultCode)?.ToString(); + + // Apply overrides if present (these take precedence) + if (!string.IsNullOrEmpty(overrideUrl)) + { + Url = overrideUrl.Truncate(SchemaConstants.RequestData_Url_MaxLength); + } + + if (!string.IsNullOrEmpty(overrideName)) + { + requestName = overrideName; + } + + if (!string.IsNullOrEmpty(overrideSource)) + { + Source = overrideSource.Truncate(SchemaConstants.RequestData_Source_MaxLength); + } + + if (!string.IsNullOrEmpty(overrideResultCode)) + { + responseCode = overrideResultCode; + } + } + + requestName ??= activity.DisplayName; + Name = requestName?.Truncate(SchemaConstants.RequestData_Name_MaxLength); Id = activity.Context.SpanId.ToHexString(); Duration = activity.Duration < SchemaConstants.RequestData_Duration_LessThanDays ? activity.Duration.ToString("c", CultureInfo.InvariantCulture) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs index 36a70956518a..67b96e13d775 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs @@ -59,7 +59,19 @@ internal struct ActivityTagsProcessor // Others SemanticConventions.AttributeEnduserId, SemanticConventions.AttributeEnduserPseudoId, - "microsoft.client.ip" + "microsoft.client.ip", + + // Microsoft Application Insights Override Attributes + SemanticConventions.AttributeMicrosoftDependencyData, + SemanticConventions.AttributeMicrosoftDependencyName, + SemanticConventions.AttributeMicrosoftOperationName, + SemanticConventions.AttributeMicrosoftDependencyResultCode, + SemanticConventions.AttributeMicrosoftDependencyTarget, + SemanticConventions.AttributeMicrosoftDependencyType, + SemanticConventions.AttributeMicrosoftRequestName, + SemanticConventions.AttributeMicrosoftRequestUrl, + SemanticConventions.AttributeMicrosoftRequestSource, + SemanticConventions.AttributeMicrosoftRequestResultCode }; internal static readonly HashSet s_semanticsSet = new(s_semantics); @@ -75,6 +87,8 @@ internal struct ActivityTagsProcessor public string? EndUserPseudoId { get; private set; } = null; + public bool HasOverrideAttributes { get; private set; } = false; + public ActivityTagsProcessor() { MappedTags = AzMonList.Initialize(); @@ -118,6 +132,18 @@ public void CategorizeTags(Activity activity) case SemanticConventions.AttributeEnduserPseudoId: EndUserPseudoId = tag.Value.ToString(); continue; + case SemanticConventions.AttributeMicrosoftDependencyData: + case SemanticConventions.AttributeMicrosoftDependencyName: + case SemanticConventions.AttributeMicrosoftDependencyTarget: + case SemanticConventions.AttributeMicrosoftDependencyType: + case SemanticConventions.AttributeMicrosoftDependencyResultCode: + case SemanticConventions.AttributeMicrosoftOperationName: + case SemanticConventions.AttributeMicrosoftRequestName: + case SemanticConventions.AttributeMicrosoftRequestUrl: + case SemanticConventions.AttributeMicrosoftRequestSource: + case SemanticConventions.AttributeMicrosoftRequestResultCode: + HasOverrideAttributes = true; + break; } AzMonList.Add(ref MappedTags, tag); diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs index c3db8146993e..a8e4974acd53 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs @@ -175,5 +175,69 @@ internal static class SemanticConventions public const string AttributeDbQuerySummary = "db.query.summary"; public const string AttributeDbQueryText = "db.query.text"; public const string AttributeDbStoredProcedureName = "db.stored_procedure.name"; + + // Microsoft Application Insights Override Attributes + // These attributes allow explicit mapping from Application Insights fields to OpenTelemetry attributes + // When present, these override computed values from standard semantic conventions + + /// + /// Override attribute for dependency data field. + /// When present, takes precedence over computed data from semantic conventions. + /// + public const string AttributeMicrosoftDependencyData = "microsoft.dependency.data"; + + /// + /// Override attribute for dependency name field. + /// When present, takes precedence over computed name from semantic conventions. + /// + public const string AttributeMicrosoftDependencyName = "microsoft.dependency.name"; + + /// + /// Override attribute for operation name field. + /// When present, takes precedence over computed operation name. + /// + public const string AttributeMicrosoftOperationName = "microsoft.operation_name"; + + /// + /// Override attribute for dependency result code field. + /// When present, takes precedence over computed result code from semantic conventions. + /// + public const string AttributeMicrosoftDependencyResultCode = "microsoft.dependency.resultCode"; + + /// + /// Override attribute for dependency target field. + /// When present, takes precedence over computed target from semantic conventions. + /// + public const string AttributeMicrosoftDependencyTarget = "microsoft.dependency.target"; + + /// + /// Override attribute for dependency type field. + /// When present, takes precedence over computed type from semantic conventions. + /// + internal const string AttributeMicrosoftDependencyType = "microsoft.dependency.type"; + + /// + /// Override attribute for request name field. + /// When present, takes precedence over Activity.DisplayName. + /// + internal const string AttributeMicrosoftRequestName = "microsoft.request.name"; + + /// + /// Override attribute for request URL field. + /// When present, takes precedence over computed URL from semantic conventions. + /// + internal const string AttributeMicrosoftRequestUrl = "microsoft.request.url"; + + /// + /// Override attribute for request source field. + /// When present, takes precedence over computed source from semantic conventions. + /// + internal const string AttributeMicrosoftRequestSource = "microsoft.request.source"; + + /// + /// Override attribute for request result code field. + /// When present, takes precedence over computed result code from semantic conventions. + /// + internal const string AttributeMicrosoftRequestResultCode = "microsoft.request.resultCode"; } } From d27973f4af2f9b0e01fbdbd53117a1dc20dbe50f Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 18 Nov 2025 11:58:27 -0800 Subject: [PATCH 2/4] Test updates. --- .../src/Customizations/Models/RequestData.cs | 1 - .../src/Internals/AzMonList.cs | 2 +- .../RemoteDependencyDataTests.cs | 149 +++++++++++++++ .../RequestDataTests.cs | 169 ++++++++++++++++++ 4 files changed, 319 insertions(+), 2 deletions(-) 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 88ffe358d0fb..2ed37c5558aa 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 @@ -62,7 +62,6 @@ public RequestData(int version, Activity activity, ref ActivityTagsProcessor act } } - requestName ??= activity.DisplayName; Name = requestName?.Truncate(SchemaConstants.RequestData_Name_MaxLength); Id = activity.Context.SpanId.ToHexString(); Duration = activity.Duration < SchemaConstants.RequestData_Duration_LessThanDays diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonList.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonList.cs index af403d1cd8d2..dd53852d39cf 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonList.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonList.cs @@ -63,7 +63,7 @@ public static void Clear(ref AzMonList list) for (int i = 0; i < length; i++) { - if (ReferenceEquals(list[i].Key, tagName)) + if (string.Equals(list[i].Key, tagName, StringComparison.Ordinal)) { return list[i].Value; } 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..16805f967fc6 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,154 @@ public void VerifyAllDependenciesSetTargetViaServerAddressAndPort() Assert.Equal("unitTestAddress:unitTestPort", remoteDependencyData.Target); } + + [Theory] + [InlineData(SemanticConventions.AttributeMicrosoftDependencyType, "CustomType")] + [InlineData(SemanticConventions.AttributeMicrosoftDependencyData, "CustomData")] + [InlineData(SemanticConventions.AttributeMicrosoftDependencyName, "CustomName")] + [InlineData(SemanticConventions.AttributeMicrosoftDependencyTarget, "CustomTarget")] + [InlineData(SemanticConventions.AttributeMicrosoftDependencyResultCode, "CustomResult")] + public void MicrosoftOverrideAttributeTakesPrecedenceOverSemanticConventions(string overrideAttribute, string expectedValue) + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Client); + Assert.NotNull(activity); + + // Set semantic convention attributes (HTTP dependency) + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, "https://example.com/api"); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + + // Set Microsoft override attribute + activity.SetTag(overrideAttribute, expectedValue); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor); + + // Verify override took precedence + switch (overrideAttribute) + { + case SemanticConventions.AttributeMicrosoftDependencyType: + Assert.Equal(expectedValue, remoteDependencyData.Type); + break; + case SemanticConventions.AttributeMicrosoftDependencyData: + Assert.Equal(expectedValue, remoteDependencyData.Data); + break; + case SemanticConventions.AttributeMicrosoftDependencyName: + Assert.Equal(expectedValue, remoteDependencyData.Name); + break; + case SemanticConventions.AttributeMicrosoftDependencyTarget: + Assert.Equal(expectedValue, remoteDependencyData.Target); + break; + case SemanticConventions.AttributeMicrosoftDependencyResultCode: + Assert.Equal(expectedValue, remoteDependencyData.ResultCode); + break; + } + } + + [Theory] + [InlineData("Http", SemanticConventions.AttributeHttpMethod, "GET", SemanticConventions.AttributeHttpUrl, "https://api.example.com")] + [InlineData("SQL", SemanticConventions.AttributeDbSystem, "mssql", SemanticConventions.AttributeDbStatement, "SELECT * FROM users")] + [InlineData("servicebus", SemanticConventions.AttributeMessagingSystem, "servicebus", SemanticConventions.AttributeMessagingDestinationName, "myqueue")] + public void MicrosoftOverrideAttributesWorkWithAllDependencyTypes(string expectedDefaultType, string conventionKey1, string conventionValue1, string conventionKey2, string conventionValue2) + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Client); + Assert.NotNull(activity); + + // Set semantic convention attributes for specific dependency type + activity.SetTag(conventionKey1, conventionValue1); + activity.SetTag(conventionKey2, conventionValue2); + + // Without override - verify semantic conventions are used + var activityTagsProcessor1 = TraceHelper.EnumerateActivityTags(activity); + var remoteDependencyData1 = new RemoteDependencyData(2, activity, ref activityTagsProcessor1); + Assert.Equal(expectedDefaultType, remoteDependencyData1.Type); + + // Add override attributes + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyType, "OverriddenType"); + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyData, "OverriddenData"); + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyTarget, "OverriddenTarget"); + + // With override - verify overrides take precedence + var activityTagsProcessor2 = TraceHelper.EnumerateActivityTags(activity); + var remoteDependencyData2 = new RemoteDependencyData(2, activity, ref activityTagsProcessor2); + Assert.Equal("OverriddenType", remoteDependencyData2.Type); + Assert.Equal("OverriddenData", remoteDependencyData2.Data); + Assert.Equal("OverriddenTarget", remoteDependencyData2.Target); + } + + [Fact] + public void HasOverrideAttributesFlagIsSetWhenOverrideAttributesPresent() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Client); + Assert.NotNull(activity); + + // Without override attributes + var activityTagsProcessor1 = TraceHelper.EnumerateActivityTags(activity); + Assert.False(activityTagsProcessor1.HasOverrideAttributes); + + // With override attribute + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyType, "CustomType"); + var activityTagsProcessor2 = TraceHelper.EnumerateActivityTags(activity); + Assert.True(activityTagsProcessor2.HasOverrideAttributes); + } + + [Fact] + public void MultipleOverrideAttributesAllApplied() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Client); + Assert.NotNull(activity); + + // Set semantic convention attributes (HTTP) + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, "https://example.com/api"); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + + // Set all override attributes + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyType, "CustomType"); + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyData, "CustomData"); + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyName, "CustomName"); + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyTarget, "CustomTarget"); + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyResultCode, "CustomResult"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor); + + // Verify all overrides applied + Assert.Equal("CustomType", remoteDependencyData.Type); + Assert.Equal("CustomData", remoteDependencyData.Data); + Assert.Equal("CustomName", remoteDependencyData.Name); + Assert.Equal("CustomTarget", remoteDependencyData.Target); + Assert.Equal("CustomResult", remoteDependencyData.ResultCode); + } + + [Fact] + public void PartialOverridesOnlyAffectSpecifiedFields() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Client); + Assert.NotNull(activity); + + var httpUrl = "https://example.com/api"; + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + + // Only override Type, leave others from semantic conventions + activity.SetTag(SemanticConventions.AttributeMicrosoftDependencyType, "CustomType"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor); + + // Type is overridden + Assert.Equal("CustomType", remoteDependencyData.Type); + // Data comes from semantic conventions + Assert.Equal(httpUrl, remoteDependencyData.Data); + // ResultCode comes from semantic conventions + Assert.Equal("200", remoteDependencyData.ResultCode); + } } } 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..61a57d26664c 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 @@ -240,5 +240,174 @@ private ActivityLink AddActivityLink(long enqueuedTime) return link; } + + [Theory] + [InlineData(SemanticConventions.AttributeMicrosoftRequestName, "CustomName")] + [InlineData(SemanticConventions.AttributeMicrosoftRequestUrl, "https://custom.url/path")] + [InlineData(SemanticConventions.AttributeMicrosoftRequestSource, "CustomSource")] + [InlineData(SemanticConventions.AttributeMicrosoftRequestResultCode, "CustomResult")] + public void MicrosoftOverrideAttributeTakesPrecedenceOverSemanticConventions(string overrideAttribute, string expectedValue) + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Server); + Assert.NotNull(activity); + + // Set semantic convention attributes (HTTP request) + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, "https://example.com/api"); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + + // Set Microsoft override attribute + activity.SetTag(overrideAttribute, expectedValue); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + + // Verify override took precedence + switch (overrideAttribute) + { + case SemanticConventions.AttributeMicrosoftRequestName: + Assert.Equal(expectedValue, requestData.Name); + break; + case SemanticConventions.AttributeMicrosoftRequestUrl: + Assert.Equal(expectedValue, requestData.Url); + break; + case SemanticConventions.AttributeMicrosoftRequestSource: + Assert.Equal(expectedValue, requestData.Source); + break; + case SemanticConventions.AttributeMicrosoftRequestResultCode: + Assert.Equal(expectedValue, requestData.ResponseCode); + break; + } + } + + [Theory] + [InlineData(ActivityKind.Server, SemanticConventions.AttributeHttpMethod, "GET", SemanticConventions.AttributeHttpUrl, "https://api.example.com")] + [InlineData(ActivityKind.Consumer, SemanticConventions.AttributeMessagingSystem, "servicebus", SemanticConventions.AttributeMessagingDestinationName, "myqueue")] + public void MicrosoftOverrideAttributesWorkWithAllRequestTypes(ActivityKind activityKind, string conventionKey1, string conventionValue1, string conventionKey2, string conventionValue2) + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, activityKind); + Assert.NotNull(activity); + + // Set semantic convention attributes for specific request type + activity.SetTag(conventionKey1, conventionValue1); + activity.SetTag(conventionKey2, conventionValue2); + + // Without override - verify semantic conventions are used + var activityTagsProcessor1 = TraceHelper.EnumerateActivityTags(activity); + var requestData1 = new RequestData(2, activity, ref activityTagsProcessor1); + + if (activityKind == ActivityKind.Server) + { + // HTTP request + Assert.NotNull(requestData1.Url); + } + + // Add override attributes + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestUrl, "https://overridden.url"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestSource, "OverriddenSource"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestResultCode, "999"); + + // With override - verify overrides take precedence + var activityTagsProcessor2 = TraceHelper.EnumerateActivityTags(activity); + var requestData2 = new RequestData(2, activity, ref activityTagsProcessor2); + Assert.Equal("https://overridden.url", requestData2.Url); + Assert.Equal("OverriddenSource", requestData2.Source); + Assert.Equal("999", requestData2.ResponseCode); + } + + [Fact] + public void HasOverrideAttributesFlagIsSetWhenRequestOverrideAttributesPresent() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Server); + Assert.NotNull(activity); + + // Without override attributes + var activityTagsProcessor1 = TraceHelper.EnumerateActivityTags(activity); + Assert.False(activityTagsProcessor1.HasOverrideAttributes); + + // With override attribute + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestName, "CustomName"); + var activityTagsProcessor2 = TraceHelper.EnumerateActivityTags(activity); + Assert.True(activityTagsProcessor2.HasOverrideAttributes); + } + + [Fact] + public void MultipleRequestOverrideAttributesAllApplied() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Server); + Assert.NotNull(activity); + + // Set semantic convention attributes (HTTP) + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, "https://example.com/api"); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + + // Set all request override attributes + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestName, "CustomName"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestUrl, "https://custom.url"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestSource, "CustomSource"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestResultCode, "CustomResult"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + + // Verify all overrides applied + Assert.Equal("CustomName", requestData.Name); + Assert.Equal("https://custom.url", requestData.Url); + Assert.Equal("CustomSource", requestData.Source); + Assert.Equal("CustomResult", requestData.ResponseCode); + } + + [Fact] + public void PartialRequestOverridesOnlyAffectSpecifiedFields() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Server); + Assert.NotNull(activity); + + var httpUrl = "https://example.com/api"; + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + + // Only override Name and Source, leave others from semantic conventions + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestName, "CustomName"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestSource, "CustomSource"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + + // Name and Source are overridden + Assert.Equal("CustomName", requestData.Name); + Assert.Equal("CustomSource", requestData.Source); + // Url comes from semantic conventions + Assert.Equal(httpUrl, requestData.Url); + // ResponseCode comes from semantic conventions + Assert.Equal("200", requestData.ResponseCode); + } + + [Fact] + public void RequestOverrideAttributesWorkWithMicrosoftOperationName() + { + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity(ActivityName, ActivityKind.Server); + Assert.NotNull(activity); + + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + activity.SetTag(SemanticConventions.AttributeMicrosoftOperationName, "CustomOperationName"); + activity.SetTag(SemanticConventions.AttributeMicrosoftRequestName, "CustomRequestName"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + // Verify HasOverrideAttributes is set for microsoft.operation_name + Assert.True(activityTagsProcessor.HasOverrideAttributes); + + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + Assert.Equal("CustomRequestName", requestData.Name); + } } } From 5b2634722cf334baf8d298313a4abe2ac61cd52f Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 18 Nov 2025 16:23:51 -0800 Subject: [PATCH 3/4] copilot feedback --- .../src/Customizations/Models/RemoteDependencyData.cs | 11 +++++++---- .../src/Internals/SemanticConventions.cs | 10 +++++----- .../src/Internals/TraceHelper.cs | 6 +++++- 3 files changed, 17 insertions(+), 10 deletions(-) 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 5d63f299d222..f077499728b6 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 @@ -83,14 +83,17 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc : SchemaConstants.Duration_MaxValue; Success = activity.Status != ActivityStatusCode.Error; - // Only set Type from Azure namespace if not already set by override - if (string.IsNullOrEmpty(Type)) + // Set Type from Azure namespace if present (unless already set by override attribute) + if (activityTagsProcessor.AzureNamespace != null) { - if (activityTagsProcessor.AzureNamespace != null) + if (string.IsNullOrEmpty(Type)) { Type = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace); } - else if (activity.Kind == ActivityKind.Internal) + } + else if (activity.Kind == ActivityKind.Internal) + { + if (string.IsNullOrEmpty(Type)) { Type = "InProc"; } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs index a8e4974acd53..09a776e6a9d9 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs @@ -214,30 +214,30 @@ internal static class SemanticConventions /// Override attribute for dependency type field. /// When present, takes precedence over computed type from semantic conventions. /// - internal const string AttributeMicrosoftDependencyType = "microsoft.dependency.type"; + public const string AttributeMicrosoftDependencyType = "microsoft.dependency.type"; /// /// Override attribute for request name field. /// When present, takes precedence over Activity.DisplayName. /// - internal const string AttributeMicrosoftRequestName = "microsoft.request.name"; + public const string AttributeMicrosoftRequestName = "microsoft.request.name"; /// /// Override attribute for request URL field. /// When present, takes precedence over computed URL from semantic conventions. /// - internal const string AttributeMicrosoftRequestUrl = "microsoft.request.url"; + public const string AttributeMicrosoftRequestUrl = "microsoft.request.url"; /// /// Override attribute for request source field. /// When present, takes precedence over computed source from semantic conventions. /// - internal const string AttributeMicrosoftRequestSource = "microsoft.request.source"; + public const string AttributeMicrosoftRequestSource = "microsoft.request.source"; /// /// Override attribute for request result code field. /// When present, takes precedence over computed result code from semantic conventions. /// - internal const string AttributeMicrosoftRequestResultCode = "microsoft.request.resultCode"; + public const string AttributeMicrosoftRequestResultCode = "microsoft.request.resultCode"; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs index 8d10e0317446..42e689ee50dc 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs @@ -50,7 +50,11 @@ internal static (List TelemetryItems, TelemetrySchemaTypeCounter { case TelemetryType.Request: var requestData = new RequestData(Version, activity, ref activityTagsProcessor); - requestData.Name = telemetryItem.Tags.TryGetValue(ContextTagKeys.AiOperationName.ToString(), out var operationName) ? operationName.Truncate(SchemaConstants.RequestData_Name_MaxLength) : activity.DisplayName.Truncate(SchemaConstants.RequestData_Name_MaxLength); + // Only set Name if not already set by override attribute + if (string.IsNullOrEmpty(requestData.Name)) + { + requestData.Name = telemetryItem.Tags.TryGetValue(ContextTagKeys.AiOperationName.ToString(), out var operationName) ? operationName.Truncate(SchemaConstants.RequestData_Name_MaxLength) : activity.DisplayName.Truncate(SchemaConstants.RequestData_Name_MaxLength); + } telemetryItem.Data = new MonitorBase { BaseType = "RequestData", From c98402360b4ecaed710a87a7b01976e33a6056f6 Mon Sep 17 00:00:00 2001 From: Rajkumar Rangaraj Date: Tue, 18 Nov 2025 18:53:32 -0800 Subject: [PATCH 4/4] fix accidental public api change. --- .../src/Internals/SdkVersionUtils.cs | 2 +- .../CustomerSdkStats/CustomerSdkStatsDimensionsTests.cs | 2 +- .../ResourceExtensionsTests.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SdkVersionUtils.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SdkVersionUtils.cs index fd1581a71669..9f8018b3b2c0 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SdkVersionUtils.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SdkVersionUtils.cs @@ -148,7 +148,7 @@ private static string GetSdkVersion() /// /// Specifies the type of SDK for telemetry identification and version labeling. /// - public enum SdkVersionType + internal enum SdkVersionType { /// Default Azure Monitor OpenTelemetry Exporter. Exporter, diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CustomerSdkStats/CustomerSdkStatsDimensionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CustomerSdkStats/CustomerSdkStatsDimensionsTests.cs index ff5b65ca2a33..d11a180b3751 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CustomerSdkStats/CustomerSdkStatsDimensionsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CustomerSdkStats/CustomerSdkStatsDimensionsTests.cs @@ -17,7 +17,7 @@ public class CustomerSdkStatsDimensionsTests [InlineData(SdkVersionType.ShimAspNetCore, "dotnet-core")] [InlineData(SdkVersionType.ShimWorkerService, "dotnet-worker")] [InlineData(SdkVersionType.ShimWeb, "dotnet-web")] - public void GetBaseTags_SetsCorrectLanguageDimensionForEachSdkVersionType(SdkVersionType versionType, string expectedLanguage) + internal void GetBaseTags_SetsCorrectLanguageDimensionForEachSdkVersionType(SdkVersionType versionType, string expectedLanguage) { // Arrange var originalVersionType = SdkVersionUtils.s_sdkVersionType; diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ResourceExtensionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ResourceExtensionsTests.cs index 1a97160cbddd..b5cbffd3fc03 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ResourceExtensionsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/ResourceExtensionsTests.cs @@ -188,7 +188,7 @@ public void DoesNotSetSdkDistroLabelForWrongValueFromResource() [InlineData("Microsoft.ApplicationInsights.AspNetCore", "shc", SdkVersionType.ShimAspNetCore)] [InlineData("Microsoft.ApplicationInsights.WorkerService", "shw", SdkVersionType.ShimWorkerService)] [InlineData("Microsoft.ApplicationInsights.Web", "shf", SdkVersionType.ShimWeb)] - public void SetsSdkShimLabelForAllShimPackages(string distroName, string expectedLabel, SdkVersionType expectedType) + internal void SetsSdkShimLabelForAllShimPackages(string distroName, string expectedLabel, SdkVersionType expectedType) { // SDK version is static, preserve to clean up later. var sdkVersion = SdkVersionUtils.s_sdkVersion;