Skip to content
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

Azure Monitor Exporter - Match HTTP URL retrieval to OpenTelemetry specification #14834

Merged
merged 23 commits into from
Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f36e198
Add EventSource
rajkumar-rangaraj Aug 20, 2020
f3b7976
Add Changelog,readme
rajkumar-rangaraj Aug 20, 2020
76304cc
Header to readme
rajkumar-rangaraj Aug 20, 2020
ddc78a7
Readme header change.
rajkumar-rangaraj Aug 20, 2020
e87111a
update readme
rajkumar-rangaraj Aug 20, 2020
b982866
Merge remote-tracking branch 'origin/master' into rajrang/sdkver
rajkumar-rangaraj Aug 21, 2020
9cc4fdf
Add Sdkversion
rajkumar-rangaraj Aug 21, 2020
64afaa5
Addressing PR feedback
rajkumar-rangaraj Aug 21, 2020
4524d60
Handled exception in SdkVersionUtils
rajkumar-rangaraj Aug 21, 2020
d23978c
Fixed indent
rajkumar-rangaraj Aug 21, 2020
a3077ac
Tags parsing, duration fix for dependency.
rajkumar-rangaraj Aug 25, 2020
d5c8b97
Resolve conflict
rajkumar-rangaraj Aug 25, 2020
c2a1208
Renamed GetHttpUrl
rajkumar-rangaraj Aug 27, 2020
cf2749c
Merge remote-tracking branch 'origin/master' into rajrang/tags
rajkumar-rangaraj Aug 27, 2020
830efa7
status change
rajkumar-rangaraj Aug 28, 2020
271cde7
Modify to xunit
rajkumar-rangaraj Aug 31, 2020
3994f8d
Merge
rajkumar-rangaraj Aug 31, 2020
3101044
Added test for GetUrl
rajkumar-rangaraj Sep 3, 2020
e7e42bd
Merge remote-tracking branch 'origin/master' into rajrang/tags
rajkumar-rangaraj Sep 3, 2020
21f0910
Incorporating PR comments
rajkumar-rangaraj Sep 3, 2020
1fe5c3d
Revert extension/RDD change
rajkumar-rangaraj Sep 3, 2020
fef68a9
Merge remote-tracking branch 'origin/master' into rajrang/tags
rajkumar-rangaraj Sep 3, 2020
3a33480
Add method summary.
rajkumar-rangaraj Sep 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,11 @@
// Licensed under the MIT License.

using System.Diagnostics;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Exporter.AzureMonitor
{
internal static class ActivityExtensions
{
private const string StatusCode_0 = "0";
private const string StatusCode_200 = "200";
private const string StatusCode_400 = "400";
private const string StatusCode_401 = "401";
private const string StatusCode_403 = "403";
private const string StatusCode_404 = "404";
private const string StatusCode_409 = "409";
private const string StatusCode_412 = "412";
private const string StatusCode_500 = "500";
private const string StatusCode_501 = "501";
private const string StatusCode_503 = "503";
private const string StatusCode_504 = "504";

internal static TelemetryType GetTelemetryType(this Activity activity)
{
var kind = activity.Kind switch
Expand All @@ -34,30 +20,5 @@ internal static TelemetryType GetTelemetryType(this Activity activity)

return kind;
}

// TODO: Change the return type to integer once .NET support it.
internal static string GetStatusCode(this Activity activity)
{
var status = activity.GetStatus().CanonicalCode switch
{
StatusCanonicalCode.Cancelled => StatusCode_400,
StatusCanonicalCode.InvalidArgument => StatusCode_400,
StatusCanonicalCode.DeadlineExceeded => StatusCode_504,
StatusCanonicalCode.NotFound => StatusCode_404,
StatusCanonicalCode.AlreadyExists => StatusCode_409,
StatusCanonicalCode.PermissionDenied => StatusCode_403,
StatusCanonicalCode.ResourceExhausted => StatusCode_409,
StatusCanonicalCode.FailedPrecondition => StatusCode_412,
StatusCanonicalCode.OutOfRange => StatusCode_400,
StatusCanonicalCode.Unimplemented => StatusCode_501,
StatusCanonicalCode.Internal => StatusCode_500,
StatusCanonicalCode.Unavailable => StatusCode_503,
StatusCanonicalCode.Unauthenticated => StatusCode_401,
StatusCanonicalCode.Ok => StatusCode_200,
_ => StatusCode_0
};

return status;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
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.ConnectionString;
using OpenTelemetry.Exporter.AzureMonitor.Models;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Exporter.AzureMonitor
{
Expand Down Expand Up @@ -101,42 +101,47 @@ 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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 = GetHttpStatusCode(tags);
var success = GetSuccessFromHttpStatusCode(statusCode);
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)
{
Id = activity.Context.SpanId.ToHexString(),
Success = activity.GetStatus().IsOk
Id = activity.Context.SpanId.ToHexString()
};

// 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();
var statusCode = GetHttpStatusCode(tags);
dependency.ResultCode = statusCode;
dependency.Success = GetSuccessFromHttpStatusCode(statusCode);
}

// TODO: Handle dependency.target.
Expand All @@ -146,30 +151,27 @@ private MonitorBase GenerateTelemetryData(Activity activity)
return telemetry;
}

private static string GetHttpUrl(IEnumerable<KeyValuePair<string, string>> tags)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetHttpStatusCode(Dictionary<string, string> tags)
{
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)
if (tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status))
{
return url;
return status;
}

httpTags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost);

if (httpHost != null)
{
httpTags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme);
httpTags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget);
url = httpScheme + httpHost + httpTarget;
return url;
}
return "0";
}

// TODO: Follow spec - https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool GetSuccessFromHttpStatusCode(string statusCode)
{
return statusCode == "200" || statusCode == "Ok";
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetMessagingUrl(Dictionary<string, string> tags)
{
tags.TryGetValue(SemanticConventions.AttributeMessagingUrl, out var url);
return url;
}

Expand Down
17 changes: 17 additions & 0 deletions sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/PartBType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace OpenTelemetry.Exporter.AzureMonitor
{
internal enum PartBType
{
Unknown,
Http,
Db,
Messaging,
Rpc,
FaaS,
Net
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ internal static class SemanticConventions

public const string AttributeFaasTrigger = "faas.trigger";
public const string AttributeFaasExecution = "faas.execution";
public const string AttributeFaasColdStart = "faas.coldstart";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sort alphabetically to make it easier for future maintenance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whole file needs rearrangement will get this covered in different PR.

public const string AttributeFaasDocumentCollection = "faas.document.collection";
public const string AttributeFaasDocumentOperation = "faas.document.operation";
public const string AttributeFaasDocumentTime = "faas.document.time";
Expand Down
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
{
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;
}

}
}
72 changes: 72 additions & 0 deletions sdk/monitor/OpenTelemetry.Exporter.AzureMonitor/src/UrlHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace OpenTelemetry.Exporter.AzureMonitor
{
internal class UrlHelper
{
private const string SchemePostfix = "://";
private const string Colon = ":";

/// <summary>
/// This method follows OpenTelemetry specification to retrieve http URL.
/// Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/http.md#http-client.
/// </summary>
/// <param name="tags">Activity Tags</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string GetUrl(Dictionary<string, string> tags)
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
{
if (tags.TryGetValue(SemanticConventions.AttributeHttpUrl, out var url))
{
if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri) && uri.IsAbsoluteUri)
{
return url;
}
}

if (tags.TryGetValue(SemanticConventions.AttributeHttpScheme, out var httpScheme))
{
tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget);
if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var httpHost) && !string.IsNullOrWhiteSpace(httpHost))
{
tags.TryGetValue(SemanticConventions.AttributeHttpHostPort, out var httpPort);
if (httpPort != null && httpPort != "80" && httpPort != "443")
{
url = $"{httpScheme}{SchemePostfix}{httpHost}{Colon}{httpPort}{httpTarget}";
}
else
{
url = $"{httpScheme}{SchemePostfix}{httpHost}{httpTarget}";
}

return url;
}
else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerName, out var netPeerName)
&& tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out var netPeerPort))
{
return string.IsNullOrWhiteSpace(netPeerName) ? null : $"{httpScheme}{SchemePostfix}{netPeerName}{(string.IsNullOrWhiteSpace(netPeerPort) ? null : Colon)}{netPeerPort}{httpTarget}";
}
else if (tags.TryGetValue(SemanticConventions.AttributeNetPeerIp, out var netPeerIP)
&& tags.TryGetValue(SemanticConventions.AttributeNetPeerPort, out netPeerPort))
{
return string.IsNullOrWhiteSpace(netPeerIP) ? null : $"{httpScheme}{SchemePostfix}{netPeerIP}{(string.IsNullOrWhiteSpace(netPeerPort) ? null : Colon)}{netPeerPort}{httpTarget}";
}
}

if (tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host) && !string.IsNullOrWhiteSpace(host))
{
tags.TryGetValue(SemanticConventions.AttributeHttpTarget, out var httpTarget);
tags.TryGetValue(SemanticConventions.AttributeHttpHostPort, out var httpPort);
url = $"{host}{(string.IsNullOrWhiteSpace(httpPort) ? null : Colon)}{httpPort}{httpTarget}";
return url;
}

return string.IsNullOrWhiteSpace(url) ? null : url;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public void Start(string url)
}

activity?.SetTag("http.url", context.Request.Url.AbsolutePath);
activity?.SetTag("http.host", context.Request.Url.Host);

string requestContent;
using (var childSpan = source.StartActivity("ReadStream", ActivityKind.Consumer))
Expand Down
Loading