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 b94ec3dab154..5d08c465e60e 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 @@ -46,7 +46,7 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc } dependencyName ??= activity.DisplayName; - Name = dependencyName.Truncate(SchemaConstants.RemoteDependencyData_Name_MaxLength); + Name = dependencyName?.Truncate(SchemaConstants.RemoteDependencyData_Name_MaxLength); Id = activity.Context.SpanId.ToHexString(); Duration = activity.Duration < SchemaConstants.RemoteDependencyData_Duration_LessThanDays ? activity.Duration.ToString("c", CultureInfo.InvariantCulture) @@ -101,8 +101,8 @@ private void SetHttpDependencyPropertiesAndDependencyName(Activity activity, ref } Type = "Http"; - Data = httpUrl.Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength); - Target = target.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength); + Data = httpUrl?.Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength); + Target = target?.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength); ResultCode = resultCode?.Truncate(SchemaConstants.RemoteDependencyData_ResultCode_MaxLength) ?? "0"; } @@ -111,11 +111,11 @@ private void SetDbDependencyProperties(ref AzMonList dbTagObjects) var dbAttributeTagObjects = AzMonList.GetTagValues(ref dbTagObjects, SemanticConventions.AttributeDbStatement, SemanticConventions.AttributeDbSystem); Data = dbAttributeTagObjects[0]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength); var (DbName, DbTarget) = dbTagObjects.GetDbDependencyTargetAndName(); - Target = DbTarget.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength); + Target = DbTarget?.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength); Type = s_sqlDbs.Contains(dbAttributeTagObjects[1]?.ToString()) ? "SQL" : dbAttributeTagObjects[1]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength); // special case for db.name - var sanitizedDbName = DbName.Truncate(SchemaConstants.KVP_MaxValueLength); + var sanitizedDbName = DbName?.Truncate(SchemaConstants.KVP_MaxValueLength); if (sanitizedDbName != null) { Properties.Add(SemanticConventions.AttributeDbName, sanitizedDbName); @@ -132,10 +132,10 @@ private void SetRpcDependencyProperties(ref AzMonList rpcTagObjects) private void SetMessagingDependencyProperties(Activity activity, ref AzMonList messagingTagObjects) { - var messagingAttributeTagObjects = AzMonList.GetTagValues(ref messagingTagObjects, SemanticConventions.AttributeMessagingUrl, SemanticConventions.AttributeMessagingSystem); - var messagingUrl = messagingAttributeTagObjects[0]?.ToString(); + var (messagingUrl, target) = messagingTagObjects.GetMessagingUrlAndSourceOrTarget(activity.Kind); Data = messagingUrl?.Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength); - Type = messagingAttributeTagObjects[1]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength); + Target = target?.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength); + Type = AzMonList.GetTagValue(ref messagingTagObjects, SemanticConventions.AttributeMessagingSystem)?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength); } } } 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 80b4bca4e4e8..a6b58387eab9 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 @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Globalization; +using System.Runtime.CompilerServices; using Azure.Core; using Azure.Monitor.OpenTelemetry.Exporter.Internals; @@ -10,6 +11,12 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Models { internal partial class RequestData { + public RequestData(int version, string? operationName, string? requestUrl, Activity activity, ref ActivityTagsProcessor activityTagsProcessor) : this(version, activity, ref activityTagsProcessor) + { + Name = operationName?.Truncate(SchemaConstants.RequestData_Name_MaxLength); + Url = requestUrl?.Truncate(SchemaConstants.RequestData_Url_MaxLength); + } + public RequestData(int version, Activity activity, ref ActivityTagsProcessor activityTagsProcessor) : base(version) { string? responseCode = null; @@ -50,6 +57,7 @@ public RequestData(int version, Activity activity, ref ActivityTagsProcessor act TraceHelper.AddPropertiesToTelemetry(Properties, ref activityTagsProcessor.UnMappedTags); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsSuccess(Activity activity, string? responseCode, OperationType operationType) { if (operationType.HasFlag(OperationType.Http) @@ -67,8 +75,8 @@ internal static bool IsSuccess(Activity activity, string? responseCode, Operatio private void SetHttpRequestPropertiesAndResponseCode(Activity activity, ref AzMonList httpTagObjects, out string responseCode) { - Url = httpTagObjects.GetRequestUrl().Truncate(SchemaConstants.RequestData_Url_MaxLength); - Name = TraceHelper.GetOperationName(activity, ref httpTagObjects).Truncate(SchemaConstants.RequestData_Name_MaxLength); + Url ??= httpTagObjects.GetRequestUrl()?.Truncate(SchemaConstants.RequestData_Url_MaxLength); + Name ??= TraceHelper.GetOperationName(activity, ref httpTagObjects)?.Truncate(SchemaConstants.RequestData_Name_MaxLength); responseCode = AzMonList.GetTagValue(ref httpTagObjects, SemanticConventions.AttributeHttpStatusCode) ?.ToString().Truncate(SchemaConstants.RequestData_ResponseCode_MaxLength) ?? "0"; @@ -76,8 +84,8 @@ private void SetHttpRequestPropertiesAndResponseCode(Activity activity, ref AzMo private void SetHttpV2RequestPropertiesAndResponseCode(Activity activity, ref AzMonList httpTagObjects, out string responseCode) { - Url = httpTagObjects.GetNewSchemaRequestUrl().Truncate(SchemaConstants.RequestData_Url_MaxLength); - Name = TraceHelper.GetNewSchemaOperationName(activity, Url, ref httpTagObjects).Truncate(SchemaConstants.RequestData_Name_MaxLength); + Url ??= httpTagObjects.GetNewSchemaRequestUrl()?.Truncate(SchemaConstants.RequestData_Url_MaxLength); + Name ??= TraceHelper.GetNewSchemaOperationName(activity, Url, ref httpTagObjects)?.Truncate(SchemaConstants.RequestData_Name_MaxLength); responseCode = AzMonList.GetTagValue(ref httpTagObjects, SemanticConventions.AttributeHttpResponseStatusCode) ?.ToString().Truncate(SchemaConstants.RequestData_ResponseCode_MaxLength) ?? "0"; @@ -85,7 +93,9 @@ private void SetHttpV2RequestPropertiesAndResponseCode(Activity activity, ref Az private void SetMessagingRequestProperties(Activity activity, ref AzMonList messagingTagObjects) { - Url = AzMonList.GetTagValue(ref messagingTagObjects, SemanticConventions.AttributeMessagingUrl)?.ToString().Truncate(SchemaConstants.RequestData_Url_MaxLength); + var (messagingUrl, source) = messagingTagObjects.GetMessagingUrlAndSourceOrTarget(activity.Kind); + Url = messagingUrl?.Truncate(SchemaConstants.RequestData_Url_MaxLength); + Source = source?.Truncate(SchemaConstants.RequestData_Source_MaxLength); Name = activity.DisplayName; } } 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 3664fcb6e387..9df309479a29 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 @@ -32,17 +32,6 @@ public TelemetryItem(Activity activity, ref ActivityTagsProcessor activityTagsPr } SetAuthenticatedUserId(ref activityTagsProcessor); - - // we only have mapping for server spans - // todo: non-server spans - if (activity.Kind == ActivityKind.Server) - { - Tags[ContextTagKeys.AiOperationName.ToString()] = activityTagsProcessor.activityType.HasFlag(OperationType.V2) - ? TraceHelper.GetNewSchemaOperationName(activity, null, ref activityTagsProcessor.MappedTags) - : TraceHelper.GetOperationName(activity, ref activityTagsProcessor.MappedTags); - Tags[ContextTagKeys.AiLocationIp.ToString()] = TraceHelper.GetLocationIp(ref activityTagsProcessor.MappedTags); - } - SetResourceSdkVersionAndIkey(resource, instrumentationKey); if (AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, "sampleRate") is float sampleRate) { @@ -62,14 +51,6 @@ public TelemetryItem(string name, TelemetryItem telemetryItem, ActivitySpanId ac Tags["ai.user.userAgent"] = userAgent; } - // we only have mapping for server spans - // todo: non-server spans - if (kind == ActivityKind.Server) - { - Tags[ContextTagKeys.AiOperationName.ToString()] = telemetryItem.Tags[ContextTagKeys.AiOperationName.ToString()]; - Tags[ContextTagKeys.AiLocationIp.ToString()] = telemetryItem.Tags[ContextTagKeys.AiLocationIp.ToString()]; - } - Tags[ContextTagKeys.AiCloudRole.ToString()] = telemetryItem.Tags[ContextTagKeys.AiCloudRole.ToString()]; Tags[ContextTagKeys.AiCloudRoleInstance.ToString()] = telemetryItem.Tags[ContextTagKeys.AiCloudRoleInstance.ToString()]; Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = SdkVersionUtils.s_sdkVersion; 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 20e7e18ad028..8771638e95bd 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs @@ -61,10 +61,8 @@ internal struct ActivityTagsProcessor SemanticConventions.AttributeEndpointAddress, // required - Messaging SemanticConventions.AttributeMessagingSystem, - SemanticConventions.AttributeMessagingDestination, - SemanticConventions.AttributeMessagingDestinationKind, - SemanticConventions.AttributeMessagingTempDestination, - SemanticConventions.AttributeMessagingUrl, + SemanticConventions.AttributeMessagingDestinationName, + SemanticConventions.AttributeNetworkProtocolName, // Others SemanticConventions.AttributeEnduserId diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs index 03215897fb39..feb3f3a96add 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace Azure.Monitor.OpenTelemetry.Exporter.Internals; @@ -15,31 +16,79 @@ internal static class AzMonNewListExtensions { try { - var serverAddress = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeServerAddress)?.ToString(); - if (serverAddress != null) + var requestUrlTagObjects = AzMonList.GetTagValues(ref tagObjects, SemanticConventions.AttributeUrlScheme, SemanticConventions.AttributeServerAddress, SemanticConventions.AttributeServerPort, SemanticConventions.AttributeUrlPath, SemanticConventions.AttributeUrlQuery); + + var scheme = requestUrlTagObjects[0]?.ToString() ?? string.Empty; // requestUrlTagObjects[0] => SemanticConventions.AttributeUrlScheme. + var host = requestUrlTagObjects[1]?.ToString() ?? string.Empty; // requestUrlTagObjects[1] => SemanticConventions.AttributeServerAddress. + var port = requestUrlTagObjects[2]?.ToString(); // requestUrlTagObjects[2] => SemanticConventions.AttributeServerPort. + port = port != null ? port = $":{port}" : string.Empty; + var path = requestUrlTagObjects[3]?.ToString() ?? string.Empty; // requestUrlTagObjects[3] => SemanticConventions.AttributeUrlPath. + var queryString = requestUrlTagObjects[4]?.ToString() ?? string.Empty; // requestUrlTagObjects[4] => SemanticConventions.AttributeUrlQuery. + + var length = scheme.Length + Uri.SchemeDelimiter.Length + host.Length + port.Length + path.Length + queryString.Length; + + var urlStringBuilder = new System.Text.StringBuilder(length) + .Append(scheme) + .Append(Uri.SchemeDelimiter) + .Append(host) + .Append(port) + .Append(path) + .Append(queryString); + + return urlStringBuilder.ToString(); + } + catch + { + // If URI building fails, there is no need to throw an exception. Instead, we can simply return null. + } + + return null; + } + + /// + /// Gets messaging url from activity tag objects. + /// + internal static (string? MessagingUrl, string? SourceOrTarget) GetMessagingUrlAndSourceOrTarget(this AzMonList tagObjects, ActivityKind activityKind) + { + string? messagingUrl = null; + string? sourceOrTarget = null; + + try + { + var host = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeServerAddress)?.ToString() + ?? AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeNetPeerName)?.ToString(); + if (!string.IsNullOrEmpty(host)) { - UriBuilder uriBuilder = new() - { - Scheme = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeUrlScheme)?.ToString(), - Host = serverAddress, - Path = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeUrlPath)?.ToString(), - Query = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeUrlQuery)?.ToString() - }; + object?[] messagingTagObjects; - if (int.TryParse(AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeServerPort)?.ToString(), out int port)) + messagingTagObjects = AzMonList.GetTagValues(ref tagObjects, SemanticConventions.AttributeNetworkProtocolName, SemanticConventions.AttributeMessagingDestinationName); + var protocolName = messagingTagObjects[0]?.ToString() ?? string.Empty; // messagingTagObjects[0] => SemanticConventions.AttributeNetworkProtocolName. + var destinationName = messagingTagObjects[1]?.ToString() ?? string.Empty; // messagingTagObjects[1] => SemanticConventions.AttributeMessagingDestinationName. + + if (destinationName.Length > 0) { - uriBuilder.Port = port; + destinationName = $"/{destinationName}"; } - return uriBuilder.Uri.AbsoluteUri; + sourceOrTarget = $"{host}{destinationName}"; + + var length = protocolName.Length + (protocolName?.Length > 0 ? Uri.SchemeDelimiter.Length : 0) + host!.Length + destinationName.Length; + + var messagingStringBuilder = new System.Text.StringBuilder(length) + .Append(protocolName) + .Append(string.IsNullOrEmpty(protocolName) ? null : Uri.SchemeDelimiter) + .Append(host) + .Append(destinationName); + + messagingUrl = messagingStringBuilder.ToString(); } } catch { - // If URI building fails, there is no need to throw an exception. Instead, we can simply return null. + // If Messaging Url building fails, there is no need to throw an exception. Instead, we can simply return null. } - return null; + return (MessagingUrl: messagingUrl, SourceOrTarget: sourceOrTarget); } /// 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 0e87aee27fed..f544ff0e1a26 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs @@ -163,5 +163,9 @@ internal static class SemanticConventions public const string AttributeUrlQuery = "url.query"; public const string AttributeUserAgentOriginal = "user_agent.original"; // replaces: "http.user_agent" (AttributeHttpUserAgent) public const string AttributeServerSocketAddress = "server.socket.address"; // replaces: "net.peer.ip" (AttributeNetPeerIp) + + // Messaging v1.21.0 https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/messaging.md + public const string AttributeMessagingDestinationName = "messaging.destination.name"; + public const string AttributeNetworkProtocolName = "network.protocol.name"; } } 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 c5b44d06390d..16dad352340f 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/TraceHelper.cs @@ -45,11 +45,26 @@ internal static List OtelToAzureMonitorTrace(Batch batc switch (activity.GetTelemetryType()) { case TelemetryType.Request: - telemetryItem.Data = new MonitorBase + if (activity.Kind == ActivityKind.Server && activityTagsProcessor.activityType.HasFlag(OperationType.Http)) { - BaseType = "RequestData", - BaseData = new RequestData(Version, activity, ref activityTagsProcessor) - }; + var (requestUrl, operationName) = GetHttpOperationNameAndUrl(activity.DisplayName, activityTagsProcessor.activityType, ref activityTagsProcessor.MappedTags); + telemetryItem.Tags[ContextTagKeys.AiOperationName.ToString()] = operationName; + telemetryItem.Tags[ContextTagKeys.AiLocationIp.ToString()] = TraceHelper.GetLocationIp(ref activityTagsProcessor.MappedTags); + + telemetryItem.Data = new MonitorBase + { + BaseType = "RequestData", + BaseData = new RequestData(Version, operationName, requestUrl, activity, ref activityTagsProcessor) + }; + } + else + { + telemetryItem.Data = new MonitorBase + { + BaseType = "RequestData", + BaseData = new RequestData(Version, activity, ref activityTagsProcessor) + }; + } break; case TelemetryType.Dependency: telemetryItem.Data = new MonitorBase @@ -171,7 +186,7 @@ internal static string GetOperationName(Activity activity, ref AzMonList MappedT return activity.DisplayName; } - internal static string GetNewSchemaOperationName(Activity activity, string? url, ref AzMonList MappedTags) + internal static string GetNewSchemaOperationName(Activity activity, string? url, ref AzMonList MappedTags) { var httpMethod = AzMonList.GetTagValue(ref MappedTags, SemanticConventions.AttributeHttpRequestMethod)?.ToString(); if (!string.IsNullOrWhiteSpace(httpMethod)) @@ -195,6 +210,42 @@ internal static string GetNewSchemaOperationName(Activity activity, string? url, return activity.DisplayName; } + internal static (string? RequestUrl, string? OperationName) GetHttpOperationNameAndUrl(string activityDisplayName, OperationType operationType, ref AzMonList httpMappedTags) + { + string? httpMethod; + string? httpUrl; + + if (operationType.HasFlag(OperationType.V2)) + { + httpUrl = httpMappedTags.GetNewSchemaRequestUrl(); + httpMethod = AzMonList.GetTagValue(ref httpMappedTags, SemanticConventions.AttributeHttpRequestMethod)?.ToString(); + } + else + { + httpUrl = AzMonList.GetTagValue(ref httpMappedTags, SemanticConventions.AttributeHttpUrl)?.ToString(); + httpMethod = AzMonList.GetTagValue(ref httpMappedTags, SemanticConventions.AttributeHttpMethod)?.ToString(); + } + + if (!string.IsNullOrWhiteSpace(httpMethod)) + { + var httpRoute = AzMonList.GetTagValue(ref httpMappedTags, SemanticConventions.AttributeHttpRoute)?.ToString(); + + // ASP.NET instrumentation assigns route as {controller}/{action}/{id} which would result in the same name for different operations. + // To work around that we will use path from httpUrl. + if (httpRoute?.Contains("{controller}") == false) + { + return (RequestUrl: httpUrl, OperationName: $"{httpMethod} {httpRoute}"); + } + + if (!string.IsNullOrWhiteSpace(httpUrl) && Uri.TryCreate(httpUrl!.ToString(), UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri) + { + return (RequestUrl: httpUrl, OperationName: $"{httpMethod} {uri.AbsolutePath}"); + } + } + + return (RequestUrl: httpUrl, OperationName: activityDisplayName); + } + private static void AddTelemetryFromActivityEvents(Activity activity, TelemetryItem telemetryItem, List telemetryItems) { foreach (ref readonly var @event in activity.EnumerateEvents()) diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonNewListExtensionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonNewListExtensionsTests.cs index 7fe3a125e7d4..e2ad49609996 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonNewListExtensionsTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonNewListExtensionsTests.cs @@ -1,8 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; - +using System.Diagnostics; using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Xunit; @@ -11,13 +12,11 @@ namespace Azure.Monitor.OpenTelemetry.Exporter.Tests public class AzMonNewListExtensionsTests { [Theory] - [InlineData("http", "example.com", "8080", "/search", "q=OpenTelemetry", "http://example.com:8080/search?q=OpenTelemetry")] - [InlineData(null, "example.com", "8080", "/search", "q=OpenTelemetry", "example.com:8080/search?q=OpenTelemetry")] - [InlineData("http", null, "8080", "/search", "q=OpenTelemetry", null)] - [InlineData("http", "example.com", null, "/search", "q=OpenTelemetry", "http://example.com/search?q=OpenTelemetry")] - [InlineData("http", "example.com", "8080", null, "q=OpenTelemetry", "http://example.com:8080/?q=OpenTelemetry")] + [InlineData("http", "example.com", "8080", "/search", "?q=OpenTelemetry", "http://example.com:8080/search?q=OpenTelemetry")] + [InlineData("http", "example.com", null, "/search", "?q=OpenTelemetry", "http://example.com/search?q=OpenTelemetry")] + [InlineData("http", "example.com", "8080", "/", "?q=OpenTelemetry", "http://example.com:8080/?q=OpenTelemetry")] [InlineData("http", "example.com", "8080", "/search", null, "http://example.com:8080/search")] - public void GetNewRequestUrl_ReturnsCorrectUrl(string urlScheme, string serverAddress, string serverPort, string urlPath, string urlQuery, string expectedUrl) + public void GetNewRequestUrl_ReturnsCorrectUrl(string urlScheme, string serverAddress, string? serverPort, string urlPath, string? urlQuery, string expectedUrl) { // Arrange AzMonList tagObjects = AzMonList.Initialize(); @@ -27,11 +26,56 @@ public void GetNewRequestUrl_ReturnsCorrectUrl(string urlScheme, string serverAd AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeUrlPath, urlPath)); AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeUrlQuery, urlQuery)); + // Validate the length with the same logic from code. + int colonLength = serverPort == null ? 0 : 1; + serverPort ??= string.Empty; + urlQuery ??= string.Empty; + var length = urlScheme.Length + Uri.SchemeDelimiter.Length + serverAddress.Length + serverPort.Length + colonLength + urlPath.Length + urlQuery.Length; + // Act string? url = tagObjects.GetNewSchemaRequestUrl(); // Assert Assert.Equal(expectedUrl, url); + Assert.Equal(length, url?.Length); + } + + [Theory] + [InlineData("my.servicebus.windows.net", "amqp", "queueName", "amqp://my.servicebus.windows.net/queueName", "my.servicebus.windows.net/queueName")] + [InlineData("my.servicebus.windows.net", "amqp", "", "amqp://my.servicebus.windows.net", "my.servicebus.windows.net")] + [InlineData("", "amqp", "queueName", null, null)] + [InlineData("my.servicebus.windows.net", "", "queueName", "my.servicebus.windows.net/queueName", "my.servicebus.windows.net/queueName")] + [InlineData("my.servicebus.windows.net", null, null, "my.servicebus.windows.net", "my.servicebus.windows.net")] + [InlineData(null, "amqp", "queueName", null, null)] + public void GetMessagingUrlAndSourceOrTarget_ReturnsCorrectResult(string serverAddress, string protocolName, string destinationName, string expectedUrl, string expectedSourceOrTarget) + { + // Arrange + AzMonList tagObjects = AzMonList.Initialize(); + AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeServerAddress, serverAddress)); + AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeNetworkProtocolName, protocolName)); + AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeMessagingDestinationName, destinationName)); + + // Act + var (messagingUrl, sourceOrTarget) = tagObjects.GetMessagingUrlAndSourceOrTarget(ActivityKind.Producer); + + // Assert + Assert.Equal(expectedUrl, messagingUrl); + Assert.Equal(expectedSourceOrTarget, sourceOrTarget); + } + + [Fact] + public void GetMessagingUrlAndSourceOrTarget_NullTagObjects_ReturnsNull() + { + // Arrange + AzMonList tagObjects = AzMonList.Initialize(); + var activityKind = ActivityKind.Consumer; + + // Act + var (messagingUrl, sourceOrTarget) = tagObjects.GetMessagingUrlAndSourceOrTarget(activityKind); + + // Assert + Assert.Null(messagingUrl); + Assert.Null(sourceOrTarget); } [Theory] diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs index 9a6fade55062..d5f93d246c46 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/CommonTestFramework/TelemetryItemValidationHelper.cs @@ -143,14 +143,6 @@ public static void AssertActivity_As_RequestTelemetry( var expectedTagsCount = 5; - if (activityKind == ActivityKind.Server) - { - expectedTagsCount = 7; - - Assert.Contains("ai.operation.name", telemetryItem.Tags.Keys); - Assert.Contains("ai.location.ip", telemetryItem.Tags.Keys); - } - Assert.Equal(expectedTagsCount, telemetryItem.Tags.Count); Assert.Equal(expectedTraceId, telemetryItem.Tags["ai.operation.id"]); Assert.Equal(expectedAuthUserId, telemetryItem.Tags["ai.user.authUserId"]); diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataNewTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataNewTests.cs index 74c517e696ec..c0e6d570948d 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataNewTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataNewTests.cs @@ -121,5 +121,38 @@ public void HttpDependencyNameIsActivityDisplayNameByDefault() Assert.Equal(activity.DisplayName, remoteDependencyDataName); } + + [Fact] + public void ValidateMessagingRemoteDependencyData() + { + using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(ValidateMessagingRemoteDependencyData)).Build(); + using var activitySource = new ActivitySource(nameof(ValidateMessagingRemoteDependencyData)); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Producer, + parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), + startTime: DateTime.UtcNow); + Assert.NotNull(activity); + activity.Stop(); + + activity.SetStatus(Status.Ok); + activity.SetTag(SemanticConventions.AttributeMessagingSystem, "servicebus"); + activity.SetTag(SemanticConventions.AttributeServerAddress, "my.servicebus.windows.net"); + activity.SetTag(SemanticConventions.AttributeMessagingDestinationName, "queueName"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor); + + Assert.Equal("RemoteDependencyDataNewActivity", remoteDependencyData.Name); + Assert.Equal(activity.Context.SpanId.ToHexString(), remoteDependencyData.Id); + Assert.Equal("my.servicebus.windows.net/queueName", remoteDependencyData.Data); + Assert.Null(remoteDependencyData.ResultCode); + Assert.Equal(activity.Duration.ToString("c", CultureInfo.InvariantCulture), remoteDependencyData.Duration); + Assert.Equal("my.servicebus.windows.net/queueName", remoteDependencyData.Target); + Assert.Equal(activity.GetStatus() != Status.Error, remoteDependencyData.Success); + Assert.True(remoteDependencyData.Properties.Count == 0); + Assert.True(remoteDependencyData.Measurements.Count == 0); + } } } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataNewTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataNewTests.cs index c3f351a793e0..b971bbf10900 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataNewTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RequestDataNewTests.cs @@ -39,6 +39,7 @@ public void ValidateHttpRequestData() activity.SetTag(SemanticConventions.AttributeServerAddress, "www.foo.bar"); activity.SetTag(SemanticConventions.AttributeUrlPath, "/search"); activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, null); + activity.SetTag("foo", "bar"); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); @@ -51,7 +52,8 @@ public void ValidateHttpRequestData() Assert.Equal(activity.Duration.ToString("c", CultureInfo.InvariantCulture), requestData.Duration); Assert.False(requestData.Success); Assert.Null(requestData.Source); - Assert.True(requestData.Properties.Count == 0); + Assert.True(requestData.Properties.Count == 1); + Assert.Equal("bar", requestData.Properties["foo"]); Assert.True(requestData.Measurements.Count == 0); } @@ -82,11 +84,15 @@ public void ValidateHttpRequestDataResponseCode(string httpStatusCode) } [Theory] - [InlineData("200", true)] - [InlineData("400", false)] - [InlineData("500", false)] - [InlineData("0", false)] - public void ValidateHttpRequestSuccess(string httpStatusCode, bool isSuccess) + [InlineData("200", OperationType.Http, true)] + [InlineData("400", OperationType.Http, false)] + [InlineData("500", OperationType.Http, false)] + [InlineData("0", OperationType.Http, false)] + [InlineData(null, OperationType.Http, true)] + [InlineData("", OperationType.Http, true)] + [InlineData("someStatusCode", OperationType.Unknown, false)] // Activity status is set to error in the test code for validation. + [InlineData("someStatusCode", OperationType.Messaging, true)] + internal void ValidateHttpRequestSuccess(string httpStatusCode, OperationType operationType, bool isSuccess) { using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(ValidateHttpRequestSuccess)).Build(); using var activitySource = new ActivitySource(nameof(ValidateHttpRequestSuccess)); @@ -98,16 +104,12 @@ public void ValidateHttpRequestSuccess(string httpStatusCode, bool isSuccess) Assert.NotNull(activity); activity.Stop(); - var httpResponseCode = httpStatusCode ?? "0"; - activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "GET"); - activity.SetTag(SemanticConventions.AttributeHttpResponseStatusCode, httpStatusCode); - - var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - - var requestData = new RequestData(2, activity, ref activityTagsProcessor); + if (operationType == OperationType.Unknown) + { + activity.SetStatus(ActivityStatusCode.Error); + } - Assert.Equal(httpResponseCode, requestData.ResponseCode); - Assert.Equal(isSuccess, requestData.Success); + Assert.Equal(isSuccess, RequestData.IsSuccess(activity, httpStatusCode, operationType)); } [Fact] @@ -220,6 +222,41 @@ public void RequestDataTimeSinceEnqueuedInvalidEmqueuedTime() Assert.False(requestData.Measurements.TryGetValue("timeSinceEnqueued", out var timeInQueue)); } + [Fact] + public void ValidateMessagingRequestData() + { + using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(ValidateMessagingRequestData)).Build(); + using var activitySource = new ActivitySource(nameof(ValidateMessagingRequestData)); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Consumer, + parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded), + startTime: DateTime.UtcNow); + Assert.NotNull(activity); + activity.Stop(); + + activity.SetStatus(Status.Ok); + activity.SetTag(SemanticConventions.AttributeMessagingSystem, "servicebus"); + activity.SetTag(SemanticConventions.AttributeServerAddress, "my.servicebus.windows.net"); + activity.SetTag(SemanticConventions.AttributeMessagingDestinationName, "queueName"); + activity.SetTag("foo", "bar"); + + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); + + var requestData = new RequestData(2, activity, ref activityTagsProcessor); + + Assert.Equal("RequestDataNewActivity", requestData.Name); + Assert.Equal(activity.Context.SpanId.ToHexString(), requestData.Id); + Assert.Equal("my.servicebus.windows.net/queueName", requestData.Url); + Assert.Equal("0", requestData.ResponseCode); + Assert.Equal(activity.Duration.ToString("c", CultureInfo.InvariantCulture), requestData.Duration); + Assert.True(requestData.Success); + Assert.Equal("my.servicebus.windows.net/queueName", requestData.Source); + Assert.True(requestData.Properties.Count == 1); + Assert.Equal("bar", requestData.Properties["foo"]); + Assert.True(requestData.Measurements.Count == 0); + } + private ActivityLink AddActivityLink(long enqueuedTime) { ActivityTagsCollection tags = new ActivityTagsCollection 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 01206f960608..b600e21b1d2b 100644 --- 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 @@ -50,7 +50,8 @@ public void ValidateHttpRequestData() activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); activity.SetTag(SemanticConventions.AttributeHttpRoute, "/search"); activity.SetTag(SemanticConventions.AttributeHttpUrl, httpUrl); // only adding test via http.url. all possible combinations are covered in AzMonListExtensionsTests. - activity.SetTag(SemanticConventions.AttributeHttpStatusCode, null); + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, "200"); + activity.SetTag("foo", "bar"); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); @@ -59,11 +60,12 @@ public void ValidateHttpRequestData() Assert.Equal("GET /search", requestData.Name); Assert.Equal(activity.Context.SpanId.ToHexString(), requestData.Id); Assert.Equal(httpUrl, requestData.Url); - Assert.Equal("0", requestData.ResponseCode); + Assert.Equal("200", requestData.ResponseCode); Assert.Equal(activity.Duration.ToString("c", CultureInfo.InvariantCulture), requestData.Duration); - Assert.False(requestData.Success); + Assert.True(requestData.Success); Assert.Null(requestData.Source); - Assert.True(requestData.Properties.Count == 0); + Assert.True(requestData.Properties.Count == 1); + Assert.Equal("bar", requestData.Properties["foo"]); Assert.True(requestData.Measurements.Count == 0); } 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 7ec1993fe449..db041415747a 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 @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Net; -using Xunit; +using Azure.Monitor.OpenTelemetry.Exporter.Internals; using Azure.Monitor.OpenTelemetry.Exporter.Models; +using OpenTelemetry; using OpenTelemetry.Resources; -using Azure.Monitor.OpenTelemetry.Exporter.Internals; +using Xunit; namespace Azure.Monitor.OpenTelemetry.Exporter.Tests { @@ -141,9 +143,10 @@ public void HttpMethodAndHttpRouteIsUsedForHttpRequestOperationName(string? rout } var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Equal(expectedOperationName, telemetryItem.Tags[ContextTagKeys.AiOperationName.ToString()]); + Assert.Equal(expectedOperationName, telemetryItem?.Tags[ContextTagKeys.AiOperationName.ToString()]); } [Fact] @@ -163,9 +166,10 @@ public void HttpMethodAndHttpUrlPathIsUsedForHttpRequestOperationName() activity.SetTag(SemanticConventions.AttributeHttpUrl, "https://www.foo.bar/path?id=1"); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Equal("GET /path", telemetryItem.Tags[ContextTagKeys.AiOperationName.ToString()]); + Assert.Equal("GET /path", telemetryItem?.Tags[ContextTagKeys.AiOperationName.ToString()]); } [Fact] @@ -180,11 +184,13 @@ public void ActivityNameIsUsedByDefaultForRequestOperationName() Assert.NotNull(activity); activity.DisplayName = "displayname"; + activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "GET"); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Equal("displayname", telemetryItem.Tags[ContextTagKeys.AiOperationName.ToString()]); + Assert.Equal("displayname", telemetryItem?.Tags[ContextTagKeys.AiOperationName.ToString()]); } [Fact] @@ -199,11 +205,13 @@ public void AiLocationIpIsSetAsHttpClientIpForHttpServerSpans() Assert.NotNull(activity); activity.SetTag(SemanticConventions.AttributeHttpClientIP, "127.0.0.1"); + activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "GET"); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Equal("127.0.0.1", telemetryItem.Tags[ContextTagKeys.AiLocationIp.ToString()]); + Assert.Equal("127.0.0.1", telemetryItem?.Tags[ContextTagKeys.AiLocationIp.ToString()]); } [Fact] @@ -218,11 +226,13 @@ public void AiLocationIpIsSetAsNetPeerIpForServerSpans() Assert.NotNull(activity); activity.SetTag(SemanticConventions.AttributeNetPeerIp, "127.0.0.1"); + activity.SetTag(SemanticConventions.AttributeHttpRequestMethod, "GET"); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Equal("127.0.0.1", telemetryItem.Tags[ContextTagKeys.AiLocationIp.ToString()]); + Assert.Equal("127.0.0.1", telemetryItem?.Tags[ContextTagKeys.AiLocationIp.ToString()]); } [Fact] @@ -240,9 +250,10 @@ public void AiUserAgentIsSetAsHttpUserAgent() activity.SetTag(SemanticConventions.AttributeHttpUserAgent, userAgent); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Equal(userAgent, telemetryItem.Tags["ai.user.userAgent"]); + Assert.Equal(userAgent, telemetryItem?.Tags["ai.user.userAgent"]); } [Fact] @@ -254,12 +265,14 @@ public void AiLocationIpIsNullByDefault() ActivityKind.Server, null, startTime: DateTime.UtcNow); + activity?.SetTag(SemanticConventions.AttributeHttpRequestMethod, "GET"); Assert.NotNull(activity); var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); - Assert.Null(telemetryItem.Tags[ContextTagKeys.AiLocationIp.ToString()]); + Assert.Null(telemetryItem?.Tags[ContextTagKeys.AiLocationIp.ToString()]); } [Fact] @@ -319,10 +332,8 @@ public void RoleInstanceIsNotOverwrittenIfSetViaServiceInstanceId() Assert.Equal("serviceinstance", telemetryItem.Tags[ContextTagKeys.AiCloudRoleInstance.ToString()]); } - [Theory] - [InlineData("GET")] - [InlineData(null)] - public void RequestNameMatchesOperationName(string? httpMethod) + [Fact] + public void RequestNameMatchesOperationName() { using ActivitySource activitySource = new ActivitySource(ActivitySourceName); using var activity = activitySource.StartActivity( @@ -333,15 +344,15 @@ public void RequestNameMatchesOperationName(string? httpMethod) Assert.NotNull(activity); activity.DisplayName = "displayname"; - if (httpMethod != null) - { - activity.SetTag(SemanticConventions.AttributeHttpMethod, httpMethod); - } + + activity.SetTag(SemanticConventions.AttributeHttpMethod, "GET"); + var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity); - var telemetryItem = new TelemetryItem(activity, ref activityTagsProcessor, null, string.Empty); + var telemetryItems = TraceHelper.OtelToAzureMonitorTrace(new Batch(new Activity[] { activity }, 1), null, "instrumentationKey"); + var telemetryItem = telemetryItems.FirstOrDefault(); var requestData = new RequestData(2, activity, ref activityTagsProcessor); - Assert.Equal(requestData.Name, telemetryItem.Tags[ContextTagKeys.AiOperationName.ToString()]); + Assert.Equal(requestData.Name, telemetryItem?.Tags[ContextTagKeys.AiOperationName.ToString()]); } /// @@ -361,9 +372,12 @@ private static Resource CreateTestResource(string? serviceName = null, string? s { var testAttributes = new Dictionary(); - if (serviceName != null) testAttributes.Add("service.name", serviceName); - if (serviceNamespace != null) testAttributes.Add("service.namespace", serviceNamespace); - if (serviceInstance != null) testAttributes.Add("service.instance.id", serviceInstance); + if (serviceName != null) + testAttributes.Add("service.name", serviceName); + if (serviceNamespace != null) + testAttributes.Add("service.namespace", serviceNamespace); + if (serviceInstance != null) + testAttributes.Add("service.instance.id", serviceInstance); return ResourceBuilder.CreateDefault().AddAttributes(testAttributes).Build(); } diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TraceHelperNewTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TraceHelperNewTests.cs index 32728d3d6c22..59dd7c1e0900 100644 --- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TraceHelperNewTests.cs +++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TraceHelperNewTests.cs @@ -90,11 +90,11 @@ public void GetNewOperationName_WithNullHttpMethod_ReturnsActivityDisplayName(st } [Fact] - public void GetNewOperationName_WithNullHttpRoute_ReturnsActivityDisplayName() + public void GetNewOperationName_WithNullHttpRoute_ReturnsUrlWithVerb() { // Arrange - using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(GetNewOperationName_WithNullHttpRoute_ReturnsActivityDisplayName)).Build(); - using var activitySource = new ActivitySource(nameof(GetNewOperationName_WithNullHttpRoute_ReturnsActivityDisplayName)); + using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(GetNewOperationName_WithNullHttpRoute_ReturnsUrlWithVerb)).Build(); + using var activitySource = new ActivitySource(nameof(GetNewOperationName_WithNullHttpRoute_ReturnsUrlWithVerb)); using var activity = activitySource.StartActivity( ActivityName, ActivityKind.Server); @@ -102,12 +102,14 @@ public void GetNewOperationName_WithNullHttpRoute_ReturnsActivityDisplayName() var tagObjects = AzMonList.Initialize(); AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "POST")); + AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeServerAddress, "example.com")); + AzMonList.Add(ref tagObjects, new KeyValuePair(SemanticConventions.AttributeUrlScheme, "http")); // Act var result = TraceHelper.GetNewSchemaOperationName(activity!, url: null, ref tagObjects); // Assert - Assert.Equal(ActivityName, result); + Assert.Equal("POST http://example.com", result); } [Fact] @@ -132,6 +134,54 @@ public void GetNewOperationName_WithNullUrl_ReturnsFormattedStringFromMappedTags Assert.Equal("GET /api/test", result); } + [Fact] + public void GetHttpOperationNameAndUrl_V1() + { + // Arrange + using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(GetNewOperationName_WithNullUrl_ReturnsFormattedStringFromMappedTags)).Build(); + using var activitySource = new ActivitySource(nameof(GetNewOperationName_WithNullUrl_ReturnsFormattedStringFromMappedTags)); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Server); + activity?.Stop(); + + var httpMappedTags = AzMonList.Initialize(); + AzMonList.Add(ref httpMappedTags, new KeyValuePair(SemanticConventions.AttributeHttpUrl, "https://example.com")); + AzMonList.Add(ref httpMappedTags, new KeyValuePair(SemanticConventions.AttributeHttpMethod, "GET")); + + // Act + var (requestUrl, operationName) = TraceHelper.GetHttpOperationNameAndUrl(activity!.DisplayName, OperationType.Http, ref httpMappedTags); + + // Assert + Assert.Equal("https://example.com", requestUrl); + Assert.Equal("GET /", operationName); + } + + [Fact] + public void GetHttpOperationNameAndUrl_V2() + { + // Arrange + using var tracerProvider = Sdk.CreateTracerProviderBuilder().AddSource(nameof(GetNewOperationName_WithNullUrl_ReturnsFormattedStringFromMappedTags)).Build(); + using var activitySource = new ActivitySource(nameof(GetNewOperationName_WithNullUrl_ReturnsFormattedStringFromMappedTags)); + using var activity = activitySource.StartActivity( + ActivityName, + ActivityKind.Server); + activity?.Stop(); + + var httpMappedTags = AzMonList.Initialize(); + AzMonList.Add(ref httpMappedTags, new KeyValuePair(SemanticConventions.AttributeUrlScheme, "http")); + AzMonList.Add(ref httpMappedTags, new KeyValuePair(SemanticConventions.AttributeServerAddress, "example.com")); + AzMonList.Add(ref httpMappedTags, new KeyValuePair(SemanticConventions.AttributeHttpRequestMethod, "GET")); + AzMonList.Add(ref httpMappedTags, new KeyValuePair(SemanticConventions.AttributeUrlPath, "/search")); + + // Act + var (requestUrl, operationName) = TraceHelper.GetHttpOperationNameAndUrl(activity!.DisplayName, OperationType.V2, ref httpMappedTags); + + // Assert + Assert.Equal("http://example.com/search", requestUrl); + Assert.Equal("GET /search", operationName); + } + private string? GetExpectedMSlinks(IEnumerable links) { if (links != null && links.Any())