diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md
index daa94e377a68..92810b74e6bb 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md
@@ -17,6 +17,9 @@
`Sdk.CreateTracerProviderBuilder().AddAzureMonitorTraceExporter(...)` path.
([#52720](https://github.com/Azure/azure-sdk-for-net/pull/52720))
+* Added handling of stable database client span semantic conventions
+ ([#53050](https://github.com/Azure/azure-sdk-for-net/pull/53050))
+
### Breaking Changes
### Bugs Fixed
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs
index d09c636d9ed3..79ddd09b638b 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs
@@ -28,7 +28,7 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc
SetHttpDependencyPropertiesAndDependencyName(activity, ref activityTagsProcessor.MappedTags, isNewSchemaVersion, out dependencyName);
break;
case OperationType.Db:
- SetDbDependencyProperties(ref activityTagsProcessor.MappedTags);
+ SetDbDependencyProperties(ref activityTagsProcessor.MappedTags, isNewSchemaVersion);
break;
case OperationType.Messaging:
SetMessagingDependencyProperties(activity, ref activityTagsProcessor.MappedTags);
@@ -86,11 +86,23 @@ private void SetHttpDependencyPropertiesAndDependencyName(Activity activity, ref
ResultCode = resultCode?.Truncate(SchemaConstants.RemoteDependencyData_ResultCode_MaxLength) ?? "0";
}
- private void SetDbDependencyProperties(ref AzMonList dbTagObjects)
+ private void SetDbDependencyProperties(ref AzMonList dbTagObjects, bool isNewSchemaVersion)
{
- var dbAttributeTagObjects = AzMonList.GetTagValues(ref dbTagObjects, SemanticConventions.AttributeDbStatement, SemanticConventions.AttributeDbSystem);
+ string statementAttributeKey;
+ string statementSystemKey;
+ if (isNewSchemaVersion)
+ {
+ statementAttributeKey = SemanticConventions.AttributeDbQueryText;
+ statementSystemKey = SemanticConventions.AttributeDbSystemName;
+ }
+ else
+ {
+ statementAttributeKey = SemanticConventions.AttributeDbStatement;
+ statementSystemKey = SemanticConventions.AttributeDbSystem;
+ }
+ var dbAttributeTagObjects = AzMonList.GetTagValues(ref dbTagObjects, statementAttributeKey, statementSystemKey);
Data = dbAttributeTagObjects[0]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength);
- var (DbName, DbTarget) = dbTagObjects.GetDbDependencyTargetAndName();
+ var (DbName, DbTarget) = dbTagObjects.GetDbDependencyTargetAndName(isNewSchemaVersion);
Target = DbTarget?.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength);
Type = AzMonListExtensions.s_dbSystems.Contains(dbAttributeTagObjects[1]?.ToString()) ? "SQL" : dbAttributeTagObjects[1]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength);
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs
index 10ebc1e47364..36a70956518a 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs
@@ -11,8 +11,11 @@ internal struct ActivityTagsProcessor
{
private static readonly string[] s_semantics = {
SemanticConventions.AttributeDbStatement,
+ SemanticConventions.AttributeDbQueryText,
SemanticConventions.AttributeDbSystem,
+ SemanticConventions.AttributeDbSystemName,
SemanticConventions.AttributeDbName,
+ SemanticConventions.AttributeDbNamespace,
// required - HTTP
SemanticConventions.AttributeHttpMethod,
@@ -97,6 +100,9 @@ public void CategorizeTags(Activity activity)
case SemanticConventions.AttributeHttpRequestMethod:
activityType = OperationType.Http | OperationType.V2;
break;
+ case SemanticConventions.AttributeDbSystemName:
+ activityType = OperationType.Db | OperationType.V2;
+ break;
case SemanticConventions.AttributeDbSystem:
activityType = OperationType.Db;
break;
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs
index 132777870ce7..357f1d9d88cb 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs
@@ -329,9 +329,19 @@ internal static string GetDefaultDbPort(string? dbSystem)
///
/// Gets Database dependency target and name from activity tag objects.
///
- internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(this AzMonList tagObjects)
+ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(this AzMonList tagObjects, bool isNewSchemaVersion)
{
- var peerServiceAndDbSystem = AzMonList.GetTagValues(ref tagObjects, SemanticConventions.AttributePeerService, SemanticConventions.AttributeDbSystem);
+ string statementDbNameKey;
+ string statementDbSystemKey;
+ if (isNewSchemaVersion) {
+ statementDbNameKey = SemanticConventions.AttributeDbNamespace;
+ statementDbSystemKey = SemanticConventions.AttributeDbSystemName;
+ } else {
+ statementDbNameKey = SemanticConventions.AttributeDbName;
+ statementDbSystemKey = SemanticConventions.AttributeDbSystem;
+ }
+
+ var peerServiceAndDbSystem = AzMonList.GetTagValues(ref tagObjects, SemanticConventions.AttributePeerService, statementDbSystemKey);
string? target = peerServiceAndDbSystem[0]?.ToString();
var defaultPort = GetDefaultDbPort(peerServiceAndDbSystem[1]?.ToString());
@@ -340,7 +350,7 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(
target = tagObjects.GetTargetUsingServerAttributes(defaultPort) ?? tagObjects.GetTargetUsingNetPeerAttributes(defaultPort);
}
- var dbName = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeDbName)?.ToString();
+ var dbName = AzMonList.GetTagValue(ref tagObjects, statementDbNameKey)?.ToString();
bool isTargetEmpty = string.IsNullOrWhiteSpace(target);
bool isDbNameEmpty = string.IsNullOrWhiteSpace(dbName);
if (!isTargetEmpty && !isDbNameEmpty)
@@ -353,7 +363,7 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(
}
else if (isTargetEmpty && isDbNameEmpty)
{
- target = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeDbSystem)?.ToString();
+ target = AzMonList.GetTagValue(ref tagObjects, statementDbSystemKey)?.ToString();
}
return (DbName: dbName, DbTarget: target);
@@ -369,7 +379,7 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(
case OperationType.Http:
return tagObjects.GetHttpDependencyTarget();
case OperationType.Db:
- return tagObjects.GetDbDependencyTargetAndName().DbTarget;
+ return tagObjects.GetDbDependencyTargetAndName(type.HasFlag(OperationType.V2)).DbTarget;
case OperationType.Messaging:
return tagObjects.GetMessagingUrlAndSourceOrTarget(ActivityKind.Producer).SourceOrTarget;
default:
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs
index b6e8477ac4bf..1a41e56fe957 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs
@@ -142,7 +142,7 @@ internal static (string? MessagingUrl, string? SourceOrTarget) GetMessagingUrlAn
case OperationType.Http:
return tagObjects.GetNewSchemaHttpDependencyTarget();
case OperationType.Db:
- return tagObjects.GetDbDependencyTargetAndName().DbTarget;
+ return tagObjects.GetDbDependencyTargetAndName(type.HasFlag(OperationType.V2)).DbTarget;
default:
return null;
}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs
index e558d2e328c6..c3db8146993e 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs
@@ -164,5 +164,16 @@ internal static class SemanticConventions
// Messaging v1.21.0 https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/messaging.md
public const string AttributeMessagingDestinationName = "messaging.destination.name";
public const string AttributeNetworkProtocolName = "network.protocol.name";
+
+ // Database v1.36.0 https://github.com/open-telemetry/semantic-conventions/tree/v1.36.0/docs/database
+ public const string AttributeDbCollectionName = "db.collection.name";
+ public const string AttributeDbOperationName = "db.operation.name";
+ public const string AttributeDbSystemName = "db.system.name";
+ public const string AttributeDbNamespace = "db.namespace";
+ public const string AttributeDbResponseStatusCode = "db.response.status_code";
+ public const string AttributeDbOperationBatchSize = "db.operation.batch.size";
+ public const string AttributeDbQuerySummary = "db.query.summary";
+ public const string AttributeDbQueryText = "db.query.text";
+ public const string AttributeDbStoredProcedureName = "db.stored_procedure.name";
}
}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonListExtensionsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonListExtensionsTests.cs
index b3005681b2f9..bd485bf9696c 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonListExtensionsTests.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonListExtensionsTests.cs
@@ -564,32 +564,32 @@ public void DbNameIsAppendedToTargetDerivedFromNetAttributesforDBDependencyTarge
AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeServerAddress, serverAddress));
AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeServerPort, serverPort));
AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeServerSocketAddress, serverSocketAddress));
- AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeDbName, "DbName"));
+ AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeDbNamespace, "DbName"));
- Assert.Equal(expectedTarget, mappedTags.GetDbDependencyTargetAndName().DbTarget);
+ Assert.Equal(expectedTarget, mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
}
[Fact]
public void DbDependencyTargetIsSetToDbNameWhenNetAttributesAreNotPresent()
{
var mappedTags = AzMonList.Initialize();
- AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeDbName, "DbName"));
- Assert.Equal("DbName", mappedTags.GetDbDependencyTargetAndName().DbTarget);
+ AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeDbNamespace, "DbName"));
+ Assert.Equal("DbName", mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
}
[Fact]
public void DbDependencyTargetIsSetToDbSystemWhenNetAndDbNameAttributesAreNotPresent()
{
var mappedTags = AzMonList.Initialize();
- AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeDbSystem, "DbSystem"));
- Assert.Equal("DbSystem", mappedTags.GetDbDependencyTargetAndName().DbTarget);
+ AzMonList.Add(ref mappedTags, new KeyValuePair(SemanticConventions.AttributeDbSystemName, "DbSystem"));
+ Assert.Equal("DbSystem", mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
}
[Fact]
public void DbDependencyTargetIsSetToNullByDefault()
{
var mappedTags = AzMonList.Initialize();
- Assert.Null(mappedTags.GetDbDependencyTargetAndName().DbTarget);
+ Assert.Null(mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
}
}
}
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs
index a9ff8b9873a0..5b0816d7950e 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs
@@ -136,7 +136,7 @@ public void ValidateHttpRemoteDependencyData()
}
[Fact]
- public void ValidateDbRemoteDependencyData()
+ public void ValidateOldDbRemoteDependencyData()
{
using ActivitySource activitySource = new ActivitySource(ActivitySourceName);
using var activity = activitySource.StartActivity(
@@ -169,6 +169,40 @@ public void ValidateDbRemoteDependencyData()
Assert.True(remoteDependencyData.Measurements.Count == 0);
}
+ [Fact]
+ public void ValidateNewDbRemoteDependencyData()
+ {
+ using ActivitySource activitySource = new ActivitySource(ActivitySourceName);
+ using var activity = activitySource.StartActivity(
+ ActivityName,
+ ActivityKind.Client,
+ parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded),
+ startTime: DateTime.UtcNow);
+ Assert.NotNull(activity);
+ activity.Stop();
+
+ activity.SetStatus(ActivityStatusCode.Ok);
+ activity.SetTag(SemanticConventions.AttributeDbNamespace, "mysqlserver");
+ activity.SetTag(SemanticConventions.AttributeDbSystemName, "mssql");
+ activity.SetTag(SemanticConventions.AttributePeerService, "localhost"); // only adding test via peer.service. all possible combinations are covered in AzMonListExtensionsTests.
+ activity.SetTag(SemanticConventions.AttributeDbQueryText, "Select * from table");
+
+ var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity);
+
+ var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor);
+
+ Assert.Equal(ActivityName, remoteDependencyData.Name);
+ Assert.Equal(activity.Context.SpanId.ToHexString(), remoteDependencyData.Id);
+ Assert.Equal("Select * from table", remoteDependencyData.Data);
+ Assert.Equal("localhost | mysqlserver", remoteDependencyData.Target);
+ Assert.Null(remoteDependencyData.ResultCode);
+ Assert.Equal(activity.Duration.ToString("c", CultureInfo.InvariantCulture), remoteDependencyData.Duration);
+ Assert.Equal(activity.Status != ActivityStatusCode.Error, remoteDependencyData.Success);
+ Assert.True(remoteDependencyData.Properties.Count == 1);
+ Assert.True(remoteDependencyData.Properties.Contains(new KeyValuePair(SemanticConventions.AttributeDbName, "mysqlserver" )));
+ Assert.True(remoteDependencyData.Measurements.Count == 0);
+ }
+
[Fact]
public void HttpDependencyNameIsActivityDisplayNameByDefault()
{
diff --git a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs
index 0d4975a5fd12..ce9a36ca2c3b 100644
--- a/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs
+++ b/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs
@@ -116,7 +116,7 @@ public void TagObjects_Mapped()
}
[Fact]
- public void TagObjects_Mapped_HonorsNewSchema()
+ public void TagObjects_Mapped_HonorsNewHTTPSchema()
{
var activityTagsProcessor = new ActivityTagsProcessor();
@@ -140,6 +140,30 @@ public void TagObjects_Mapped_HonorsNewSchema()
Assert.Equal("/test", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeUrlPath));
}
+ [Fact]
+ public void TagObjects_Mapped_HonorsNewDBSchema()
+ {
+ var activityTagsProcessor = new ActivityTagsProcessor();
+
+ IEnumerable> tagObjects = new Dictionary
+ {
+ [SemanticConventions.AttributeDbNamespace] = "mysqlserver",
+ [SemanticConventions.AttributeDbSystemName] = "mssql",
+ [SemanticConventions.AttributePeerService] = "localhost",
+ [SemanticConventions.AttributeDbQueryText] = "Select * from table",
+ };
+
+ using var activity = CreateTestActivity(tagObjects);
+ activityTagsProcessor.CategorizeTags(activity);
+
+ Assert.Equal(OperationType.Db | OperationType.V2, activityTagsProcessor.activityType);
+ Assert.Equal(4, activityTagsProcessor.MappedTags.Length);
+ Assert.Equal("mysqlserver", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeDbNamespace));
+ Assert.Equal("mssql", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeDbSystemName));
+ Assert.Equal("localhost", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributePeerService));
+ Assert.Equal("Select * from table", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeDbQueryText));
+ }
+
[Fact]
public void TagObjects_Mapped_UnMapped()
{