-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Azure Monitor Exporter - Match HTTP URL retrieval to OpenTelemetry specification #14834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 19 commits
f36e198
f3b7976
76304cc
ddc78a7
e87111a
b982866
9cc4fdf
64afaa5
4524d60
d23978c
a3077ac
d5c8b97
c2a1208
cf2749c
830efa7
271cde7
3994f8d
3101044
e7e42bd
21f0910
1fe5c3d
fef68a9
3a33480
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,18 +6,23 @@ | |
| using System.Diagnostics; | ||
| using System.Globalization; | ||
| using System.Linq; | ||
| using System.Runtime.CompilerServices; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using Azure.Core.Pipeline; | ||
| using OpenTelemetry.Exporter.AzureMonitor.Extensions; | ||
|
|
||
| using OpenTelemetry.Exporter.AzureMonitor.ConnectionString; | ||
| using OpenTelemetry.Exporter.AzureMonitor.Models; | ||
| using OpenTelemetry.Trace; | ||
|
|
||
| namespace OpenTelemetry.Exporter.AzureMonitor | ||
| { | ||
| internal class AzureMonitorTransmitter | ||
| { | ||
| private const string StatusCode200 = "200"; | ||
|
||
| private const string StatusCode0 = "0"; | ||
| private const string StatusCodeOk = "Ok"; | ||
|
|
||
| private readonly ServiceRestClient serviceRestClient; | ||
| private readonly AzureMonitorExporterOptions options; | ||
| private readonly string instrumentationKey; | ||
|
|
@@ -101,42 +106,46 @@ private static TelemetryEnvelope GeneratePartAEnvelope(Activity activity) | |
|
|
||
| private MonitorBase GenerateTelemetryData(Activity activity) | ||
| { | ||
| MonitorBase telemetry = new MonitorBase(); | ||
|
|
||
| var telemetryType = activity.GetTelemetryType(); | ||
| telemetry.BaseType = Telemetry_Base_Type_Mapping[telemetryType]; | ||
| string url = GetHttpUrl(activity.Tags); | ||
| var tags = activity.Tags.ToAzureMonitorTags(out var activityType); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should be using TagObjects instead of Tags. Tags only contain a subset of TagObjects which are string value.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a TODO section, will get this handled in a separate PR. This changes requires a custom serialization to generated classes. |
||
| MonitorBase telemetry = new MonitorBase | ||
| { | ||
| BaseType = Telemetry_Base_Type_Mapping[telemetryType] | ||
| }; | ||
|
|
||
| if (telemetryType == TelemetryType.Request) | ||
| { | ||
| var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), activity.GetStatus().IsOk, activity.GetStatusCode()) | ||
| var url = activity.Kind == ActivityKind.Server ? UrlHelper.GetUrl(tags) : GetMessagingUrl(tags); | ||
rajkumar-rangaraj marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| var statusCode = GetStatus(tags, out bool success) ; | ||
rajkumar-rangaraj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode) | ||
| { | ||
| Name = activity.DisplayName, | ||
| Url = url, | ||
| // TODO: Handle request.source. | ||
| }; | ||
|
|
||
| // TODO: Handle activity.TagObjects | ||
| ExtractPropertiesFromTags(request.Properties, activity.Tags); | ||
| // TODO: Handle activity.TagObjects, extract well-known tags | ||
| // ExtractPropertiesFromTags(request.Properties, activity.Tags); | ||
|
|
||
| telemetry.BaseData = request; | ||
| } | ||
| else if (telemetryType == TelemetryType.Dependency) | ||
| { | ||
| var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration) | ||
| var statusCode = GetStatus(tags, out bool success); | ||
rajkumar-rangaraj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
rajkumar-rangaraj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture)) | ||
| { | ||
| Id = activity.Context.SpanId.ToHexString(), | ||
| Success = activity.GetStatus().IsOk | ||
| Success = success | ||
| }; | ||
|
|
||
| // TODO: Handle activity.TagObjects | ||
| ExtractPropertiesFromTags(dependency.Properties, activity.Tags); | ||
| // ExtractPropertiesFromTags(dependency.Properties, activity.Tags); | ||
|
|
||
| if (url != null) | ||
| if (activityType == PartBType.Http) | ||
| { | ||
| dependency.Data = url; | ||
| dependency.Data = UrlHelper.GetUrl(tags); | ||
| dependency.Type = "HTTP"; // TODO: Parse for storage / SB. | ||
| dependency.ResultCode = activity.GetStatusCode(); | ||
| dependency.ResultCode = statusCode; | ||
| } | ||
|
|
||
| // TODO: Handle dependency.target. | ||
|
|
@@ -146,30 +155,19 @@ private MonitorBase GenerateTelemetryData(Activity activity) | |
| return telemetry; | ||
| } | ||
|
|
||
| private static string GetHttpUrl(IEnumerable<KeyValuePair<string, string>> tags) | ||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| private static string GetStatus(Dictionary<string, string> tags, out bool success) | ||
| { | ||
| var httpTags = tags.Where(item => item.Key.StartsWith("http.", StringComparison.InvariantCulture)) | ||
| .ToDictionary(item => item.Key, item => item.Value); | ||
|
|
||
|
|
||
| httpTags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url); | ||
| if (url != null) | ||
| { | ||
| return url; | ||
| } | ||
|
|
||
| httpTags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost); | ||
| tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status); | ||
| success = status == StatusCode200 || status == StatusCodeOk; | ||
|
|
||
| if (httpHost != null) | ||
| { | ||
| httpTags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme); | ||
| httpTags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget); | ||
| url = httpScheme + httpHost + httpTarget; | ||
| return url; | ||
| } | ||
|
|
||
| // TODO: Follow spec - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client | ||
| return status ?? StatusCode0; | ||
| } | ||
|
|
||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| private static string GetMessagingUrl(Dictionary<string, string> tags) | ||
| { | ||
| tags.TryGetValue(SemanticConventions.AttributeMessagingUrl, out var url); | ||
| return url; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Diagnostics; | ||
|
|
||
| namespace OpenTelemetry.Exporter.AzureMonitor.Extensions | ||
TimothyMothra marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| internal static class ActivityExtensions | ||
| { | ||
| internal static TelemetryType GetTelemetryType(this Activity activity) | ||
| { | ||
| var kind = activity.Kind switch | ||
| { | ||
| ActivityKind.Server => TelemetryType.Request, | ||
| ActivityKind.Client => TelemetryType.Dependency, | ||
| ActivityKind.Producer => TelemetryType.Dependency, | ||
| ActivityKind.Consumer => TelemetryType.Request, | ||
| _ => TelemetryType.Dependency | ||
| }; | ||
|
|
||
| return kind; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System; | ||
| using System.Globalization; | ||
| using System.Threading; | ||
|
|
||
| namespace OpenTelemetry.Exporter.AzureMonitor.Extensions | ||
| { | ||
| internal static class ExceptionExtensions | ||
| { | ||
| internal static string ToInvariantString(this Exception exception) | ||
| { | ||
| var originalUICulture = Thread.CurrentThread.CurrentUICulture; | ||
|
|
||
| try | ||
| { | ||
| Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; | ||
| return exception.ToString(); | ||
| } | ||
| finally | ||
| { | ||
| Thread.CurrentThread.CurrentUICulture = originalUICulture; | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using System.Collections.Generic; | ||
|
|
||
| namespace OpenTelemetry.Exporter.AzureMonitor.Extensions | ||
| { | ||
| internal static class TagsExtension | ||
| { | ||
| private static readonly IReadOnlyDictionary<string, PartBType> Part_B_Mapping = new Dictionary<string, PartBType>() | ||
| { | ||
| [SemanticConventions.AttributeDbSystem] = PartBType.Db, | ||
| [SemanticConventions.AttributeDbConnectionString] = PartBType.Db, | ||
| [SemanticConventions.AttributeDbUser] = PartBType.Db, | ||
|
|
||
| [SemanticConventions.AttributeHttpMethod] = PartBType.Http, | ||
| [SemanticConventions.AttributeHttpUrl] = PartBType.Http, | ||
| [SemanticConventions.AttributeHttpStatusCode] = PartBType.Http, | ||
| [SemanticConventions.AttributeHttpScheme] = PartBType.Http, | ||
| [SemanticConventions.AttributeHttpHost] = 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.AttributeRpcSystem] = PartBType.Rpc, | ||
| [SemanticConventions.AttributeRpcService] = PartBType.Rpc, | ||
| [SemanticConventions.AttributeRpcMethod] = PartBType.Rpc, | ||
|
|
||
| [SemanticConventions.AttributeFaasTrigger] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasExecution] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasColdStart] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasDocumentCollection] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasDocumentOperation] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasDocumentTime] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasDocumentName] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasCron] = PartBType.FaaS, | ||
| [SemanticConventions.AttributeFaasTime] = PartBType.FaaS, | ||
|
|
||
| [SemanticConventions.AttributeMessagingSystem] = PartBType.Messaging, | ||
| [SemanticConventions.AttributeMessagingDestination] = PartBType.Messaging, | ||
| [SemanticConventions.AttributeMessagingDestinationKind] = PartBType.Messaging, | ||
| [SemanticConventions.AttributeMessagingTempDestination] = PartBType.Messaging, | ||
| [SemanticConventions.AttributeMessagingUrl] = PartBType.Messaging | ||
| }; | ||
|
|
||
| internal static Dictionary<string, string> ToAzureMonitorTags(this IEnumerable<KeyValuePair<string, string>> tags, out PartBType activityType) | ||
| { | ||
| Dictionary<string, string> partBTags = new Dictionary<string, string>(); | ||
| activityType = PartBType.Unknown; | ||
|
|
||
| foreach (var entry in tags) | ||
| { | ||
| // TODO: May need to store unknown to write to properties as Part C | ||
| if ((activityType == PartBType.Unknown || activityType == PartBType.Net) && !Part_B_Mapping.TryGetValue(entry.Key, out activityType)) | ||
| { | ||
| if (activityType == PartBType.Net) | ||
| { | ||
| partBTags.Add(entry.Key, entry.Value); | ||
| activityType = PartBType.Unknown; | ||
| } | ||
|
|
||
| continue; | ||
| } | ||
|
|
||
| if (Part_B_Mapping.TryGetValue(entry.Key, out var tempActivityType) && (tempActivityType == activityType || tempActivityType == PartBType.Net)) | ||
| { | ||
| partBTags.Add(entry.Key, entry.Value); | ||
| } | ||
| } | ||
|
|
||
| return partBTags; | ||
| } | ||
|
|
||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.