diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/TelemetryItem.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/TelemetryItem.cs index f371a9aaf0f3..17d367bf5cb1 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/TelemetryItem.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/TelemetryItem.cs @@ -17,6 +17,7 @@ internal partial class TelemetryItem { private static volatile string? s_cloudRoleNameOverride; private static volatile string? s_cloudRoleInstanceOverride; + private static volatile string? s_componentVersionOverride; public TelemetryItem(Activity activity, ref ActivityTagsProcessor activityTagsProcessor, AzureMonitorResource? resource, string instrumentationKey, float sampleRate) : this(activity.GetTelemetryType() == TelemetryType.Request ? "Request" : "RemoteDependency", FormatUtcTimestamp(activity.StartTimeUtc)) @@ -28,7 +29,7 @@ public TelemetryItem(Activity activity, ref ActivityTagsProcessor activityTagsPr Tags[ContextTagKeys.AiOperationId.ToString()] = activity.TraceId.ToHexString(); - string? microsoftClientIp = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, "microsoft.client.ip")?.ToString(); + string? microsoftClientIp = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftClientIp)?.ToString(); // Check for microsoft.operation_name override (applies to both request and dependency) string? overrideOperationName = activityTagsProcessor.HasOverrideAttributes @@ -88,6 +89,12 @@ public TelemetryItem(Activity activity, ref ActivityTagsProcessor activityTagsPr } SetUserIdAndAuthenticatedUserId(ref activityTagsProcessor); + + if (activityTagsProcessor.HasOverrideAttributes) + { + SetOverrideContextTags(ref activityTagsProcessor); + } + SetResourceSdkVersionAndIkey(resource, instrumentationKey); if (sampleRate != 100f) @@ -102,6 +109,8 @@ public TelemetryItem(string name, TelemetryItem telemetryItem, ActivitySpanId ac Tags[ContextTagKeys.AiOperationParentId.ToString()] = activitySpanId.ToHexString(); Tags[ContextTagKeys.AiOperationId.ToString()] = telemetryItem.Tags[ContextTagKeys.AiOperationId.ToString()]; + // Copy user agent from the parent TelemetryItem created by the Activity constructor, + // where it was set from ActivityTagsProcessor.MappedTags. if (telemetryItem.Tags.TryGetValue("ai.user.userAgent", out string? userAgent)) { // todo: update swagger to include this key. @@ -112,6 +121,13 @@ public TelemetryItem(string name, TelemetryItem telemetryItem, ActivitySpanId ac Tags[ContextTagKeys.AiCloudRoleInstance.ToString()] = telemetryItem.Tags[ContextTagKeys.AiCloudRoleInstance.ToString()].Truncate(SchemaConstants.Tags_AiCloudRoleInstance_MaxLength); Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = SdkVersionUtils.s_sdkVersion.Truncate(SchemaConstants.Tags_AiInternalSdkVersion_MaxLength); Tags[ContextTagKeys.AiApplicationVer.ToString()] = telemetryItem.Tags[ContextTagKeys.AiApplicationVer.ToString()].Truncate(SchemaConstants.Tags_AiApplicationVer_MaxLength); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiSessionId.ToString()); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiDeviceId.ToString()); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiDeviceModel.ToString()); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiDeviceType.ToString()); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiDeviceOSVersion.ToString()); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiOperationSyntheticSource.ToString()); + CopyTagIfPresent(telemetryItem, ContextTagKeys.AiUserAccountId.ToString()); InstrumentationKey = telemetryItem.InstrumentationKey; if (telemetryItem.SampleRate != 100f) @@ -160,6 +176,41 @@ public TelemetryItem (string name, LogRecord logRecord, AzureMonitorResource? re { Tags[ContextTagKeys.AiOperationName.ToString()] = logContext.OperationName.Truncate(SchemaConstants.Tags_AiOperationName_MaxLength); } + + if (logContext.SessionId != null) + { + Tags[ContextTagKeys.AiSessionId.ToString()] = logContext.SessionId.Truncate(SchemaConstants.Tags_AiSessionId_MaxLength); + } + + if (logContext.DeviceId != null) + { + Tags[ContextTagKeys.AiDeviceId.ToString()] = logContext.DeviceId.Truncate(SchemaConstants.Tags_AiDeviceId_MaxLength); + } + + if (logContext.DeviceModel != null) + { + Tags[ContextTagKeys.AiDeviceModel.ToString()] = logContext.DeviceModel.Truncate(SchemaConstants.Tags_AiDeviceModel_MaxLength); + } + + if (logContext.DeviceType != null) + { + Tags[ContextTagKeys.AiDeviceType.ToString()] = logContext.DeviceType.Truncate(SchemaConstants.Tags_AiDeviceType_MaxLength); + } + + if (logContext.DeviceOsVersion != null) + { + Tags[ContextTagKeys.AiDeviceOSVersion.ToString()] = logContext.DeviceOsVersion.Truncate(SchemaConstants.Tags_AiDeviceOsVersion_MaxLength); + } + + if (logContext.SyntheticSource != null) + { + Tags[ContextTagKeys.AiOperationSyntheticSource.ToString()] = logContext.SyntheticSource.Truncate(SchemaConstants.Tags_AiOperationSyntheticSource_MaxLength); + } + + if (logContext.UserAccountId != null) + { + Tags[ContextTagKeys.AiUserAccountId.ToString()] = logContext.UserAccountId.Truncate(SchemaConstants.Tags_AiUserAccountId_MaxLength); + } } InstrumentationKey = instrumentationKey; @@ -196,6 +247,12 @@ private void SetResourceSdkVersionAndIkey(AzureMonitorResource? resource, string { Tags[ContextTagKeys.AiCloudRoleInstance.ToString()] = roleInstance.Truncate(SchemaConstants.Tags_AiCloudRoleInstance_MaxLength); } + + var componentVersion = s_componentVersionOverride ?? (s_componentVersionOverride = Environment.GetEnvironmentVariable(EnvironmentVariableConstants.APPLICATIONINSIGHTS_COMPONENT_VERSION) ?? string.Empty); + if (componentVersion.Length > 0) + { + Tags[ContextTagKeys.AiApplicationVer.ToString()] = componentVersion.Truncate(SchemaConstants.Tags_AiApplicationVer_MaxLength); + } } /// @@ -205,6 +262,7 @@ internal static void ResetEnvironmentVariableOverrides() { s_cloudRoleNameOverride = null; s_cloudRoleInstanceOverride = null; + s_componentVersionOverride = null; } internal static DateTimeOffset FormatUtcTimestamp(System.DateTime utcTimestamp) @@ -212,6 +270,15 @@ internal static DateTimeOffset FormatUtcTimestamp(System.DateTime utcTimestamp) return DateTime.SpecifyKind(utcTimestamp, DateTimeKind.Utc); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void CopyTagIfPresent(TelemetryItem source, string key) + { + if (source.Tags.TryGetValue(key, out string? value) && value != null) + { + Tags[key] = value; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetUserIdAndAuthenticatedUserId(ref ActivityTagsProcessor activityTagsProcessor) { @@ -225,5 +292,27 @@ private void SetUserIdAndAuthenticatedUserId(ref ActivityTagsProcessor activityT Tags[ContextTagKeys.AiUserId.ToString()] = activityTagsProcessor.EndUserPseudoId.Truncate(SchemaConstants.Tags_AiUserId_MaxLength); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetOverrideContextTags(ref ActivityTagsProcessor activityTagsProcessor) + { + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeMicrosoftSessionId, ContextTagKeys.AiSessionId, SchemaConstants.Tags_AiSessionId_MaxLength); + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeAiDeviceId, ContextTagKeys.AiDeviceId, SchemaConstants.Tags_AiDeviceId_MaxLength); + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeAiDeviceModel, ContextTagKeys.AiDeviceModel, SchemaConstants.Tags_AiDeviceModel_MaxLength); + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeAiDeviceType, ContextTagKeys.AiDeviceType, SchemaConstants.Tags_AiDeviceType_MaxLength); + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeAiDeviceOsVersion, ContextTagKeys.AiDeviceOSVersion, SchemaConstants.Tags_AiDeviceOsVersion_MaxLength); + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeMicrosoftSyntheticSource, ContextTagKeys.AiOperationSyntheticSource, SchemaConstants.Tags_AiOperationSyntheticSource_MaxLength); + SetTagFromMappedTags(ref activityTagsProcessor, SemanticConventions.AttributeMicrosoftUserAccountId, ContextTagKeys.AiUserAccountId, SchemaConstants.Tags_AiUserAccountId_MaxLength); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetTagFromMappedTags(ref ActivityTagsProcessor activityTagsProcessor, string attributeKey, ContextTagKeys contextTagKey, int maxLength) + { + var value = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, attributeKey)?.ToString(); + if (value != null) + { + Tags[contextTagKey.ToString()] = value.Truncate(maxLength); + } + } } } 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 67b96e13d775..e04f42bf5779 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,7 @@ internal struct ActivityTagsProcessor // Others SemanticConventions.AttributeEnduserId, SemanticConventions.AttributeEnduserPseudoId, - "microsoft.client.ip", + SemanticConventions.AttributeMicrosoftClientIp, // Microsoft Application Insights Override Attributes SemanticConventions.AttributeMicrosoftDependencyData, @@ -71,7 +71,16 @@ internal struct ActivityTagsProcessor SemanticConventions.AttributeMicrosoftRequestName, SemanticConventions.AttributeMicrosoftRequestUrl, SemanticConventions.AttributeMicrosoftRequestSource, - SemanticConventions.AttributeMicrosoftRequestResultCode + SemanticConventions.AttributeMicrosoftRequestResultCode, + + // Context tag attributes from Application Insights shim + SemanticConventions.AttributeMicrosoftSessionId, + SemanticConventions.AttributeAiDeviceId, + SemanticConventions.AttributeAiDeviceModel, + SemanticConventions.AttributeAiDeviceType, + SemanticConventions.AttributeAiDeviceOsVersion, + SemanticConventions.AttributeMicrosoftSyntheticSource, + SemanticConventions.AttributeMicrosoftUserAccountId, }; internal static readonly HashSet s_semanticsSet = new(s_semantics); @@ -132,6 +141,13 @@ public void CategorizeTags(Activity activity) case SemanticConventions.AttributeEnduserPseudoId: EndUserPseudoId = tag.Value.ToString(); continue; + case SemanticConventions.AttributeMicrosoftSessionId: + case SemanticConventions.AttributeAiDeviceId: + case SemanticConventions.AttributeAiDeviceModel: + case SemanticConventions.AttributeAiDeviceType: + case SemanticConventions.AttributeAiDeviceOsVersion: + case SemanticConventions.AttributeMicrosoftSyntheticSource: + case SemanticConventions.AttributeMicrosoftUserAccountId: case SemanticConventions.AttributeMicrosoftDependencyData: case SemanticConventions.AttributeMicrosoftDependencyName: case SemanticConventions.AttributeMicrosoftDependencyTarget: diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogContextInfo.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogContextInfo.cs index 6393432acf13..b4e0394ca099 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogContextInfo.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogContextInfo.cs @@ -14,6 +14,13 @@ internal struct LogContextInfo public string? EndUserId; public string? UserAgent; public string? OperationName; + public string? SessionId; + public string? DeviceId; + public string? DeviceModel; + public string? DeviceType; + public string? DeviceOsVersion; + public string? SyntheticSource; + public string? UserAccountId; /// /// Returns true if any context field has been set. @@ -23,6 +30,13 @@ internal struct LogContextInfo EndUserPseudoId != null || EndUserId != null || UserAgent != null || - OperationName != null; + OperationName != null || + SessionId != null || + DeviceId != null || + DeviceModel != null || + DeviceType != null || + DeviceOsVersion != null || + SyntheticSource != null || + UserAccountId != null; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogsHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogsHelper.cs index 5e2a8e5b70fd..d0c6cf609f7e 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogsHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/LogsHelper.cs @@ -26,6 +26,13 @@ internal static class LogsHelper private const string EndUserIdAttributeName = "enduser.id"; private const string UserAgentOriginalAttributeName = "user_agent.original"; private const string OperationNameAttributeName = "microsoft.operation_name"; + private const string SessionIdAttributeName = "microsoft.session.id"; + private const string DeviceIdAttributeName = "ai.device.id"; + private const string DeviceModelAttributeName = "ai.device.model"; + private const string DeviceTypeAttributeName = "ai.device.type"; + private const string DeviceOsVersionAttributeName = "ai.device.osVersion"; + private const string SyntheticSourceAttributeName = "microsoft.synthetic_source"; + private const string UserAccountIdAttributeName = "microsoft.user.account_id"; private const string AvailabilityIdAttributeName = "microsoft.availability.id"; private const string AvailabilityNameAttributeName = "microsoft.availability.name"; private const string AvailabilityDurationAttributeName = "microsoft.availability.duration"; @@ -169,6 +176,27 @@ internal static void ProcessLogRecordProperties(LogRecord logRecord, IDictionary case OperationNameAttributeName: logContext.OperationName = item.Value?.ToString(); break; + case SessionIdAttributeName: + logContext.SessionId = item.Value?.ToString(); + break; + case DeviceIdAttributeName: + logContext.DeviceId = item.Value?.ToString(); + break; + case DeviceModelAttributeName: + logContext.DeviceModel = item.Value?.ToString(); + break; + case DeviceTypeAttributeName: + logContext.DeviceType = item.Value?.ToString(); + break; + case DeviceOsVersionAttributeName: + logContext.DeviceOsVersion = item.Value?.ToString(); + break; + case SyntheticSourceAttributeName: + logContext.SyntheticSource = item.Value?.ToString(); + break; + case UserAccountIdAttributeName: + logContext.UserAccountId = item.Value?.ToString(); + break; default: // Note: if Key exceeds MaxLength, the entire KVP will be dropped. if (item.Key.Length <= SchemaConstants.MessageData_Properties_MaxKeyLength && item.Value != null) @@ -287,6 +315,27 @@ internal static void ProcessLogRecordProperties(LogRecord logRecord, IDictionary case OperationNameAttributeName: logContext.OperationName = item.Value?.ToString(); break; + case SessionIdAttributeName: + logContext.SessionId = item.Value?.ToString(); + break; + case DeviceIdAttributeName: + logContext.DeviceId = item.Value?.ToString(); + break; + case DeviceModelAttributeName: + logContext.DeviceModel = item.Value?.ToString(); + break; + case DeviceTypeAttributeName: + logContext.DeviceType = item.Value?.ToString(); + break; + case DeviceOsVersionAttributeName: + logContext.DeviceOsVersion = item.Value?.ToString(); + break; + case SyntheticSourceAttributeName: + logContext.SyntheticSource = item.Value?.ToString(); + break; + case UserAccountIdAttributeName: + logContext.UserAccountId = item.Value?.ToString(); + break; default: if (item.Key.Length <= SchemaConstants.AvailabilityData_Properties_MaxValueLength && item.Value != null && !properties.ContainsKey(item.Key)) { diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/MetricHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/MetricHelper.cs index d1477054746e..56c753c161bd 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/MetricHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/MetricHelper.cs @@ -26,14 +26,15 @@ internal static (List TelemetryItems, TelemetrySchemaTypeCounter { try { - telemetryItems.Add(new TelemetryItem(metricPoint.EndTime.UtcDateTime, resource, instrumentationKey) + var telemetryItem = new TelemetryItem(metricPoint.EndTime.UtcDateTime, resource, instrumentationKey) { Data = new MonitorBase { BaseType = "MetricData", BaseData = new MetricsData(Version, metric, metricPoint) } - }); + }; + telemetryItems.Add(telemetryItem); } catch (Exception ex) { diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs index 48ccc9656815..c1243fc7228e 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/Platform/EnvironmentVariableConstants.cs @@ -19,6 +19,7 @@ internal static class EnvironmentVariableConstants APPLICATIONINSIGHTS_SDKSTATS_EXPORT_INTERVAL, APPLICATIONINSIGHTS_CLOUD_ROLE_NAME, APPLICATIONINSIGHTS_CLOUD_ROLE_INSTANCE, + APPLICATIONINSIGHTS_COMPONENT_VERSION, FUNCTIONS_WORKER_RUNTIME, LOCALAPPDATA, TEMP, @@ -154,5 +155,11 @@ internal static class EnvironmentVariableConstants /// the cloud role instance after the OTel Resource has been built and is immutable. /// public const string APPLICATIONINSIGHTS_CLOUD_ROLE_INSTANCE = "APPLICATIONINSIGHTS_CLOUD_ROLE_INSTANCE"; + + /// + /// Set by the Application Insights shim (TelemetryClient.Context.Component.Version) to override + /// the application version after the OTel Resource has been built and is immutable. + /// + public const string APPLICATIONINSIGHTS_COMPONENT_VERSION = "APPLICATIONINSIGHTS_COMPONENT_VERSION"; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SchemaConstants.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SchemaConstants.cs index e1ef7310b08e..d7d5fb18aed3 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SchemaConstants.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SchemaConstants.cs @@ -127,6 +127,13 @@ internal static class SchemaConstants public const int Tags_AiCloudRole_MaxLength = 256; public const int Tags_AiCloudRoleInstance_MaxLength = 256; public const int Tags_AiInternalSdkVersion_MaxLength = 64; + public const int Tags_AiSessionId_MaxLength = 64; + public const int Tags_AiDeviceId_MaxLength = 1024; + public const int Tags_AiDeviceModel_MaxLength = 256; + public const int Tags_AiDeviceType_MaxLength = 64; + public const int Tags_AiDeviceOsVersion_MaxLength = 256; + public const int Tags_AiOperationSyntheticSource_MaxLength = 1024; + public const int Tags_AiUserAccountId_MaxLength = 1024; /// /// GenAI semantic convention property keys that are exempt from value truncation. 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 09a776e6a9d9..1f227aabe2f8 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs @@ -239,5 +239,15 @@ internal static class SemanticConventions /// When present, takes precedence over computed result code from semantic conventions. /// public const string AttributeMicrosoftRequestResultCode = "microsoft.request.resultCode"; + + // Context tag attributes set by the Application Insights shim (TelemetryClient.Context) + public const string AttributeMicrosoftClientIp = "microsoft.client.ip"; + public const string AttributeMicrosoftSessionId = "microsoft.session.id"; + public const string AttributeAiDeviceId = "ai.device.id"; + public const string AttributeAiDeviceModel = "ai.device.model"; + public const string AttributeAiDeviceType = "ai.device.type"; + public const string AttributeAiDeviceOsVersion = "ai.device.osVersion"; + public const string AttributeMicrosoftSyntheticSource = "microsoft.synthetic_source"; + public const string AttributeMicrosoftUserAccountId = "microsoft.user.account_id"; } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/LogsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/LogsTests.cs index 65a479dca5d7..962b412313e0 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/LogsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/LogsTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using Azure.Monitor.OpenTelemetry.Exporter.Models; +using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Azure.Monitor.OpenTelemetry.Exporter.Tests.CommonTestFramework; using Microsoft.Extensions.Logging; using OpenTelemetry.Logs; @@ -204,5 +205,59 @@ public void VerifyLogWithClientIPMapsToAiLocationIp() expectedTraceId: null, expectedClientIp: "1.2.3.4"); } + + [Fact] + public void VerifyContextTagAttributes_MappedToEnvelopeTags() + { + // SETUP + var uniqueTestId = Guid.NewGuid(); + var logCategoryName = $"logCategoryName{uniqueTestId}"; + List? telemetryItems = null; + + var loggerFactory = LoggerFactory.Create(builder => + { + builder + .AddOpenTelemetry(options => + { + options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddAttributes(testResourceAttributes)); + options.AddAzureMonitorLogExporterForTest(out telemetryItems); + }); + }); + + // ACT + var logger = loggerFactory.CreateLogger(logCategoryName); + logger.LogInformation( + "{user_agent.original} {microsoft.session.id} {ai.device.id} {ai.device.model} {ai.device.type} {ai.device.osVersion} {microsoft.synthetic_source} {microsoft.user.account_id}", + "TestAgent/1.0", "session-123", "device-456", "Surface Pro", "PC", "Microsoft Windows NT 10.0.22621.0", "test-bot", "account-789"); + + // CLEANUP + loggerFactory.Dispose(); + + // ASSERT + Assert.True(telemetryItems?.Any(), "Unit test failed to collect telemetry."); + this.telemetryOutput.Write(telemetryItems); + var telemetryItem = telemetryItems!.First(x => x.Name == "Message"); + + // Verify context tags are mapped to envelope tags + Assert.Equal("TestAgent/1.0", telemetryItem.Tags["ai.user.userAgent"]); + Assert.Equal("session-123", telemetryItem.Tags[ContextTagKeys.AiSessionId.ToString()]); + Assert.Equal("device-456", telemetryItem.Tags[ContextTagKeys.AiDeviceId.ToString()]); + Assert.Equal("Surface Pro", telemetryItem.Tags[ContextTagKeys.AiDeviceModel.ToString()]); + Assert.Equal("PC", telemetryItem.Tags[ContextTagKeys.AiDeviceType.ToString()]); + Assert.Equal("Microsoft Windows NT 10.0.22621.0", telemetryItem.Tags[ContextTagKeys.AiDeviceOSVersion.ToString()]); + Assert.Equal("test-bot", telemetryItem.Tags[ContextTagKeys.AiOperationSyntheticSource.ToString()]); + Assert.Equal("account-789", telemetryItem.Tags[ContextTagKeys.AiUserAccountId.ToString()]); + + // Verify context tags are NOT in customDimensions + var messageData = (MessageData)telemetryItem.Data.BaseData; + Assert.False(messageData.Properties.ContainsKey("user_agent.original")); + Assert.False(messageData.Properties.ContainsKey("microsoft.session.id")); + Assert.False(messageData.Properties.ContainsKey("ai.device.id")); + Assert.False(messageData.Properties.ContainsKey("ai.device.model")); + Assert.False(messageData.Properties.ContainsKey("ai.device.type")); + Assert.False(messageData.Properties.ContainsKey("ai.device.osVersion")); + Assert.False(messageData.Properties.ContainsKey("microsoft.synthetic_source")); + Assert.False(messageData.Properties.ContainsKey("microsoft.user.account_id")); + } } } \ No newline at end of file diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs index 3e1f0152f637..690ca1e8e133 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/E2ETelemetryItemValidation/TracesTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using Azure.Monitor.OpenTelemetry.Exporter.Models; +using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Azure.Monitor.OpenTelemetry.Exporter.Tests.CommonTestFramework; using Microsoft.Extensions.Logging; using OpenTelemetry; @@ -361,5 +362,78 @@ public void TestActivityEvents() expectedSpanId: spanId, expectedTraceId: traceId); } + + [Theory] + [InlineData(ActivityKind.Client, "RemoteDependency")] + [InlineData(ActivityKind.Server, "Request")] + public void VerifyContextTagAttributes_MappedToEnvelopeTags(ActivityKind activityKind, string expectedTelemetryName) + { + // SETUP + var uniqueTestId = Guid.NewGuid(); + + var activitySourceName = $"activitySourceName{uniqueTestId}"; + using var activitySource = new ActivitySource(activitySourceName); + + var tracerProvider = Sdk.CreateTracerProviderBuilder() + .ConfigureResource(x => x.AddAttributes(testResourceAttributes)) + .AddSource(activitySourceName) + .AddAzureMonitorTraceExporterForTest(out List telemetryItems) + .Build(); + + // ACT + using (var activity = activitySource.StartActivity(name: "TestOperation", kind: activityKind)) + { + Assert.NotNull(activity); + + // Set all context tag attributes from the shim + activity.SetTag(SemanticConventions.AttributeUserAgentOriginal, "TestAgent/1.0"); + activity.SetTag(SemanticConventions.AttributeMicrosoftSessionId, "session-123"); + activity.SetTag(SemanticConventions.AttributeAiDeviceId, "device-456"); + activity.SetTag(SemanticConventions.AttributeAiDeviceModel, "Surface Pro"); + activity.SetTag(SemanticConventions.AttributeAiDeviceType, "PC"); + activity.SetTag(SemanticConventions.AttributeAiDeviceOsVersion, "Microsoft Windows NT 10.0.22621.0"); + activity.SetTag(SemanticConventions.AttributeMicrosoftSyntheticSource, "test-bot"); + activity.SetTag(SemanticConventions.AttributeMicrosoftUserAccountId, "account-789"); + } + + // CLEANUP + tracerProvider?.Dispose(); + + // ASSERT + Assert.True(telemetryItems.Any(), "Unit test failed to collect telemetry."); + this.telemetryOutput.Write(telemetryItems); + var telemetryItem = telemetryItems.First(x => x.Name == expectedTelemetryName); + + // Verify context tags are mapped to envelope tags + Assert.Equal("TestAgent/1.0", telemetryItem.Tags["ai.user.userAgent"]); + Assert.Equal("session-123", telemetryItem.Tags[ContextTagKeys.AiSessionId.ToString()]); + Assert.Equal("device-456", telemetryItem.Tags[ContextTagKeys.AiDeviceId.ToString()]); + Assert.Equal("Surface Pro", telemetryItem.Tags[ContextTagKeys.AiDeviceModel.ToString()]); + Assert.Equal("PC", telemetryItem.Tags[ContextTagKeys.AiDeviceType.ToString()]); + Assert.Equal("Microsoft Windows NT 10.0.22621.0", telemetryItem.Tags[ContextTagKeys.AiDeviceOSVersion.ToString()]); + Assert.Equal("test-bot", telemetryItem.Tags[ContextTagKeys.AiOperationSyntheticSource.ToString()]); + Assert.Equal("account-789", telemetryItem.Tags[ContextTagKeys.AiUserAccountId.ToString()]); + + // Verify context tags are NOT in customDimensions + var baseData = telemetryItem.Data.BaseData; + IDictionary properties; + if (baseData is RemoteDependencyData depData) + { + properties = depData.Properties; + } + else + { + properties = ((RequestData)baseData).Properties; + } + + Assert.False(properties.ContainsKey(SemanticConventions.AttributeUserAgentOriginal)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeMicrosoftSessionId)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeAiDeviceId)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeAiDeviceModel)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeAiDeviceType)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeAiDeviceOsVersion)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeMicrosoftSyntheticSource)); + Assert.False(properties.ContainsKey(SemanticConventions.AttributeMicrosoftUserAccountId)); + } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TelemetryItemTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TelemetryItemTests.cs index 9629bbbdd00e..34d123f178ef 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TelemetryItemTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TelemetryItemTests.cs @@ -691,6 +691,37 @@ public void CloudRoleNameOverriddenByEnvironmentVariable() } } + [Fact] + public void ComponentVersionOverriddenByEnvironmentVariable() + { + var priorComponentVersion = Environment.GetEnvironmentVariable("APPLICATIONINSIGHTS_COMPONENT_VERSION"); + TelemetryItem.ResetEnvironmentVariableOverrides(); + try + { + Environment.SetEnvironmentVariable("APPLICATIONINSIGHTS_COMPONENT_VERSION", "2.0.0-beta1"); + + using ActivitySource activitySource = new ActivitySource(ActivitySourceName); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Client, + parentContext: default, + startTime: DateTime.UtcNow) + ?? throw new Exception("Failed to create Activity"); + + var resource = CreateTestResource(serviceName: "my-service", serviceInstance: "my-instance"); + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + var traceResource = resource.CreateAzureMonitorResource(instrumentationKey: null, platform: GetMockPlatform()); + var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, traceResource, "00000000-0000-0000-0000-000000000000", 1.0f); + + Assert.Equal("2.0.0-beta1", telemetryItem.Tags[ContextTagKeys.AiApplicationVer.ToString()]); + } + finally + { + Environment.SetEnvironmentVariable("APPLICATIONINSIGHTS_COMPONENT_VERSION", priorComponentVersion); + TelemetryItem.ResetEnvironmentVariableOverrides(); + } + } + /// /// If SERVICE.NAME is not defined, it will fall-back to "unknown_service". /// (https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/resource/semantic_conventions#semantic-attributes-with-sdk-provided-default-value).