-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SqlClient] Include instance in db.namespace
and activity name, start activity with required tags
#2277
[SqlClient] Include instance in db.namespace
and activity name, start activity with required tags
#2277
Changes from 4 commits
ae7c672
6731ac3
3a87e3a
af4922c
6a62be6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,41 +20,72 @@ internal sealed class SqlActivitySourceHelper | |
public static readonly AssemblyName AssemblyName = Assembly.GetName(); | ||
public static readonly string ActivitySourceName = AssemblyName.Name!; | ||
public static readonly ActivitySource ActivitySource = new(ActivitySourceName, Assembly.GetPackageVersion()); | ||
public static readonly string ActivityName = ActivitySourceName + ".Execute"; | ||
|
||
public static readonly IEnumerable<KeyValuePair<string, object?>> CreationTags = new[] | ||
public static TagList GetTagListFromConnectionInfo(string? dataSource, string? databaseName, SqlClientTraceInstrumentationOptions options, out string activityName) | ||
{ | ||
new KeyValuePair<string, object?>(SemanticConventions.AttributeDbSystem, MicrosoftSqlServerDatabaseSystemName), | ||
}; | ||
activityName = MicrosoftSqlServerDatabaseSystemName; | ||
|
||
public static void AddConnectionLevelDetailsToActivity(string dataSource, Activity activity, SqlClientTraceInstrumentationOptions options) | ||
{ | ||
// TODO: The attributes added here are required. We need to consider | ||
// collecting these attributes by default. | ||
if (options.EnableConnectionLevelAttributes) | ||
var tags = new TagList | ||
{ | ||
var connectionDetails = SqlConnectionDetails.ParseFromDataSource((string)dataSource); | ||
{ SemanticConventions.AttributeDbSystem, MicrosoftSqlServerDatabaseSystemName }, | ||
}; | ||
|
||
// TODO: In the new conventions, instance name should now be captured | ||
// as a part of db.namespace, when available. | ||
if (options.EmitOldAttributes && !string.IsNullOrEmpty(connectionDetails.InstanceName)) | ||
if (options.EnableConnectionLevelAttributes && dataSource != null) | ||
{ | ||
var connectionDetails = SqlConnectionDetails.ParseFromDataSource(dataSource); | ||
|
||
if (options.EmitOldAttributes && !string.IsNullOrEmpty(databaseName)) | ||
{ | ||
tags.Add(SemanticConventions.AttributeDbName, databaseName); | ||
activityName = databaseName!; | ||
} | ||
|
||
if (options.EmitNewAttributes && !string.IsNullOrEmpty(databaseName)) | ||
{ | ||
var dbNamespace = !string.IsNullOrEmpty(connectionDetails.InstanceName) | ||
? $"{connectionDetails.InstanceName}.{databaseName}" // TODO: Refactor SqlConnectionDetails to include database to avoid string allocation here. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Left a couple TODOs here. I'm considering adding database name to the SqlConnectionDetails that gets cached. This could mean though that the cache could be a little bigger in the event that multiple databases were accessed for a given instance. |
||
: databaseName!; | ||
tags.Add(SemanticConventions.AttributeDbNamespace, dbNamespace); | ||
activityName = dbNamespace; | ||
} | ||
|
||
var serverAddress = connectionDetails.ServerHostName ?? connectionDetails.ServerIpAddress; | ||
if (!string.IsNullOrEmpty(serverAddress)) | ||
{ | ||
activity.SetTag(SemanticConventions.AttributeDbMsSqlInstanceName, connectionDetails.InstanceName); | ||
tags.Add(SemanticConventions.AttributeServerAddress, serverAddress); | ||
if (connectionDetails.Port.HasValue) | ||
{ | ||
tags.Add(SemanticConventions.AttributeServerPort, connectionDetails.Port); | ||
} | ||
|
||
if (activityName == MicrosoftSqlServerDatabaseSystemName) | ||
{ | ||
activityName = connectionDetails.Port.HasValue | ||
? $"{serverAddress}:{connectionDetails.Port}" // TODO: Another opportunity to refactor SqlConnectionDetails | ||
: serverAddress!; | ||
} | ||
} | ||
|
||
if (!string.IsNullOrEmpty(connectionDetails.ServerHostName)) | ||
if (options.EmitOldAttributes && !string.IsNullOrEmpty(connectionDetails.InstanceName)) | ||
{ | ||
activity.SetTag(SemanticConventions.AttributeServerAddress, connectionDetails.ServerHostName); | ||
tags.Add(SemanticConventions.AttributeDbMsSqlInstanceName, connectionDetails.InstanceName); | ||
} | ||
else | ||
} | ||
else if (!string.IsNullOrEmpty(databaseName)) | ||
{ | ||
if (options.EmitNewAttributes) | ||
{ | ||
activity.SetTag(SemanticConventions.AttributeServerAddress, connectionDetails.ServerIpAddress); | ||
tags.Add(SemanticConventions.AttributeDbNamespace, databaseName); | ||
} | ||
|
||
if (connectionDetails.Port.HasValue) | ||
if (options.EmitOldAttributes) | ||
{ | ||
activity.SetTag(SemanticConventions.AttributeServerPort, connectionDetails.Port); | ||
tags.Add(SemanticConventions.AttributeDbName, databaseName); | ||
} | ||
|
||
activityName = databaseName!; | ||
} | ||
|
||
return tags; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -112,36 +112,23 @@ private void OnBeginExecute(EventWrittenEventArgs eventData) | |||||
return; | ||||||
} | ||||||
|
||||||
string dataSource = (string)eventData.Payload[1]; | ||||||
string databaseName = (string)eventData.Payload[2]; | ||||||
var startTags = SqlActivitySourceHelper.GetTagListFromConnectionInfo(dataSource, databaseName, this.options, out var activityName); | ||||||
var activity = SqlActivitySourceHelper.ActivitySource.StartActivity( | ||||||
SqlActivitySourceHelper.ActivityName, | ||||||
activityName, | ||||||
ActivityKind.Client, | ||||||
default(ActivityContext), | ||||||
SqlActivitySourceHelper.CreationTags); | ||||||
startTags); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting that In the meantime, maybe don't use A Lines 30 to 31 in 0351a0b
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I was bummed there was no
I had a similar thought to use ThreadStatic. Ultimately, these tags will need to be shared by the metric instrumentation as well. A ThreadStatic would allow us to capture them on command start and then use them on command end when recording the metric. I'll try this in a follow up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
You may update dotnet/runtime#103245 (comment) with whatever request. the runtime issue was talking about CreateActivity but can include StartActivity too. |
||||||
|
||||||
if (activity == null) | ||||||
{ | ||||||
// There is no listener or it decided not to sample the current request. | ||||||
return; | ||||||
} | ||||||
|
||||||
string? databaseName = (string)eventData.Payload[2]; | ||||||
|
||||||
activity.DisplayName = databaseName; | ||||||
|
||||||
if (activity.IsAllDataRequested) | ||||||
{ | ||||||
if (this.options.EmitOldAttributes) | ||||||
{ | ||||||
activity.SetTag(SemanticConventions.AttributeDbName, databaseName); | ||||||
} | ||||||
|
||||||
if (this.options.EmitNewAttributes) | ||||||
{ | ||||||
activity.SetTag(SemanticConventions.AttributeDbNamespace, databaseName); | ||||||
} | ||||||
|
||||||
SqlActivitySourceHelper.AddConnectionLevelDetailsToActivity((string)eventData.Payload[1], activity, this.options); | ||||||
|
||||||
string commandText = (string)eventData.Payload[3]; | ||||||
if (!string.IsNullOrEmpty(commandText) && this.options.SetDbStatementForText) | ||||||
{ | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
using System.Collections; | ||
|
||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests; | ||
|
||
public class SqlClientTestCase : IEnumerable<object[]> | ||
{ | ||
public string ConnectionString { get; set; } = string.Empty; | ||
|
||
public string ExpectedActivityName { get; set; } = string.Empty; | ||
|
||
public string? ExpectedServerAddress { get; set; } | ||
|
||
public int? ExpectedPort { get; set; } | ||
|
||
public string? ExpectedDbNamespace { get; set; } | ||
|
||
public string? ExpectedInstanceName { get; set; } | ||
|
||
private static SqlClientTestCase[] TestCases => | ||
[ | ||
new SqlClientTestCase | ||
{ | ||
ConnectionString = @"Data Source=SomeServer", | ||
ExpectedActivityName = "SomeServer", | ||
ExpectedServerAddress = "SomeServer", | ||
ExpectedPort = null, | ||
ExpectedDbNamespace = null, | ||
ExpectedInstanceName = null, | ||
}, | ||
new SqlClientTestCase | ||
{ | ||
ConnectionString = @"Data Source=SomeServer,1434", | ||
ExpectedActivityName = "SomeServer:1434", | ||
ExpectedServerAddress = "SomeServer", | ||
ExpectedPort = 1434, | ||
ExpectedDbNamespace = null, | ||
ExpectedInstanceName = null, | ||
}, | ||
]; | ||
|
||
public IEnumerator<object[]> GetEnumerator() | ||
{ | ||
foreach (var testCase in TestCases) | ||
{ | ||
yield return new object[] { testCase }; | ||
} | ||
} | ||
|
||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TagList
is a sizeablestruct
. It would probably be faster to return by reference a la:Probably the name sucks too 🤣
GetSamplerTagsFromConnectionInfo
🤷There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I agree. I'm gonna leave it sucky for now cause I don't know what to call it yet. This code may continue to be refactored a bit once I integrate metric instrumentation which will also need what this method is doing.