Skip to content

Commit

Permalink
Azure Monitor Exporter - Split Transmitter into two classes (#15748)
Browse files Browse the repository at this point in the history
* split transmitter into two classes

* cleanup
  • Loading branch information
TimothyMothra authored Oct 6, 2020
1 parent f48d897 commit cbb7115
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 203 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// 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 OpenTelemetry.Exporter.AzureMonitor.Models;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Exporter.AzureMonitor
{
/// <summary>
/// This class is responsible for converting an OpenTelemetry <see cref="Batch{T}"/> of <see cref="Activity"/>
/// into a collection of <see cref="TelemetryItem"/> for Azure Monitor.
/// </summary>
internal static class AzureMonitorConverter
{
private static readonly IReadOnlyDictionary<TelemetryType, string> Telemetry_Base_Type_Mapping = new Dictionary<TelemetryType, string>
{
[TelemetryType.Request] = "RequestData",
[TelemetryType.Dependency] = "RemoteDependencyData",
[TelemetryType.Message] = "MessageData",
[TelemetryType.Event] = "EventData",
};

private static readonly IReadOnlyDictionary<TelemetryType, string> PartA_Name_Mapping = new Dictionary<TelemetryType, string>
{
[TelemetryType.Request] = "Request",
[TelemetryType.Dependency] = "RemoteDependency",
[TelemetryType.Message] = "Message",
[TelemetryType.Event] = "Event",
};

public static List<TelemetryItem> Convert(Batch<Activity> batchActivity, string instrumentationKey)
{
List<TelemetryItem> telemetryItems = new List<TelemetryItem>();
TelemetryItem telemetryItem;

foreach (var activity in batchActivity)
{
telemetryItem = GeneratePartAEnvelope(activity);
telemetryItem.InstrumentationKey = instrumentationKey;
telemetryItem.Data = GenerateTelemetryData(activity);
telemetryItems.Add(telemetryItem);
}

return telemetryItems;
}

private static TelemetryItem GeneratePartAEnvelope(Activity activity)
{
TelemetryItem telemetryItem = new TelemetryItem(PartA_Name_Mapping[activity.GetTelemetryType()], activity.StartTimeUtc.ToString(CultureInfo.InvariantCulture));
ExtractRoleInfo(activity.GetResource(), out var roleName, out var roleInstance);
telemetryItem.Tags[ContextTagKeys.AiCloudRole.ToString()] = roleName;
telemetryItem.Tags[ContextTagKeys.AiCloudRoleInstance.ToString()] = roleInstance;
telemetryItem.Tags[ContextTagKeys.AiOperationId.ToString()] = activity.TraceId.ToHexString();
if (activity.Parent != null)
{
telemetryItem.Tags[ContextTagKeys.AiOperationParentId.ToString()] = activity.Parent.SpanId.ToHexString();
}
// TODO: Handle exception
telemetryItem.Tags[ContextTagKeys.AiInternalSdkVersion.ToString()] = SdkVersionUtils.SdkVersion;

return telemetryItem;
}

internal static void ExtractRoleInfo(Resource resource, out string roleName, out string roleInstance)
{
if (resource == null)
{
roleName = null;
roleInstance = null;
return;
}

string serviceName = null;
string serviceNamespace = null;
roleInstance = null;

foreach (var attribute in resource.Attributes)
{
if (attribute.Key == Resource.ServiceNameKey && attribute.Value is string)
{
serviceName = attribute.Value.ToString();
}
else if (attribute.Key == Resource.ServiceNamespaceKey && attribute.Value is string)
{
serviceNamespace = attribute.Value.ToString();
}
else if (attribute.Key == Resource.ServiceInstanceIdKey && attribute.Value is string)
{
roleInstance = attribute.Value.ToString();
}
}

if (serviceName != null && serviceNamespace != null)
{
roleName = string.Concat(serviceNamespace, ".", serviceName);
}
else
{
roleName = serviceName;
}
}

private static MonitorBase GenerateTelemetryData(Activity activity)
{
var telemetryType = activity.GetTelemetryType();
var activityType = activity.TagObjects.ToAzureMonitorTags(out var partBTags, out var PartCTags);
MonitorBase telemetry = new MonitorBase
{
BaseType = Telemetry_Base_Type_Mapping[telemetryType]
};

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)
{
Name = activity.DisplayName,
Url = url,
// TODO: Handle request.source.
};

// 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.ToString("c", CultureInfo.InvariantCulture))
{
Id = activity.Context.SpanId.ToHexString()
};

// TODO: Handle activity.TagObjects
// ExtractPropertiesFromTags(dependency.Properties, activity.Tags);

if (activityType == PartBType.Http)
{
dependency.Data = UrlHelper.GetUrl(partBTags);
dependency.Type = "HTTP"; // TODO: Parse for storage / SB.
var statusCode = GetHttpStatusCode(partBTags);
dependency.ResultCode = statusCode;
dependency.Success = GetSuccessFromHttpStatusCode(statusCode);
}

// TODO: Handle dependency.target.
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)
{
// 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)))
{
destination.Add(tag);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,44 @@
using System;
using System.Diagnostics;
using System.Threading;

using Azure.Core.Pipeline;

using OpenTelemetry.Trace;

namespace OpenTelemetry.Exporter.AzureMonitor
{
public class AzureMonitorTraceExporter : ActivityExporter
{
private readonly AzureMonitorTransmitter AzureMonitorTransmitter;
private readonly AzureMonitorExporterOptions options;
private readonly string instrumentationKey;

public AzureMonitorTraceExporter(AzureMonitorExporterOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
this.options = options ?? throw new ArgumentNullException(nameof(options));
ConnectionString.ConnectionStringParser.GetValues(this.options.ConnectionString, out this.instrumentationKey, out _);

this.AzureMonitorTransmitter = new AzureMonitorTransmitter(options);
}

/// <inheritdoc/>
public override ExportResult Export(in Batch<Activity> batch)
{
// Prevent Azure Monitor's HTTP operations from being instrumented.
using var scope = SuppressInstrumentationScope.Begin();

try
{
var telemetryItems = AzureMonitorConverter.Convert(batch, this.instrumentationKey);

// TODO: Handle return value, it can be converted as metrics.
// TODO: Validate CancellationToken and async pattern here.
this.AzureMonitorTransmitter.AddBatchActivityAsync(batch, false, CancellationToken.None).EnsureCompleted();
this.AzureMonitorTransmitter.TrackAsync(telemetryItems, false, CancellationToken.None).EnsureCompleted();
return ExportResult.Success;
}
catch (Exception ex)
{
AzureMonitorTraceExporterEventSource.Log.FailedExport(ex);
return ExportResult.Failure;
}

}
}
}
Loading

0 comments on commit cbb7115

Please sign in to comment.