Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

### Bugs Fixed

* Fixed a bug in LiveMetrics that counted all manually created Dependencies as failures.
([#45103](https://github.com/Azure/azure-sdk-for-net/pull/45103))

### Other Changes

## 1.3.0-beta.1 (2024-07-12)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,13 @@ internal static RemoteDependency ConvertToDependencyDocument(Activity activity)
RemoteDependency remoteDependencyDocument = new()
{
DocumentType = DocumentType.RemoteDependency,
Duration = activity.Duration < SchemaConstants.RemoteDependencyData_Duration_LessThanDays
? activity.Duration.ToString("c", CultureInfo.InvariantCulture)
: SchemaConstants.Duration_MaxValue,

// The following "EXTENSION" properties are used to calculate metrics. These are not serialized.
Extension_Duration = activity.Duration.TotalMilliseconds,
Extension_IsSuccess = activity.Status != ActivityStatusCode.Error,
};

var liveMetricsTagsProcessor = new LiveMetricsTagsProcessor();
Expand All @@ -118,42 +123,53 @@ internal static RemoteDependency ConvertToDependencyDocument(Activity activity)
switch (liveMetricsTagsProcessor.ActivityType)
{
case OperationType.Http:
remoteDependencyDocument.Name = activity.DisplayName;
remoteDependencyDocument.CommandName = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags, SemanticConventions.AttributeUrlFull)?.ToString();

var httpUrl = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags, SemanticConventions.AttributeUrlFull)?.ToString();
remoteDependencyDocument.Name = liveMetricsTagsProcessor.Tags.GetNewSchemaHttpDependencyName(httpUrl) ?? activity.DisplayName;

remoteDependencyDocument.CommandName = httpUrl;
var httpResponseStatusCode = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags, SemanticConventions.AttributeHttpResponseStatusCode)?.ToString();
remoteDependencyDocument.ResultCode = httpResponseStatusCode;
remoteDependencyDocument.Duration = activity.Duration < SchemaConstants.RequestData_Duration_LessThanDays
? activity.Duration.ToString("c", CultureInfo.InvariantCulture)
: SchemaConstants.Duration_MaxValue;
remoteDependencyDocument.ResultCode = httpResponseStatusCode ?? "0";

// The following "EXTENSION" properties are used to calculate metrics. These are not serialized.
remoteDependencyDocument.Extension_IsSuccess = IsHttpSuccess(activity, httpResponseStatusCode);
break;
case OperationType.Db:
// Note: The Exception details are recorded in Activity.Events only if the configuration has opt-ed into this (SqlClientInstrumentationOptions.RecordException).

var (_, dbTarget) = liveMetricsTagsProcessor.Tags.GetDbDependencyTargetAndName();
remoteDependencyDocument.Name = activity.DisplayName;

remoteDependencyDocument.Name = dbTarget;
remoteDependencyDocument.CommandName = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags, SemanticConventions.AttributeDbStatement)?.ToString();
remoteDependencyDocument.Duration = activity.Duration.ToString("c", CultureInfo.InvariantCulture);

// TODO: remoteDependencyDocumentIngress.ResultCode = "";
// AI SDK reads a Number property from Connection or Command objects.
// As of Feb 2024, OpenTelemetry doesn't record this. This may change in the future when the semantic convention stabalizes.

// The following "EXTENSION" properties are used to calculate metrics. These are not serialized.
remoteDependencyDocument.Extension_IsSuccess = activity.Status != ActivityStatusCode.Error;
var dbName = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags,, SemanticConventions.AttributeDbName)?.ToString();
if (dbName != null)
{
remoteDependencyDocument.Properties.Add(new KeyValuePairString(SemanticConventions.AttributeDbName, dbName));
}
break;
case OperationType.Rpc:
// TODO RPC
remoteDependencyDocument.Name = activity.DisplayName;
remoteDependencyDocument.CommandName = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags, SemanticConventions.AttributeRpcService)?.ToString();
remoteDependencyDocument.ResultCode = AzMonList.GetTagValue(ref liveMetricsTagsProcessor.Tags, SemanticConventions.AttributeRpcStatus)?.ToString();

break;
case OperationType.Messaging:
// TODO MESSAGING
var (messagingUrl, _) = liveMetricsTagsProcessor.Tags.GetMessagingUrlAndSourceOrTarget(activity.Kind);

remoteDependencyDocument.Name = activity.DisplayName;
remoteDependencyDocument.CommandName = messagingUrl;

break;
default:
// Unknown or Unexpected Dependency Type
remoteDependencyDocument.Name = liveMetricsTagsProcessor.ActivityType.ToString();
// Unknown or Manual or Unexpected Dependency Type
remoteDependencyDocument.Name = activity.DisplayName;

remoteDependencyDocument.Properties.Add(new KeyValuePairString("ActivityType", liveMetricsTagsProcessor.ActivityType.ToString()));
remoteDependencyDocument.Properties.Add(new KeyValuePairString("ActivitySource", activity.Source.Name));

// TODO: SHOULD WE COLLECT TAGS HERE?
break;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Azure.Monitor.OpenTelemetry.AspNetCore.LiveMetrics.DataCollection;
using Azure.Monitor.OpenTelemetry.AspNetCore.Models;
using Microsoft.AspNetCore.Builder;
using OpenTelemetry;
using OpenTelemetry.Trace;
using Xunit;
using Xunit.Abstractions;

namespace Azure.Monitor.OpenTelemetry.AspNetCore.Tests.LiveMetrics.DocumentTests
{
public class ManualDependencyTests : DocumentTestBase
{
public ManualDependencyTests(ITestOutputHelper output) : base(output)
{
}

[Theory]
[InlineData(ActivityStatusCode.Ok, true)]
[InlineData(ActivityStatusCode.Error, false)]
[InlineData(ActivityStatusCode.Unset, true)]
public void VerifyManualDependency(ActivityStatusCode activityStatusCode, bool expectedIsSuccess)
{
var exportedActivities = new List<Activity>();

var testActivitySource = new ActivitySource("testSource");

// SETUP
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("testSource")
.AddInMemoryExporter(exportedActivities)
.Build();

// ACT
using (var activity = testActivitySource.StartActivity("testActivity", ActivityKind.Internal))
{
activity?.SetStatus(activityStatusCode);
}

WaitForActivityExport(exportedActivities);

// Assert
var dependencyActivity = exportedActivities.Last();
PrintActivity(dependencyActivity);
var dependencyDocument = DocumentHelper.ConvertToDependencyDocument(dependencyActivity);

Assert.Null(dependencyDocument.CommandName);
Assert.Equal(DocumentType.RemoteDependency, dependencyDocument.DocumentType);
Assert.Equal("testActivity", dependencyDocument.Name);
Assert.Equal("testSource", dependencyDocument.Properties.Single(x => x.Key == "ActivitySource").Value);
Assert.Equal("Unknown", dependencyDocument.Properties.Single(x => x.Key == "ActivityType").Value);

//// The following "EXTENSION" properties are used to calculate metrics. These are not serialized.
Assert.Equal(dependencyActivity.Duration.TotalMilliseconds, dependencyDocument.Extension_Duration);
Assert.Equal(expectedIsSuccess, dependencyDocument.Extension_IsSuccess);
}
}
}