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 19 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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Diagnostics.Tracing;
using OpenTelemetry.Exporter.AzureMonitor.Extensions;

namespace OpenTelemetry.Exporter.AzureMonitor
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Member

Choose a reason for hiding this comment

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

If this is only supposed to be used in one place, probably no need to extract a const?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used in status validation for all activities and will be used for complete life cycle.

private const string StatusCode0 = "0";
private const string StatusCodeOk = "Ok";

private readonly ServiceRestClient serviceRestClient;
private readonly AzureMonitorExporterOptions options;
private readonly string instrumentationKey;
Expand Down Expand Up @@ -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);
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);
var statusCode = GetStatus(tags, out bool success) ;
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);
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.
Expand All @@ -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;
}

Expand Down
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
{
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.

Loading