Skip to content
Open
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
Expand Up @@ -22,6 +22,7 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc
activityTagsProcessor.activityType &= ~OperationType.V2;
}

// Process based on operation type
switch (activityTagsProcessor.activityType)
{
case OperationType.Http:
Expand All @@ -38,6 +39,42 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc
break;
}

// Check for Microsoft override attributes only if present (avoids overhead for standalone OTel usage)
if (activityTagsProcessor.HasOverrideAttributes)
{
var overrideData = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyData)?.ToString();
var overrideName = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyName)?.ToString();
var overrideTarget = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyTarget)?.ToString();
var overrideType = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyType)?.ToString();
var overrideResultCode = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftDependencyResultCode)?.ToString();

// Apply overrides if present (these take precedence)
if (!string.IsNullOrEmpty(overrideData))
{
Data = overrideData.Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength);
}

if (!string.IsNullOrEmpty(overrideName))
{
dependencyName = overrideName;
}

if (!string.IsNullOrEmpty(overrideTarget))
{
Target = overrideTarget.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength);
}

if (!string.IsNullOrEmpty(overrideType))
{
Type = overrideType.Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength);
}

if (!string.IsNullOrEmpty(overrideResultCode))
{
ResultCode = overrideResultCode.Truncate(SchemaConstants.RemoteDependencyData_ResultCode_MaxLength);
}
}

dependencyName ??= activity.DisplayName;
Name = dependencyName?.Truncate(SchemaConstants.RemoteDependencyData_Name_MaxLength);
Id = activity.Context.SpanId.ToHexString();
Expand All @@ -46,13 +83,20 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc
: SchemaConstants.Duration_MaxValue;
Success = activity.Status != ActivityStatusCode.Error;

// Set Type from Azure namespace if present (unless already set by override attribute)
if (activityTagsProcessor.AzureNamespace != null)
{
Type = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace);
if (string.IsNullOrEmpty(Type))
{
Type = TraceHelper.GetAzureSDKDependencyType(activity.Kind, activityTagsProcessor.AzureNamespace);
}
}
else if (activity.Kind == ActivityKind.Internal)
{
Type = "InProc";
if (string.IsNullOrEmpty(Type))
{
Type = "InProc";
}
}

TraceHelper.AddActivityLinksToProperties(activity, ref activityTagsProcessor.UnMappedTags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ internal partial class RequestData
public RequestData(int version, Activity activity, ref ActivityTagsProcessor activityTagsProcessor) : base(version)
{
string? responseCode = null;
string? requestName = null;
Properties = new ChangeTrackingDictionary<string, string>();
Measurements = new ChangeTrackingDictionary<string, double>();

// Process based on operation type
switch (activityTagsProcessor.activityType)
{
case OperationType.Http | OperationType.V2:
Expand All @@ -30,6 +32,37 @@ public RequestData(int version, Activity activity, ref ActivityTagsProcessor act
break;
}

// Check for Microsoft override attributes only if present (avoids overhead for standalone OTel usage)
if (activityTagsProcessor.HasOverrideAttributes)
{
var overrideUrl = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestUrl)?.ToString();
var overrideName = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestName)?.ToString();
var overrideSource = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestSource)?.ToString();
var overrideResultCode = AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeMicrosoftRequestResultCode)?.ToString();

// Apply overrides if present (these take precedence)
if (!string.IsNullOrEmpty(overrideUrl))
{
Url = overrideUrl.Truncate(SchemaConstants.RequestData_Url_MaxLength);
}

if (!string.IsNullOrEmpty(overrideName))
{
requestName = overrideName;
}

if (!string.IsNullOrEmpty(overrideSource))
{
Source = overrideSource.Truncate(SchemaConstants.RequestData_Source_MaxLength);
}

if (!string.IsNullOrEmpty(overrideResultCode))
{
responseCode = overrideResultCode;
}
}

Name = requestName?.Truncate(SchemaConstants.RequestData_Name_MaxLength);
Id = activity.Context.SpanId.ToHexString();
Duration = activity.Duration < SchemaConstants.RequestData_Duration_LessThanDays
? activity.Duration.ToString("c", CultureInfo.InvariantCulture)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,19 @@ internal struct ActivityTagsProcessor
// Others
SemanticConventions.AttributeEnduserId,
SemanticConventions.AttributeEnduserPseudoId,
"microsoft.client.ip"
"microsoft.client.ip",

// Microsoft Application Insights Override Attributes
SemanticConventions.AttributeMicrosoftDependencyData,
SemanticConventions.AttributeMicrosoftDependencyName,
SemanticConventions.AttributeMicrosoftOperationName,
SemanticConventions.AttributeMicrosoftDependencyResultCode,
SemanticConventions.AttributeMicrosoftDependencyTarget,
SemanticConventions.AttributeMicrosoftDependencyType,
SemanticConventions.AttributeMicrosoftRequestName,
SemanticConventions.AttributeMicrosoftRequestUrl,
SemanticConventions.AttributeMicrosoftRequestSource,
SemanticConventions.AttributeMicrosoftRequestResultCode
};

internal static readonly HashSet<string> s_semanticsSet = new(s_semantics);
Expand All @@ -75,6 +87,8 @@ internal struct ActivityTagsProcessor

public string? EndUserPseudoId { get; private set; } = null;

public bool HasOverrideAttributes { get; private set; } = false;

public ActivityTagsProcessor()
{
MappedTags = AzMonList.Initialize();
Expand Down Expand Up @@ -118,6 +132,18 @@ public void CategorizeTags(Activity activity)
case SemanticConventions.AttributeEnduserPseudoId:
EndUserPseudoId = tag.Value.ToString();
continue;
case SemanticConventions.AttributeMicrosoftDependencyData:
case SemanticConventions.AttributeMicrosoftDependencyName:
case SemanticConventions.AttributeMicrosoftDependencyTarget:
case SemanticConventions.AttributeMicrosoftDependencyType:
case SemanticConventions.AttributeMicrosoftDependencyResultCode:
case SemanticConventions.AttributeMicrosoftOperationName:
case SemanticConventions.AttributeMicrosoftRequestName:
case SemanticConventions.AttributeMicrosoftRequestUrl:
case SemanticConventions.AttributeMicrosoftRequestSource:
case SemanticConventions.AttributeMicrosoftRequestResultCode:
HasOverrideAttributes = true;
break;
}

AzMonList.Add(ref MappedTags, tag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static void Clear(ref AzMonList list)

for (int i = 0; i < length; i++)
{
if (ReferenceEquals(list[i].Key, tagName))
if (string.Equals(list[i].Key, tagName, StringComparison.Ordinal))
Copy link

Choose a reason for hiding this comment

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

Not a big deal, just caught my eye. How did this even work in the previous version? Some basic tests should have caught it, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Our tests only highlighted this issue. All these days we had semantics that are in OpenTelemetry, which we could use as reference. But now we have to move to support both internal and OpenTelemetry semantics. Hence the change.

The ReferenceEquals worked before because we were consistently using the same string constant references from OpenTelemetry semantic conventions (string interning). But with dual semantics support, we can't rely on reference equality anymore - we need proper string comparison to handle cases where semantically equivalent tag names come from different sources.

Copy link

Choose a reason for hiding this comment

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

How did this even work before? Looks like it would have returned false always, no?

{
return list[i].Value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private static string GetSdkVersion()
/// <summary>
/// Specifies the type of SDK for telemetry identification and version labeling.
/// </summary>
public enum SdkVersionType
internal enum SdkVersionType
{
/// <summary>Default Azure Monitor OpenTelemetry Exporter.</summary>
Exporter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,69 @@ internal static class SemanticConventions
public const string AttributeDbQuerySummary = "db.query.summary";
public const string AttributeDbQueryText = "db.query.text";
public const string AttributeDbStoredProcedureName = "db.stored_procedure.name";

// Microsoft Application Insights Override Attributes
// These attributes allow explicit mapping from Application Insights fields to OpenTelemetry attributes
// When present, these override computed values from standard semantic conventions
Copy link

Choose a reason for hiding this comment

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

NIT: should it say when present, these ..... from OTel event ?


/// <summary>
/// Override attribute for dependency data field.
/// When present, takes precedence over computed data from semantic conventions.
Copy link

Choose a reason for hiding this comment

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

NIT: ...data from OTel event might be appropriate, instead of semantic convention

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Event has a specific meaning in OTel (it refers to the Events data model), so I used "semantic conventions" to avoid confusion. That said, I don't have a strong preference - if you think 'data from OTel event' reads better will change it.

/// </summary>
public const string AttributeMicrosoftDependencyData = "microsoft.dependency.data";

/// <summary>
/// Override attribute for dependency name field.
/// When present, takes precedence over computed name from semantic conventions.
/// </summary>
public const string AttributeMicrosoftDependencyName = "microsoft.dependency.name";

/// <summary>
/// Override attribute for operation name field.
/// When present, takes precedence over computed operation name.
/// </summary>
public const string AttributeMicrosoftOperationName = "microsoft.operation_name";

/// <summary>
/// Override attribute for dependency result code field.
/// When present, takes precedence over computed result code from semantic conventions.
/// </summary>
public const string AttributeMicrosoftDependencyResultCode = "microsoft.dependency.resultCode";

/// <summary>
/// Override attribute for dependency target field.
/// When present, takes precedence over computed target from semantic conventions.
/// </summary>
public const string AttributeMicrosoftDependencyTarget = "microsoft.dependency.target";

/// <summary>
/// Override attribute for dependency type field.
/// When present, takes precedence over computed type from semantic conventions.
/// </summary>
public const string AttributeMicrosoftDependencyType = "microsoft.dependency.type";

/// <summary>
/// Override attribute for request name field.
/// When present, takes precedence over Activity.DisplayName.
/// </summary>
public const string AttributeMicrosoftRequestName = "microsoft.request.name";

/// <summary>
/// Override attribute for request URL field.
/// When present, takes precedence over computed URL from semantic conventions.
/// </summary>
public const string AttributeMicrosoftRequestUrl = "microsoft.request.url";

/// <summary>
/// Override attribute for request source field.
/// When present, takes precedence over computed source from semantic conventions.
/// </summary>
public const string AttributeMicrosoftRequestSource = "microsoft.request.source";

/// <summary>
/// Override attribute for request result code field.
/// When present, takes precedence over computed result code from semantic conventions.
/// </summary>
public const string AttributeMicrosoftRequestResultCode = "microsoft.request.resultCode";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ internal static (List<TelemetryItem> TelemetryItems, TelemetrySchemaTypeCounter
{
case TelemetryType.Request:
var requestData = new RequestData(Version, activity, ref activityTagsProcessor);
requestData.Name = telemetryItem.Tags.TryGetValue(ContextTagKeys.AiOperationName.ToString(), out var operationName) ? operationName.Truncate(SchemaConstants.RequestData_Name_MaxLength) : activity.DisplayName.Truncate(SchemaConstants.RequestData_Name_MaxLength);
// Only set Name if not already set by override attribute
if (string.IsNullOrEmpty(requestData.Name))
{
requestData.Name = telemetryItem.Tags.TryGetValue(ContextTagKeys.AiOperationName.ToString(), out var operationName) ? operationName.Truncate(SchemaConstants.RequestData_Name_MaxLength) : activity.DisplayName.Truncate(SchemaConstants.RequestData_Name_MaxLength);
Copy link
Member

Choose a reason for hiding this comment

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

nit: break down into multiple lines

}
telemetryItem.Data = new MonitorBase
{
BaseType = "RequestData",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class CustomerSdkStatsDimensionsTests
[InlineData(SdkVersionType.ShimAspNetCore, "dotnet-core")]
[InlineData(SdkVersionType.ShimWorkerService, "dotnet-worker")]
[InlineData(SdkVersionType.ShimWeb, "dotnet-web")]
public void GetBaseTags_SetsCorrectLanguageDimensionForEachSdkVersionType(SdkVersionType versionType, string expectedLanguage)
internal void GetBaseTags_SetsCorrectLanguageDimensionForEachSdkVersionType(SdkVersionType versionType, string expectedLanguage)
{
// Arrange
var originalVersionType = SdkVersionUtils.s_sdkVersionType;
Expand Down
Loading
Loading