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 - Parse dependency types #15533

Closed
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
@@ -1,17 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
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.HttpParsers;
using OpenTelemetry.Exporter.AzureMonitor.Models;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
Expand Down Expand Up @@ -148,19 +146,31 @@ private 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 ? HttpHelper.GetUrl(partBTags) : ComponentHelper.GetMessagingUrl(partBTags);
statusCode = HttpHelper.GetHttpStatusCode(partBTags);
success = HttpHelper.GetSuccessFromHttpStatusCode(statusCode);
break;
case PartBType.Azure:
ComponentHelper.ExtractComponentProperties(partBTags, activity.Kind, out _, out source);
break;
}

RequestData 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.
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)
Expand All @@ -170,53 +180,47 @@ private MonitorBase GenerateTelemetryData(Activity activity)
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 = HttpHelper.GetUrl(partBTags);
dependency.Target = HttpHelper.GetHost(partBTags);
bool parsed = AzureBlobHttpParser.TryParse(ref dependency)
reyang marked this conversation as resolved.
Show resolved Hide resolved
|| AzureTableHttpParser.TryParse(ref dependency)
|| AzureQueueHttpParser.TryParse(ref dependency)
|| DocumentDbHttpParser.TryParse(ref dependency)
|| AzureServiceBusHttpParser.TryParse(ref dependency)
|| GenericServiceHttpParser.TryParse(ref dependency)
|| AzureIotHubHttpParser.TryParse(ref dependency)
|| AzureSearchHttpParser.TryParse(ref dependency);

if (!parsed)
{
dependency.Type = RemoteDependencyConstants.HTTP;
}

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

return telemetry;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string GetHttpStatusCode(Dictionary<string, string> tags)
{
if (tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status))
{
return status;
}

return "0";
}

[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;
}

private static void ExtractPropertiesFromTags(IDictionary<string, string> destination, IEnumerable<KeyValuePair<string, string>> tags)
private static void AddPropertiesToTelemetry(IDictionary<string, string> destination, IEnumerable<KeyValuePair<string, string>> 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// 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
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
internal static void ExtractComponentProperties(Dictionary<string, string> 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;
}

string kind = null;

switch (activityKind)
{
case ActivityKind.Internal:
kind = "InProc";
break;
case ActivityKind.Producer:
kind = "Queue Message";
break;
default:
break;
}

switch (component)
{
case "eventhubs":
case "Microsoft.EventHub":
type = kind == null ? RemoteDependencyConstants.AzureEventHubs : string.Concat(kind, " | ", RemoteDependencyConstants.AzureEventHubs);
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
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 = kind == null ? component : string.Concat(kind, " | ", component);
break;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string GetMessagingUrl(Dictionary<string, string> tags)
{
if (tags == null)
{
return null;
}

tags.TryGetValue(SemanticConventions.AttributeMessagingUrl, out var url);
return url;
}
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace OpenTelemetry.Exporter.AzureMonitor
{
internal class UrlHelper
internal class HttpHelper
{
private const string SchemePostfix = "://";
private const string Colon = ":";
Expand Down Expand Up @@ -68,5 +68,33 @@ internal static string GetUrl(Dictionary<string, string> tags)

return string.IsNullOrWhiteSpace(url) ? null : url;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string GetHttpStatusCode(Dictionary<string, string> tags)
{
if (tags != null && tags.TryGetValue(SemanticConventions.AttributeHttpStatusCode, out var status))
{
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
return status;
}

return "0";
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetSuccessFromHttpStatusCode(string statusCode)
{
return statusCode == "200" || statusCode == "Ok";
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static string GetHost(Dictionary<string, string> tags)
{
if (tags != null && tags.TryGetValue(SemanticConventions.AttributeHttpHost, out var host))
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will get this as a part of next PR. This changes makes this PR complex.

{
return host;
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using OpenTelemetry.Exporter.AzureMonitor.Models;

namespace OpenTelemetry.Exporter.AzureMonitor.HttpParsers
{
/// <summary>
/// HTTP Dependency parser that attempts to parse dependency as Azure Blob call.
/// </summary>
internal static class AzureBlobHttpParser
{
private static readonly string[] AzureBlobHostSuffixes =
{
".blob.core.windows.net",
".blob.core.chinacloudapi.cn",
".blob.core.cloudapi.de",
".blob.core.usgovcloudapi.net",
};

private static readonly string[] AzureBlobSupportedVerbs = { "GET", "PUT", "OPTIONS", "HEAD", "DELETE" };

/// <summary>
/// Tries parsing given dependency telemetry item.
/// </summary>
/// <param name="httpDependency">Dependency item to parse. It is expected to be of HTTP type.</param>
/// <returns><code>true</code> if successfully parsed dependency.</returns>
internal static bool TryParse(ref RemoteDependencyData httpDependency)
{
string name = httpDependency.Name;
string host = httpDependency.Target;
string url = httpDependency.Data;

if (name == null || host == null || url == null)
{
return false;
}

if (!HttpParsingHelper.EndsWithAny(host, AzureBlobHostSuffixes))
{
return false;
}

////
//// Blob Service REST API: https://msdn.microsoft.com/en-us/library/azure/dd135733.aspx
////

string account = host.Substring(0, host.IndexOf('.'));

string verb;
string nameWithoutVerb;

// try to parse out the verb
HttpParsingHelper.ExtractVerb(name, out verb, out nameWithoutVerb, AzureBlobSupportedVerbs);

List<string> pathTokens = HttpParsingHelper.TokenizeRequestPath(nameWithoutVerb);

string container = null;
string blob = null;

if (pathTokens.Count == 1)
{
container = pathTokens[0];
}
else if (pathTokens.Count > 1)
{
Dictionary<string, string> queryParameters = HttpParsingHelper.ExtractQuryParameters(url);
string resType;
if (queryParameters == null || !queryParameters.TryGetValue("restype", out resType)
|| !string.Equals(resType, "container", StringComparison.OrdinalIgnoreCase))
{
// if restype != container then the last path entry is blob name
blob = pathTokens[pathTokens.Count - 1];
httpDependency.Properties["Blob"] = blob;

pathTokens.RemoveAt(pathTokens.Count - 1);
}

container = string.Join("/", pathTokens);
}

if (container != null)
{
httpDependency.Properties["Container"] = container;
}

httpDependency.Type = RemoteDependencyConstants.AzureBlob;

return true;
}
}
}
Loading