Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@
* Fixed a bug where LiveMetrics displays "UNKNOWN_INSTANCE" and "UNKNOWN_NAME" for "server name" and "role name" respectively.
([#45433](https://github.com/Azure/azure-sdk-for-net/pull/45433))

* 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

* Updated field mappings for telemetry sent to LiveMetrics.
([#45103](https://github.com/Azure/azure-sdk-for-net/pull/45103))

* Updated log collection to default to Warning level and above for Azure SDKs
via `Microsoft.Extensions.Logging`. For more information, refer to [Logging
with the Azure SDK for
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 @@ -119,41 +124,41 @@ internal static RemoteDependency ConvertToDependencyDocument(Activity activity)
{
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.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;
break;
case OperationType.Rpc:
// TODO RPC
break;
case OperationType.Messaging:
// TODO MESSAGING
remoteDependencyDocument.Name = activity.DisplayName;

var (messagingUrl, _) = liveMetricsTagsProcessor.Tags.GetMessagingUrlAndSourceOrTarget(activity.Kind);
remoteDependencyDocument.CommandName = messagingUrl;

break;
case OperationType.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();
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("ActivitySource", activity.Source.Name));
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void VerifyHttpClientAttributes()
ActivitySource.AddActivityListener(listener);

// ACT
using var dependencyActivity = activitySource.StartActivity(name: "HelloWorld", kind: ActivityKind.Client);
using var dependencyActivity = activitySource.StartActivity(name: "TestActivityName", kind: ActivityKind.Client);
Assert.NotNull(dependencyActivity);
dependencyActivity.SetTag("http.request.method", "GET");
dependencyActivity.SetTag("url.full", "http://bing.com");
Expand All @@ -67,7 +67,7 @@ public void VerifyHttpClientAttributes()
// ASSERT
Assert.Equal("http://bing.com", dependencyDocument.CommandName);
Assert.Equal(DocumentType.RemoteDependency, dependencyDocument.DocumentType);
Assert.Equal("HelloWorld", dependencyDocument.Name);
Assert.Equal("TestActivityName", dependencyDocument.Name);
Assert.Equal("200", dependencyDocument.ResultCode);

VerifyCustomProperties(dependencyDocument);
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("TestActivitySource");

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

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

tracerProvider.ForceFlush();
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("TestActivityName", dependencyDocument.Name);
Assert.Equal("TestActivitySource", dependencyDocument.Properties.Single(x => x.Key == "ActivitySource").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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void VerifySqlClientAttributes()
ActivitySource.AddActivityListener(listener);

// ACT
using var dependencyActivity = activitySource.StartActivity(name: "HelloWorld", kind: ActivityKind.Client);
using var dependencyActivity = activitySource.StartActivity(name: "TestActivityName", kind: ActivityKind.Client);
Assert.NotNull(dependencyActivity);
dependencyActivity.SetTag("db.system", "mssql");
dependencyActivity.SetTag("db.name", "MyDatabase");
Expand All @@ -71,7 +71,7 @@ public void VerifySqlClientAttributes()
Assert.Equal("select * from sys.databases", dependencyDocument.CommandName);
Assert.Equal(DocumentType.RemoteDependency, dependencyDocument.DocumentType);
Assert.Equal(dependencyActivity.Duration.ToString("c"), dependencyDocument.Duration);
Assert.Equal("(localdb)\\MSSQLLocalDB | MyDatabase", dependencyDocument.Name);
Assert.Equal("TestActivityName", dependencyDocument.Name);

VerifyCustomProperties(dependencyDocument);

Expand Down Expand Up @@ -141,7 +141,7 @@ public void VerifySqlClientDependency(
Assert.Equal(commandText, dependencyDocument.CommandName);
Assert.Equal(DocumentType.RemoteDependency, dependencyDocument.DocumentType);
Assert.Equal(dependencyActivity.Duration.ToString("c"), dependencyDocument.Duration);
Assert.Equal("(localdb)\\MSSQLLocalDB | MyDatabase", dependencyDocument.Name);
Assert.Equal("MyDatabase", dependencyDocument.Name);

// The following "EXTENSION" properties are used to calculate metrics. These are not serialized.
Assert.Equal(dependencyActivity.Duration.TotalMilliseconds, dependencyDocument.Extension_Duration);
Expand Down Expand Up @@ -214,7 +214,7 @@ public void VerifySqlClientDependencyWithException(
Assert.Equal(commandText, dependencyDocument.CommandName);
Assert.Equal(DocumentType.RemoteDependency, dependencyDocument.DocumentType);
Assert.Equal(dependencyActivity.Duration.ToString("c"), dependencyDocument.Duration);
Assert.Equal("(localdb)\\MSSQLLocalDB | MyDatabase", dependencyDocument.Name);
Assert.Equal("MyDatabase", dependencyDocument.Name);

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