diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorConverter.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorConverter.cs index d8b18e81b9b6e..b69125b99fd3d 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorConverter.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/AzureMonitorConverter.cs @@ -20,6 +20,7 @@ namespace OpenTelemetry.Exporter.AzureMonitor /// internal static class AzureMonitorConverter { + private const int Version2 = 2; // The ingestion schema version private static readonly IReadOnlyDictionary Telemetry_Base_Type_Mapping = new Dictionary { [TelemetryType.Request] = "RequestData", @@ -119,41 +120,58 @@ private static MonitorBase GenerateTelemetryData(Activity activity) if (telemetryType == TelemetryType.Request) { - var url = activity.Kind == ActivityKind.Server ? UrlHelper.GetUrl(partBTags) : GetMessagingUrl(partBTags); - var statusCode = GetHttpStatusCode(partBTags); - var success = GetSuccessFromHttpStatusCode(statusCode); - var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) + string source = null; + string statusCode = string.Empty; + string url = null; + bool success = true; + + switch (activityType) + { + case PartBType.Http: + url = activity.Kind == ActivityKind.Server ? UrlHelper.GetUrl(partBTags) : AzureMonitorConverter.GetMessagingUrl(partBTags); + statusCode = GetHttpStatusCode(partBTags); + success = GetSuccessFromHttpStatusCode(statusCode); + break; + case PartBType.Azure: + ComponentHelper.ExtractComponentProperties(partBTags, activity.Kind, out _, out source); + break; + } + + RequestData request = new RequestData(Version2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) { Name = activity.DisplayName, Url = url, - // TODO: Handle request.source. + Source = source }; - // TODO: Handle activity.TagObjects, extract well-known tags - // ExtractPropertiesFromTags(request.Properties, activity.Tags); - + AddPropertiesToTelemetry(request.Properties, PartCTags); telemetry.BaseData = request; } else if (telemetryType == TelemetryType.Dependency) { - var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) + var dependency = new RemoteDependencyData(Version2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) { Id = activity.Context.SpanId.ToHexString() }; - // TODO: Handle activity.TagObjects - // ExtractPropertiesFromTags(dependency.Properties, activity.Tags); - - if (activityType == PartBType.Http) + switch (activityType) { - dependency.Data = UrlHelper.GetUrl(partBTags); - dependency.Type = "HTTP"; // TODO: Parse for storage / SB. - var statusCode = GetHttpStatusCode(partBTags); - dependency.ResultCode = statusCode; - dependency.Success = GetSuccessFromHttpStatusCode(statusCode); + case PartBType.Http: + dependency.Data = UrlHelper.GetUrl(partBTags); + dependency.Target = GetHost(partBTags); + dependency.Type = RemoteDependencyConstants.HTTP; + var statusCode = GetHttpStatusCode(partBTags); + dependency.ResultCode = statusCode; + dependency.Success = GetSuccessFromHttpStatusCode(statusCode); + break; + case PartBType.Azure: + ComponentHelper.ExtractComponentProperties(partBTags, activity.Kind, out var type, out var target); + dependency.Target = target; + dependency.Type = type; + break; } - // TODO: Handle dependency.target. + AddPropertiesToTelemetry(dependency.Properties, PartCTags); telemetry.BaseData = dependency; } @@ -178,16 +196,32 @@ private static bool GetSuccessFromHttpStatusCode(string statusCode) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static string GetMessagingUrl(Dictionary tags) + internal static string GetHost(Dictionary tags) { - tags.TryGetValue(SemanticConventions.AttributeMessagingUrl, out var url); - return url; + if (tags != null && tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host)) + { + return host; + } + + return null; } - private static void ExtractPropertiesFromTags(IDictionary destination, IEnumerable> tags) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string GetMessagingUrl(Dictionary tags) + { + if (tags != null && tags.TryGetValue(SemanticConventions.AttributeMessagingUrl, out var url)) + { + return url; + } + + return null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void AddPropertiesToTelemetry(IDictionary destination, IEnumerable> PartCTags) { // TODO: Iterate only interested fields. Ref: https://github.com/Azure/azure-sdk-for-net/pull/14254#discussion_r470907560 - foreach (var tag in tags.Where(item => !item.Key.StartsWith("http.", StringComparison.InvariantCulture))) + foreach (var tag in PartCTags) { destination.Add(tag); } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ComponentHelper.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ComponentHelper.cs new file mode 100644 index 0000000000000..670a57cfc0236 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/ComponentHelper.cs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal class ComponentHelper + { + private const string ehInProcKind = "InProc | Azure Event Hubs"; + private const string ehQueueKind = "Queue Message | Azure Event Hubs"; + private const string inProcKind = "InProc | "; + private const string queueKind = "Queue Message | "; + + internal static void ExtractComponentProperties(Dictionary tags, ActivityKind activityKind, out string type, out string sourceOrTarget) + { + sourceOrTarget = null; + type = null; + + if (!tags.TryGetValue(SemanticConventions.AttributeAzureNameSpace, out var component)) + { + tags.TryGetValue(SemanticConventions.AttributeComponent, out component); + } + + if (component == null) + { + return; + } + + switch (component) + { + case "eventhubs": + case "Microsoft.EventHub": + type = activityKind switch + { + ActivityKind.Internal => ehInProcKind, + ActivityKind.Producer => ehQueueKind, + _ => RemoteDependencyConstants.AzureEventHubs, + }; + tags.TryGetValue(SemanticConventions.AttributeEndpointAddress, out var endpoint); + tags.TryGetValue(SemanticConventions.AttributeMessageBusDestination, out var queueName); + + if (endpoint == null || queueName == null) + { + return; + } + + // Target uniquely identifies the resource, we use both: queueName and endpoint + // with schema used for SQL-dependencies + string separator = "/"; + if (endpoint.EndsWith(separator, StringComparison.Ordinal)) + { + separator = string.Empty; + } + + sourceOrTarget = string.Concat(endpoint, separator, queueName); + + break; + default: + type = activityKind switch + { + ActivityKind.Internal => inProcKind + component, + ActivityKind.Producer => queueKind + component, + _ => component, + }; + break; + } + } + } +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs index b2f94a4b9f915..a318d9e517ebe 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs @@ -6,12 +6,13 @@ namespace OpenTelemetry.Exporter.AzureMonitor internal enum PartBType { Unknown, - Http, + Azure, + Common, Db, - Messaging, - Rpc, FaaS, - Net + Http, + Messaging, + Rpc }; } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyConstants.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyConstants.cs new file mode 100644 index 0000000000000..04df14e724555 --- /dev/null +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/RemoteDependencyConstants.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace OpenTelemetry.Exporter.AzureMonitor +{ + internal static class RemoteDependencyConstants + { + public const string AzureBlob = "Azure blob"; + public const string AzureDocumentDb = "Azure DocumentDB"; + public const string AzureEventHubs = "Azure Event Hubs"; + public const string AzureIotHub = "Azure IoT Hub"; + public const string AzureQueue = "Azure queue"; + public const string AzureTable = "Azure table"; + public const string AzureSearch = "Azure Search"; + public const string AzureServiceBus = "Azure Service Bus"; + + public const string SQL = "SQL"; + public const string HTTP = "Http"; + public const string AI = "Http (tracked component)"; + + public const string InProc = "InProc"; + public const string QueueMessage = "Queue Message"; + + public const string WcfService = "WCF Service"; + public const string WebService = "Web Service"; + + public const string HttpRequestOperationDetailName = "HttpRequest"; + public const string HttpResponseOperationDetailName = "HttpResponse"; + public const string HttpResponseHeadersOperationDetailName = "HttpResponseHeaders"; + + public const string SqlCommandOperationDetailName = "SqlCommand"; + + public const string DependencyErrorPropertyKey = "Error"; + } +} diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs index 6ceb60002b4a9..af6f11ff6f6e5 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/SemanticConventions.cs @@ -134,5 +134,10 @@ internal static class SemanticConventions public const string AttributeMessagingPayloadSize = "messaging.message_payload_size_bytes"; public const string AttributeMessagingPayloadCompressedSize = "messaging.message_payload_compressed_size_bytes"; public const string AttributeMessagingOperation = "messaging.operation"; + + public const string AttributeEndpointAddress = "peer.address"; + public const string AttributeMessageBusDestination = "message_bus.destination"; + + public const string AttributeAzureNameSpace = "az.namespace"; } } diff --git a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs index e43eda421859f..8c404f32a883c 100644 --- a/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs +++ b/sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/TagsExtension.cs @@ -23,13 +23,14 @@ internal static class TagsExtension [SemanticConventions.AttributeHttpHostPort] = PartBType.Http, [SemanticConventions.AttributeHttpTarget] = PartBType.Http, - [SemanticConventions.AttributeNetPeerName] = PartBType.Net, - [SemanticConventions.AttributeNetPeerIp] = PartBType.Net, - [SemanticConventions.AttributeNetPeerPort] = PartBType.Net, - [SemanticConventions.AttributeNetTransport] = PartBType.Net, - [SemanticConventions.AttributeNetHostIp] = PartBType.Net, - [SemanticConventions.AttributeNetHostPort] = PartBType.Net, - [SemanticConventions.AttributeNetHostName] = PartBType.Net, + [SemanticConventions.AttributeNetPeerName] = PartBType.Common, + [SemanticConventions.AttributeNetPeerIp] = PartBType.Common, + [SemanticConventions.AttributeNetPeerPort] = PartBType.Common, + [SemanticConventions.AttributeNetTransport] = PartBType.Common, + [SemanticConventions.AttributeNetHostIp] = PartBType.Common, + [SemanticConventions.AttributeNetHostPort] = PartBType.Common, + [SemanticConventions.AttributeNetHostName] = PartBType.Common, + [SemanticConventions.AttributeComponent] = PartBType.Common, [SemanticConventions.AttributeRpcSystem] = PartBType.Rpc, [SemanticConventions.AttributeRpcService] = PartBType.Rpc, @@ -45,6 +46,10 @@ internal static class TagsExtension [SemanticConventions.AttributeFaasCron] = PartBType.FaaS, [SemanticConventions.AttributeFaasTime] = PartBType.FaaS, + [SemanticConventions.AttributeAzureNameSpace] = PartBType.Azure, + [SemanticConventions.AttributeEndpointAddress] = PartBType.Azure, + [SemanticConventions.AttributeMessageBusDestination] = PartBType.Azure, + [SemanticConventions.AttributeMessagingSystem] = PartBType.Messaging, [SemanticConventions.AttributeMessagingDestination] = PartBType.Messaging, [SemanticConventions.AttributeMessagingDestinationKind] = PartBType.Messaging, @@ -99,12 +104,12 @@ internal static PartBType ToAzureMonitorTags(this IEnumerable + { + ["az.namespace"] = "Microsoft.EventHub" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Producer, out var type, out var target); + + Assert.Equal("Queue Message | Azure Event Hubs", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreCollectedProducerKindWithAzNamespaceEventHubsAndAttributes() + { + var partBTags = new Dictionary + { + ["az.namespace"] = "Microsoft.EventHub", + ["peer.address"] = "amqps://eventHub.servicebus.windows.net/", + ["message_bus.destination"] = "queueName", + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Producer, out var type, out var target); + + Assert.Equal("Queue Message | Azure Event Hubs", type); + Assert.Equal("amqps://eventHub.servicebus.windows.net/queueName", target); + } + + [Fact] + public void PartBTagsAreCollectedInternalKindWithAzNamespaceEventHubs() + { + var partBTags = new Dictionary + { + ["az.namespace"] = "Microsoft.EventHub" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Internal, out var type, out var target); + + Assert.Equal("InProc | Azure Event Hubs", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreAreCollectedProducerKindWithAzNamespace() + { + var partBTags = new Dictionary + { + ["az.namespace"] = "foo" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Producer, out var type, out var target); + + Assert.Equal("Queue Message | foo", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreCollectedInternalKindWithAzNamespace() + { + var partBTags = new Dictionary + { + ["az.namespace"] = "foo" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Internal, out var type, out var target); + + Assert.Equal("InProc | foo", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreCollectedServerKindEventHubs() + { + var partBTags = new Dictionary + { + ["az.namespace"] = "Microsoft.EventHub", + ["peer.address"] = "amqps://eventHub.servicebus.windows.net/", + ["message_bus.destination"] = "queueName", + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Server, out var _, out var source); + Assert.Equal("amqps://eventHub.servicebus.windows.net/queueName", source); + } + + [Fact] + public void PartBTagsAreCollectedConsumerKindEventHubs() + { + var partBTags = new Dictionary + { + ["az.namespace"] = "Microsoft.EventHub", + ["peer.address"] = "amqps://eventHub.servicebus.windows.net/", + ["message_bus.destination"] = "queueName", + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Consumer, out var _, out var source); + Assert.Equal("amqps://eventHub.servicebus.windows.net/queueName", source); + } + + [Fact] + public void PartBTagsAreCollectedProducerKindWithComponentEventHubs() + { + var partBTags = new Dictionary + { + ["component"] = "eventhubs" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Producer, out var type, out var target); + + Assert.Equal("Queue Message | Azure Event Hubs", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreCollectedProducerKindWithComponent() + { + var partBTags = new Dictionary + { + ["component"] = "foo" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Producer, out var type, out var target); + + Assert.Equal("Queue Message | foo", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreCollectedInternalKindWithComponent() + { + var partBTags = new Dictionary + { + ["component"] = "foo" + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Internal, out var type, out var target); + + Assert.Equal("InProc | foo", type); + Assert.Null(target); + } + + [Fact] + public void PartBTagsAreCollectedForEventHubs() + { + var partBTags = new Dictionary + { + ["component"] = "eventhubs", + ["peer.address"] = "amqps://eventHub.servicebus.windows.net/", + ["message_bus.destination"] = "queueName", + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Client, out var type, out var target); + + Assert.Equal("Azure Event Hubs", type); + Assert.Equal("amqps://eventHub.servicebus.windows.net/queueName", target); + } + + [Fact] + public void PartBTagsAreCollectedForEventHubsMessages() + { + var partBTags = new Dictionary + { + ["component"] = "eventhubs", + ["peer.address"] = "amqps://eventHub.servicebus.windows.net/", + ["message_bus.destination"] = "queueName", + }; + + ComponentHelper.ExtractComponentProperties(partBTags, ActivityKind.Producer, out var type, out var target); + Assert.Equal("Queue Message | Azure Event Hubs", type); + Assert.Equal("amqps://eventHub.servicebus.windows.net/queueName", target); + } + } +}