Skip to content

Commit 0f219ba

Browse files
mattsainsMatthew Sainsbury
andauthored
Add handling of stable database client span semantic conventions (Azure#53050)
* initial changes * initial go at tests * fix test * add tags tests * fix tests * update changelog * PR feedback --------- Co-authored-by: Matthew Sainsbury <[email protected]>
1 parent 0dd3cf9 commit 0f219ba

File tree

9 files changed

+119
-19
lines changed

9 files changed

+119
-19
lines changed

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
`Sdk.CreateTracerProviderBuilder().AddAzureMonitorTraceExporter(...)` path.
1818
([#52720](https://github.com/Azure/azure-sdk-for-net/pull/52720))
1919

20+
* Added handling of stable database client span semantic conventions
21+
([#53050](https://github.com/Azure/azure-sdk-for-net/pull/53050))
22+
2023
### Breaking Changes
2124

2225
### Bugs Fixed

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Customizations/Models/RemoteDependencyData.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public RemoteDependencyData(int version, Activity activity, ref ActivityTagsProc
2828
SetHttpDependencyPropertiesAndDependencyName(activity, ref activityTagsProcessor.MappedTags, isNewSchemaVersion, out dependencyName);
2929
break;
3030
case OperationType.Db:
31-
SetDbDependencyProperties(ref activityTagsProcessor.MappedTags);
31+
SetDbDependencyProperties(ref activityTagsProcessor.MappedTags, isNewSchemaVersion);
3232
break;
3333
case OperationType.Messaging:
3434
SetMessagingDependencyProperties(activity, ref activityTagsProcessor.MappedTags);
@@ -86,11 +86,23 @@ private void SetHttpDependencyPropertiesAndDependencyName(Activity activity, ref
8686
ResultCode = resultCode?.Truncate(SchemaConstants.RemoteDependencyData_ResultCode_MaxLength) ?? "0";
8787
}
8888

89-
private void SetDbDependencyProperties(ref AzMonList dbTagObjects)
89+
private void SetDbDependencyProperties(ref AzMonList dbTagObjects, bool isNewSchemaVersion)
9090
{
91-
var dbAttributeTagObjects = AzMonList.GetTagValues(ref dbTagObjects, SemanticConventions.AttributeDbStatement, SemanticConventions.AttributeDbSystem);
91+
string statementAttributeKey;
92+
string statementSystemKey;
93+
if (isNewSchemaVersion)
94+
{
95+
statementAttributeKey = SemanticConventions.AttributeDbQueryText;
96+
statementSystemKey = SemanticConventions.AttributeDbSystemName;
97+
}
98+
else
99+
{
100+
statementAttributeKey = SemanticConventions.AttributeDbStatement;
101+
statementSystemKey = SemanticConventions.AttributeDbSystem;
102+
}
103+
var dbAttributeTagObjects = AzMonList.GetTagValues(ref dbTagObjects, statementAttributeKey, statementSystemKey);
92104
Data = dbAttributeTagObjects[0]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Data_MaxLength);
93-
var (DbName, DbTarget) = dbTagObjects.GetDbDependencyTargetAndName();
105+
var (DbName, DbTarget) = dbTagObjects.GetDbDependencyTargetAndName(isNewSchemaVersion);
94106
Target = DbTarget?.Truncate(SchemaConstants.RemoteDependencyData_Target_MaxLength);
95107
Type = AzMonListExtensions.s_dbSystems.Contains(dbAttributeTagObjects[1]?.ToString()) ? "SQL" : dbAttributeTagObjects[1]?.ToString().Truncate(SchemaConstants.RemoteDependencyData_Type_MaxLength);
96108

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/ActivityTagsProcessor.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ internal struct ActivityTagsProcessor
1111
{
1212
private static readonly string[] s_semantics = {
1313
SemanticConventions.AttributeDbStatement,
14+
SemanticConventions.AttributeDbQueryText,
1415
SemanticConventions.AttributeDbSystem,
16+
SemanticConventions.AttributeDbSystemName,
1517
SemanticConventions.AttributeDbName,
18+
SemanticConventions.AttributeDbNamespace,
1619

1720
// required - HTTP
1821
SemanticConventions.AttributeHttpMethod,
@@ -97,6 +100,9 @@ public void CategorizeTags(Activity activity)
97100
case SemanticConventions.AttributeHttpRequestMethod:
98101
activityType = OperationType.Http | OperationType.V2;
99102
break;
103+
case SemanticConventions.AttributeDbSystemName:
104+
activityType = OperationType.Db | OperationType.V2;
105+
break;
100106
case SemanticConventions.AttributeDbSystem:
101107
activityType = OperationType.Db;
102108
break;

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonListExtensions.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,9 +329,19 @@ internal static string GetDefaultDbPort(string? dbSystem)
329329
///<summary>
330330
/// Gets Database dependency target and name from activity tag objects.
331331
///</summary>
332-
internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(this AzMonList tagObjects)
332+
internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(this AzMonList tagObjects, bool isNewSchemaVersion)
333333
{
334-
var peerServiceAndDbSystem = AzMonList.GetTagValues(ref tagObjects, SemanticConventions.AttributePeerService, SemanticConventions.AttributeDbSystem);
334+
string statementDbNameKey;
335+
string statementDbSystemKey;
336+
if (isNewSchemaVersion) {
337+
statementDbNameKey = SemanticConventions.AttributeDbNamespace;
338+
statementDbSystemKey = SemanticConventions.AttributeDbSystemName;
339+
} else {
340+
statementDbNameKey = SemanticConventions.AttributeDbName;
341+
statementDbSystemKey = SemanticConventions.AttributeDbSystem;
342+
}
343+
344+
var peerServiceAndDbSystem = AzMonList.GetTagValues(ref tagObjects, SemanticConventions.AttributePeerService, statementDbSystemKey);
335345
string? target = peerServiceAndDbSystem[0]?.ToString();
336346
var defaultPort = GetDefaultDbPort(peerServiceAndDbSystem[1]?.ToString());
337347

@@ -340,7 +350,7 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(
340350
target = tagObjects.GetTargetUsingServerAttributes(defaultPort) ?? tagObjects.GetTargetUsingNetPeerAttributes(defaultPort);
341351
}
342352

343-
var dbName = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeDbName)?.ToString();
353+
var dbName = AzMonList.GetTagValue(ref tagObjects, statementDbNameKey)?.ToString();
344354
bool isTargetEmpty = string.IsNullOrWhiteSpace(target);
345355
bool isDbNameEmpty = string.IsNullOrWhiteSpace(dbName);
346356
if (!isTargetEmpty && !isDbNameEmpty)
@@ -353,7 +363,7 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(
353363
}
354364
else if (isTargetEmpty && isDbNameEmpty)
355365
{
356-
target = AzMonList.GetTagValue(ref tagObjects, SemanticConventions.AttributeDbSystem)?.ToString();
366+
target = AzMonList.GetTagValue(ref tagObjects, statementDbSystemKey)?.ToString();
357367
}
358368

359369
return (DbName: dbName, DbTarget: target);
@@ -369,7 +379,7 @@ internal static (string? DbName, string? DbTarget) GetDbDependencyTargetAndName(
369379
case OperationType.Http:
370380
return tagObjects.GetHttpDependencyTarget();
371381
case OperationType.Db:
372-
return tagObjects.GetDbDependencyTargetAndName().DbTarget;
382+
return tagObjects.GetDbDependencyTargetAndName(type.HasFlag(OperationType.V2)).DbTarget;
373383
case OperationType.Messaging:
374384
return tagObjects.GetMessagingUrlAndSourceOrTarget(ActivityKind.Producer).SourceOrTarget;
375385
default:

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/AzMonNewListExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ internal static (string? MessagingUrl, string? SourceOrTarget) GetMessagingUrlAn
142142
case OperationType.Http:
143143
return tagObjects.GetNewSchemaHttpDependencyTarget();
144144
case OperationType.Db:
145-
return tagObjects.GetDbDependencyTargetAndName().DbTarget;
145+
return tagObjects.GetDbDependencyTargetAndName(type.HasFlag(OperationType.V2)).DbTarget;
146146
default:
147147
return null;
148148
}

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/src/Internals/SemanticConventions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,5 +164,16 @@ internal static class SemanticConventions
164164
// Messaging v1.21.0 https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/trace/semantic_conventions/messaging.md
165165
public const string AttributeMessagingDestinationName = "messaging.destination.name";
166166
public const string AttributeNetworkProtocolName = "network.protocol.name";
167+
168+
// Database v1.36.0 https://github.com/open-telemetry/semantic-conventions/tree/v1.36.0/docs/database
169+
public const string AttributeDbCollectionName = "db.collection.name";
170+
public const string AttributeDbOperationName = "db.operation.name";
171+
public const string AttributeDbSystemName = "db.system.name";
172+
public const string AttributeDbNamespace = "db.namespace";
173+
public const string AttributeDbResponseStatusCode = "db.response.status_code";
174+
public const string AttributeDbOperationBatchSize = "db.operation.batch.size";
175+
public const string AttributeDbQuerySummary = "db.query.summary";
176+
public const string AttributeDbQueryText = "db.query.text";
177+
public const string AttributeDbStoredProcedureName = "db.stored_procedure.name";
167178
}
168179
}

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/AzMonListExtensionsTests.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -564,32 +564,32 @@ public void DbNameIsAppendedToTargetDerivedFromNetAttributesforDBDependencyTarge
564564
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeServerAddress, serverAddress));
565565
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeServerPort, serverPort));
566566
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeServerSocketAddress, serverSocketAddress));
567-
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeDbName, "DbName"));
567+
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeDbNamespace, "DbName"));
568568

569-
Assert.Equal(expectedTarget, mappedTags.GetDbDependencyTargetAndName().DbTarget);
569+
Assert.Equal(expectedTarget, mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
570570
}
571571

572572
[Fact]
573573
public void DbDependencyTargetIsSetToDbNameWhenNetAttributesAreNotPresent()
574574
{
575575
var mappedTags = AzMonList.Initialize();
576-
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeDbName, "DbName"));
577-
Assert.Equal("DbName", mappedTags.GetDbDependencyTargetAndName().DbTarget);
576+
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeDbNamespace, "DbName"));
577+
Assert.Equal("DbName", mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
578578
}
579579

580580
[Fact]
581581
public void DbDependencyTargetIsSetToDbSystemWhenNetAndDbNameAttributesAreNotPresent()
582582
{
583583
var mappedTags = AzMonList.Initialize();
584-
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeDbSystem, "DbSystem"));
585-
Assert.Equal("DbSystem", mappedTags.GetDbDependencyTargetAndName().DbTarget);
584+
AzMonList.Add(ref mappedTags, new KeyValuePair<string, object?>(SemanticConventions.AttributeDbSystemName, "DbSystem"));
585+
Assert.Equal("DbSystem", mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
586586
}
587587

588588
[Fact]
589589
public void DbDependencyTargetIsSetToNullByDefault()
590590
{
591591
var mappedTags = AzMonList.Initialize();
592-
Assert.Null(mappedTags.GetDbDependencyTargetAndName().DbTarget);
592+
Assert.Null(mappedTags.GetDbDependencyTargetAndName(true).DbTarget);
593593
}
594594
}
595595
}

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/RemoteDependencyDataTests.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ public void ValidateHttpRemoteDependencyData()
136136
}
137137

138138
[Fact]
139-
public void ValidateDbRemoteDependencyData()
139+
public void ValidateOldDbRemoteDependencyData()
140140
{
141141
using ActivitySource activitySource = new ActivitySource(ActivitySourceName);
142142
using var activity = activitySource.StartActivity(
@@ -169,6 +169,40 @@ public void ValidateDbRemoteDependencyData()
169169
Assert.True(remoteDependencyData.Measurements.Count == 0);
170170
}
171171

172+
[Fact]
173+
public void ValidateNewDbRemoteDependencyData()
174+
{
175+
using ActivitySource activitySource = new ActivitySource(ActivitySourceName);
176+
using var activity = activitySource.StartActivity(
177+
ActivityName,
178+
ActivityKind.Client,
179+
parentContext: new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded),
180+
startTime: DateTime.UtcNow);
181+
Assert.NotNull(activity);
182+
activity.Stop();
183+
184+
activity.SetStatus(ActivityStatusCode.Ok);
185+
activity.SetTag(SemanticConventions.AttributeDbNamespace, "mysqlserver");
186+
activity.SetTag(SemanticConventions.AttributeDbSystemName, "mssql");
187+
activity.SetTag(SemanticConventions.AttributePeerService, "localhost"); // only adding test via peer.service. all possible combinations are covered in AzMonListExtensionsTests.
188+
activity.SetTag(SemanticConventions.AttributeDbQueryText, "Select * from table");
189+
190+
var activityTagsProcessor = TraceHelper.EnumerateActivityTags(activity);
191+
192+
var remoteDependencyData = new RemoteDependencyData(2, activity, ref activityTagsProcessor);
193+
194+
Assert.Equal(ActivityName, remoteDependencyData.Name);
195+
Assert.Equal(activity.Context.SpanId.ToHexString(), remoteDependencyData.Id);
196+
Assert.Equal("Select * from table", remoteDependencyData.Data);
197+
Assert.Equal("localhost | mysqlserver", remoteDependencyData.Target);
198+
Assert.Null(remoteDependencyData.ResultCode);
199+
Assert.Equal(activity.Duration.ToString("c", CultureInfo.InvariantCulture), remoteDependencyData.Duration);
200+
Assert.Equal(activity.Status != ActivityStatusCode.Error, remoteDependencyData.Success);
201+
Assert.True(remoteDependencyData.Properties.Count == 1);
202+
Assert.True(remoteDependencyData.Properties.Contains(new KeyValuePair<string, string>(SemanticConventions.AttributeDbName, "mysqlserver" )));
203+
Assert.True(remoteDependencyData.Measurements.Count == 0);
204+
}
205+
172206
[Fact]
173207
public void HttpDependencyNameIsActivityDisplayNameByDefault()
174208
{

sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Tests/TagsTests.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void TagObjects_Mapped()
116116
}
117117

118118
[Fact]
119-
public void TagObjects_Mapped_HonorsNewSchema()
119+
public void TagObjects_Mapped_HonorsNewHTTPSchema()
120120
{
121121
var activityTagsProcessor = new ActivityTagsProcessor();
122122

@@ -140,6 +140,30 @@ public void TagObjects_Mapped_HonorsNewSchema()
140140
Assert.Equal("/test", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeUrlPath));
141141
}
142142

143+
[Fact]
144+
public void TagObjects_Mapped_HonorsNewDBSchema()
145+
{
146+
var activityTagsProcessor = new ActivityTagsProcessor();
147+
148+
IEnumerable<KeyValuePair<string, object?>> tagObjects = new Dictionary<string, object?>
149+
{
150+
[SemanticConventions.AttributeDbNamespace] = "mysqlserver",
151+
[SemanticConventions.AttributeDbSystemName] = "mssql",
152+
[SemanticConventions.AttributePeerService] = "localhost",
153+
[SemanticConventions.AttributeDbQueryText] = "Select * from table",
154+
};
155+
156+
using var activity = CreateTestActivity(tagObjects);
157+
activityTagsProcessor.CategorizeTags(activity);
158+
159+
Assert.Equal(OperationType.Db | OperationType.V2, activityTagsProcessor.activityType);
160+
Assert.Equal(4, activityTagsProcessor.MappedTags.Length);
161+
Assert.Equal("mysqlserver", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeDbNamespace));
162+
Assert.Equal("mssql", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeDbSystemName));
163+
Assert.Equal("localhost", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributePeerService));
164+
Assert.Equal("Select * from table", AzMonList.GetTagValue(ref activityTagsProcessor.MappedTags, SemanticConventions.AttributeDbQueryText));
165+
}
166+
143167
[Fact]
144168
public void TagObjects_Mapped_UnMapped()
145169
{

0 commit comments

Comments
 (0)