Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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,10 @@
// 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.Runtime.CompilerServices;
using System.Text;
using Microsoft.OpenTelemetry.Exporter.AzureMonitor.Models;

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using Microsoft.OpenTelemetry.Exporter.AzureMonitor.Models;

namespace Microsoft.OpenTelemetry.Exporter.AzureMonitor
{
Expand All @@ -29,284 +22,33 @@ internal static class AzureMonitorConverter
[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",
};

private static readonly IReadOnlyDictionary<string, PartBType> Part_B_Mapping = new Dictionary<string, PartBType>()
{
[SemanticConventions.AttributeDbStatement] = PartBType.Db,
[SemanticConventions.AttributeDbSystem] = PartBType.Db,

[SemanticConventions.AttributeHttpMethod] = PartBType.Http,
[SemanticConventions.AttributeHttpUrl] = PartBType.Http,
[SemanticConventions.AttributeHttpStatusCode] = PartBType.Http,
[SemanticConventions.AttributeHttpScheme] = PartBType.Http,
[SemanticConventions.AttributeHttpHost] = PartBType.Http,
[SemanticConventions.AttributeHttpHostPort] = PartBType.Http,
[SemanticConventions.AttributeHttpTarget] = PartBType.Http,

[SemanticConventions.AttributeNetPeerName] = PartBType.Common,
[SemanticConventions.AttributeNetPeerIp] = PartBType.Common,
[SemanticConventions.AttributeNetPeerPort] = PartBType.Common,
[SemanticConventions.AttributeNetTransport] = PartBType.Common,
[SemanticConventions.AttributeNetHostIp] = PartBType.Common,
[SemanticConventions.AttributeNetHostPort] = PartBType.Common,
[SemanticConventions.AttributeNetHostName] = PartBType.Common,
[SemanticConventions.AttributeComponent] = PartBType.Common,

[SemanticConventions.AttributeRpcService] = PartBType.Rpc,
[SemanticConventions.AttributeRpcSystem] = PartBType.Rpc,
[SemanticConventions.AttributeRpcStatus] = 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.AttributeAzureNameSpace] = PartBType.Azure,
[SemanticConventions.AttributeMessageBusDestination] = PartBType.Azure,

[SemanticConventions.AttributeEndpointAddress] = PartBType.Messaging,
[SemanticConventions.AttributeMessagingSystem] = PartBType.Messaging,
[SemanticConventions.AttributeMessagingDestination] = PartBType.Messaging,
[SemanticConventions.AttributeMessagingDestinationKind] = PartBType.Messaging,
[SemanticConventions.AttributeMessagingTempDestination] = PartBType.Messaging,
[SemanticConventions.AttributeMessagingUrl] = PartBType.Messaging
};

internal static string RoleName { get; set; } = null;

internal static string RoleInstance { get; set; } = null;

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

internal static TelemetryItem GeneratePartAEnvelope(Activity activity)
{
TelemetryItem telemetryItem = new TelemetryItem(PartA_Name_Mapping[activity.GetTelemetryType()], activity.StartTimeUtc.ToString(CultureInfo.InvariantCulture));
InitRoleInfo(activity);
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 InitRoleInfo(Activity activity)
{
if (RoleName != null || RoleInstance != null)
{
return;
}

var resource = activity.GetResource();

if (resource == null)
{
return;
}

string serviceName = null;
string serviceNamespace = 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;
}
}
MonitorBase telemetryData = new MonitorBase();
telemetryItem = TelemetryPartA.GetTelemetryItem(activity, instrumentationKey);

private static MonitorBase GenerateTelemetryData(Activity activity)
{
var telemetryType = activity.GetTelemetryType();
var monitorTags = new TagEnumerationState
{
PartBTags = AzMonList.Initialize(),
PartCTags = AzMonList.Initialize()
};

activity.EnumerateTags(ref monitorTags);

MonitorBase telemetry = new MonitorBase
{
BaseType = Telemetry_Base_Type_Mapping[telemetryType]
};

if (telemetryType == TelemetryType.Request)
{
monitorTags.PartBTags.GenerateUrlAndAuthority(out var url, out var urlAuthority);
var statusCode = AzMonList.GetTagValue(ref monitorTags.PartBTags, SemanticConventions.AttributeHttpStatusCode)?.ToString() ?? "0";
var success = activity.GetStatus() != Status.Error;
var request = new RequestData(2, activity.Context.SpanId.ToHexString(), activity.Duration.ToString("c", CultureInfo.InvariantCulture), success, statusCode)
{
Name = activity.DisplayName,
Url = url,
Source = urlAuthority
};

// TODO: Handle activity.TagObjects, extract well-known tags
AddPropertiesToTelemetry(request.Properties, monitorTags.PartCTags);
telemetry.BaseData = request;
}
else if (telemetryType == TelemetryType.Dependency)
{
var dependency = new RemoteDependencyData(2, activity.DisplayName, activity.Duration.ToString("c", CultureInfo.InvariantCulture))
switch (activity.GetTelemetryType())
{
Id = activity.Context.SpanId.ToHexString(),
Success = activity.GetStatus() != Status.Error
};

switch (monitorTags.activityType)
{
case PartBType.Http:
monitorTags.PartBTags.GenerateUrlAndAuthority(out var url, out var urlAuthority);
dependency.Data = url;
dependency.Target = urlAuthority;
dependency.Type = "Http";
dependency.ResultCode = AzMonList.GetTagValue(ref monitorTags.PartBTags, SemanticConventions.AttributeHttpStatusCode)?.ToString() ?? "0";
break;
case PartBType.Db:
var depDataAndType = AzMonList.GetTagValues(ref monitorTags.PartBTags, SemanticConventions.AttributeDbStatement, SemanticConventions.AttributeDbSystem);
dependency.Data = depDataAndType[0]?.ToString();
dependency.Type = depDataAndType[1]?.ToString();
case TelemetryType.Request:
telemetryData.BaseType = Telemetry_Base_Type_Mapping[TelemetryType.Request];
telemetryData.BaseData = TelemetryPartB.GetRequestData(activity);
break;
case PartBType.Rpc:
var depInfo = AzMonList.GetTagValues(ref monitorTags.PartBTags, SemanticConventions.AttributeRpcService, SemanticConventions.AttributeRpcSystem, SemanticConventions.AttributeRpcStatus);
dependency.Data = depInfo[0]?.ToString();
dependency.Type = depInfo[1]?.ToString();
dependency.ResultCode = depInfo[2]?.ToString();
break;
case PartBType.Messaging:
depDataAndType = AzMonList.GetTagValues(ref monitorTags.PartBTags, SemanticConventions.AttributeMessagingUrl, SemanticConventions.AttributeMessagingSystem);
dependency.Data = depDataAndType[0]?.ToString();
dependency.Type = depDataAndType[1]?.ToString();
case TelemetryType.Dependency:
telemetryData.BaseType = Telemetry_Base_Type_Mapping[TelemetryType.Dependency];
telemetryData.BaseData = TelemetryPartB.GetRemoteDependencyData(activity);
break;
}

AddPropertiesToTelemetry(dependency.Properties, monitorTags.PartCTags);
telemetry.BaseData = dependency;
}

return telemetry;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AddPropertiesToTelemetry(IDictionary<string, string> destination, AzMonList PartCTags)
{
// TODO: Iterate only interested fields. Ref: https://github.com/Azure/azure-sdk-for-net/pull/14254#discussion_r470907560
for (int i = 0; i < PartCTags.Length; i++)
{
destination.Add(PartCTags[i].Key, PartCTags[i].Value?.ToString());
telemetryItem.Data = telemetryData;
telemetryItems.Add(telemetryItem);
}
}

internal struct TagEnumerationState : IActivityEnumerator<KeyValuePair<string, object>>
{
public AzMonList PartBTags;
public AzMonList PartCTags;

private PartBType tempActivityType;
public PartBType activityType;

public bool ForEach(KeyValuePair<string, object> activityTag)
{
if (activityTag.Value == null)
{
return true;
}

if (activityTag.Value is Array array)
{
StringBuilder sw = new StringBuilder();
foreach (var item in array)
{
// TODO: Consider changing it to JSon array.
if (item != null)
{
sw.Append(item);
sw.Append(',');
}
}

if (sw.Length > 0)
{
sw.Length--;
}

AzMonList.Add(ref PartCTags, new KeyValuePair<string, object>(activityTag.Key, sw.ToString()));
return true;
}

if (!Part_B_Mapping.TryGetValue(activityTag.Key, out tempActivityType))
{
AzMonList.Add(ref PartCTags, activityTag);
return true;
}

if (activityType == PartBType.Unknown || activityType == PartBType.Common)
{
activityType = tempActivityType;
}

if (tempActivityType == activityType || tempActivityType == PartBType.Common)
{
AzMonList.Add(ref PartBTags, activityTag);
}
else
{
AzMonList.Add(ref PartCTags, activityTag);
}

return true;
}
return telemetryItems;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
using System.Globalization;
using System.Linq;
using System.Reflection;

using OpenTelemetrySdk = OpenTelemetry.Sdk;
using OpenTelemetry;

namespace Microsoft.OpenTelemetry.Exporter.AzureMonitor
{
Expand All @@ -19,7 +18,7 @@ private static string GetSdkVersion()
try
{
Version dotnetSdkVersion = GetVersion(typeof(object));
Version otelSdkVersion = GetVersion(typeof(OpenTelemetrySdk));
Version otelSdkVersion = GetVersion(typeof(Sdk));
Version extensionVersion = GetVersion(typeof(AzureMonitorTraceExporter));

return string.Format(CultureInfo.InvariantCulture, $"dotnet{dotnetSdkVersion.ToString(2)}:otel{otelSdkVersion.ToString(3)}:ext{extensionVersion.ToString(3)}");
Expand Down
Loading