-
Notifications
You must be signed in to change notification settings - Fork 780
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
Added instrumentation for netfx SqlClient. #761
Changes from 3 commits
06978ba
40dd3b1
fb25ff8
401f740
dade046
baa5f1b
14c399d
c3a499c
83200ea
7e62d1b
b9908b8
e9ac05e
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 |
---|---|---|
|
@@ -40,9 +40,10 @@ public static class SpanAttributeConstants | |
public const string HttpRouteKey = "http.route"; | ||
public const string HttpFlavorKey = "http.flavor"; | ||
|
||
public const string DatabaseTypeKey = "db.type"; | ||
public const string DatabaseInstanceKey = "db.instance"; | ||
public const string DatabaseSystemKey = "db.system"; | ||
public const string DatabaseNameKey = "db.name"; | ||
public const string DatabaseStatementKey = "db.statement"; | ||
public const string DatabaseStatementTypeKey = "db.statement_type"; | ||
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. Is this part of spec? 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. Oh I now recollect we already discussed this. Will need to follow up on spec. Note to myself! |
||
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,6 @@ | |
using System.Data; | ||
using System.Diagnostics; | ||
using OpenTelemetry.Trace; | ||
using OpenTelemetry.Trace.Samplers; | ||
|
||
namespace OpenTelemetry.Instrumentation.Dependencies.Implementation | ||
{ | ||
|
@@ -32,7 +31,7 @@ internal class SqlClientDiagnosticListener : ListenerHandler | |
internal const string SqlDataWriteCommandError = "System.Data.SqlClient.WriteCommandError"; | ||
internal const string SqlMicrosoftWriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError"; | ||
|
||
private const string DatabaseStatementTypeSpanAttributeKey = "db.statementType"; | ||
internal const string MicrosoftSqlServerDatabaseSystemName = "mssql"; | ||
|
||
private readonly PropertyFetcher commandFetcher = new PropertyFetcher("Command"); | ||
private readonly PropertyFetcher connectionFetcher = new PropertyFetcher("Connection"); | ||
|
@@ -85,17 +84,16 @@ public override void OnCustom(string name, Activity activity, object payload) | |
var commandText = this.commandTextFetcher.Fetch(command); | ||
|
||
activity.AddTag(SpanAttributeConstants.ComponentKey, "sql"); | ||
activity.AddTag(SpanAttributeConstants.DatabaseTypeKey, "sql"); | ||
activity.AddTag(SpanAttributeConstants.DatabaseSystemKey, MicrosoftSqlServerDatabaseSystemName); | ||
activity.AddTag(SpanAttributeConstants.PeerServiceKey, (string)dataSource); | ||
activity.AddTag(SpanAttributeConstants.DatabaseInstanceKey, (string)database); | ||
activity.AddTag(SpanAttributeConstants.DatabaseNameKey, (string)database); | ||
|
||
if (this.commandTypeFetcher.Fetch(command) is CommandType commandType) | ||
{ | ||
activity.AddTag(DatabaseStatementTypeSpanAttributeKey, commandType.ToString()); | ||
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. This change was made to avoid the 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. :D I read this comment late, and was still trying to figure out this part ! |
||
|
||
switch (commandType) | ||
{ | ||
case CommandType.StoredProcedure: | ||
activity.AddTag(SpanAttributeConstants.DatabaseStatementTypeKey, nameof(CommandType.StoredProcedure)); | ||
if (this.options.CaptureStoredProcedureCommandName) | ||
{ | ||
activity.AddTag(SpanAttributeConstants.DatabaseStatementKey, (string)commandText); | ||
|
@@ -104,12 +102,17 @@ public override void OnCustom(string name, Activity activity, object payload) | |
break; | ||
|
||
case CommandType.Text: | ||
activity.AddTag(SpanAttributeConstants.DatabaseStatementTypeKey, nameof(CommandType.Text)); | ||
if (this.options.CaptureTextCommandContent) | ||
{ | ||
activity.AddTag(SpanAttributeConstants.DatabaseStatementKey, (string)commandText); | ||
} | ||
|
||
break; | ||
|
||
case CommandType.TableDirect: | ||
activity.AddTag(SpanAttributeConstants.DatabaseStatementTypeKey, nameof(CommandType.TableDirect)); | ||
break; | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,195 @@ | ||||||||||||||||||||||||||||||||||||
// <copyright file="SqlEventSourceListener.netfx.cs" company="OpenTelemetry Authors"> | ||||||||||||||||||||||||||||||||||||
// Copyright The OpenTelemetry Authors | ||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||||||||||||||||
// you may not use this file except in compliance with the License. | ||||||||||||||||||||||||||||||||||||
// You may obtain a copy of the License at | ||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||
// http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||
// Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||||||||||||||||
// distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||||||||||||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||||||||||||||||
// See the License for the specific language governing permissions and | ||||||||||||||||||||||||||||||||||||
// limitations under the License. | ||||||||||||||||||||||||||||||||||||
// </copyright> | ||||||||||||||||||||||||||||||||||||
#if NETFRAMEWORK | ||||||||||||||||||||||||||||||||||||
using System; | ||||||||||||||||||||||||||||||||||||
using System.Data; | ||||||||||||||||||||||||||||||||||||
using System.Diagnostics; | ||||||||||||||||||||||||||||||||||||
using System.Diagnostics.Tracing; | ||||||||||||||||||||||||||||||||||||
using OpenTelemetry.Trace; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
namespace OpenTelemetry.Instrumentation.Dependencies.Implementation | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
/// <summary> | ||||||||||||||||||||||||||||||||||||
/// .NET Framework SqlClient doesn't emit DiagnosticSource events. | ||||||||||||||||||||||||||||||||||||
/// We hook into its EventSource if it is available: | ||||||||||||||||||||||||||||||||||||
/// See: <a href="https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Data/System/Data/Common/SqlEventSource.cs#L29">reference source</a>. | ||||||||||||||||||||||||||||||||||||
/// </summary> | ||||||||||||||||||||||||||||||||||||
internal class SqlEventSourceListener : EventListener | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
internal const string ActivitySourceName = "System.Data.SqlClient"; | ||||||||||||||||||||||||||||||||||||
internal const string ActivityName = ActivitySourceName + ".Execute"; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
private const string AdoNetEventSourceName = "Microsoft-AdoNet-SystemData"; | ||||||||||||||||||||||||||||||||||||
private const int BeginExecuteEventId = 1; | ||||||||||||||||||||||||||||||||||||
private const int EndExecuteEventId = 2; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
private static readonly Version Version = typeof(SqlEventSourceListener).Assembly.GetName().Version; | ||||||||||||||||||||||||||||||||||||
private static readonly ActivitySource SqlClientActivitySource = new ActivitySource(ActivitySourceName, Version.ToString()); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
private readonly SqlClientInstrumentationOptions options; | ||||||||||||||||||||||||||||||||||||
private EventSource eventSource; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
public SqlEventSourceListener(SqlClientInstrumentationOptions options = null) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
this.options = options ?? new SqlClientInstrumentationOptions(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
public override void Dispose() | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
if (this.eventSource != null) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
this.DisableEvents(this.eventSource); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
base.Dispose(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
protected override void OnEventSourceCreated(EventSource eventSource) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
if (eventSource?.Name == AdoNetEventSourceName) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
this.eventSource = eventSource; | ||||||||||||||||||||||||||||||||||||
this.EnableEvents(eventSource, EventLevel.Informational, (EventKeywords)1); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
base.OnEventSourceCreated(eventSource); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
protected override void OnEventWritten(EventWrittenEventArgs eventData) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
if (eventData?.Payload == null) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
InstrumentationEventSource.Log.NullPayload(nameof(SqlEventSourceListener) + nameof(this.OnEventWritten)); | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
try | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
if (eventData.EventId == BeginExecuteEventId) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
this.OnBeginExecute(eventData); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
else if (eventData.EventId == EndExecuteEventId) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
this.OnEndExecute(eventData); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
catch (Exception exc) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
InstrumentationEventSource.Log.UnknownErrorProcessingEvent(nameof(SqlEventSourceListener), nameof(this.OnEventWritten), exc); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
private void OnBeginExecute(EventWrittenEventArgs eventData) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||||||||||||
Expected payload: | ||||||||||||||||||||||||||||||||||||
[0] -> ObjectId | ||||||||||||||||||||||||||||||||||||
[1] -> DataSource | ||||||||||||||||||||||||||||||||||||
[2] -> Database | ||||||||||||||||||||||||||||||||||||
[3] -> CommandText ([3] = CommandType == CommandType.StoredProcedure ? CommandText : string.Empty) | ||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
if (eventData.Payload.Count < 4) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
var activity = SqlClientActivitySource.StartActivity(ActivityName, ActivityKind.Client); | ||||||||||||||||||||||||||||||||||||
if (activity == null) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
string databaseName = (string)eventData.Payload[2]; | ||||||||||||||||||||||||||||||||||||
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. wondering why setting displayname is not inside 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. I modeled after the HttpWebRequestActivitySource: Lines 94 to 110 in ca322b3
Line 96 in that snippet. I did it there originally because the DisplayName felt useful to have always? Open to change it. 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. Got. I couldn't find any guidelines on this, so at this points, its up to individual library authors to do it. (in case adapters "we" are the authors). |
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
activity.DisplayName = databaseName; | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
if (activity.IsAllDataRequested) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.ComponentKey, "sql"); | ||||||||||||||||||||||||||||||||||||
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. note to myself. |
||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.DatabaseSystemKey, SqlClientDiagnosticListener.MicrosoftSqlServerDatabaseSystemName); | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.PeerServiceKey, (string)eventData.Payload[1]); | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.DatabaseNameKey, databaseName); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
string commandText = (string)eventData.Payload[3]; | ||||||||||||||||||||||||||||||||||||
if (string.IsNullOrEmpty(commandText)) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.DatabaseStatementTypeKey, nameof(CommandType.Text)); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.DatabaseStatementTypeKey, nameof(CommandType.StoredProcedure)); | ||||||||||||||||||||||||||||||||||||
if (this.options.CaptureStoredProcedureCommandName) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.DatabaseStatementKey, commandText); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
private void OnEndExecute(EventWrittenEventArgs eventData) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
/* | ||||||||||||||||||||||||||||||||||||
Expected payload: | ||||||||||||||||||||||||||||||||||||
[0] -> ObjectId | ||||||||||||||||||||||||||||||||||||
[1] -> CompositeState bitmask (0b001 -> successFlag, 0b010 -> isSqlExceptionFlag , 0b100 -> synchronousFlag) | ||||||||||||||||||||||||||||||||||||
[2] -> SqlExceptionNumber | ||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
if (eventData.Payload.Count < 3) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
var activity = Activity.Current; | ||||||||||||||||||||||||||||||||||||
if (activity?.Source != SqlClientActivitySource) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
return; | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
try | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
if (activity.IsAllDataRequested) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
int compositeState = (int)eventData.Payload[1]; | ||||||||||||||||||||||||||||||||||||
if ((compositeState & 0b001) == 0b001) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.StatusCodeKey, SpanHelper.GetCachedCanonicalCodeString(StatusCanonicalCode.Ok)); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.StatusCodeKey, SpanHelper.GetCachedCanonicalCodeString(StatusCanonicalCode.Unknown)); | ||||||||||||||||||||||||||||||||||||
if ((compositeState & 0b010) == 0b010) | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.StatusDescriptionKey, $"SqlExceptionNumber {eventData.Payload[2]} thrown."); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.AddTag(SpanAttributeConstants.StatusDescriptionKey, $"Unknown Sql failure."); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
finally | ||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||
activity.Stop(); | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
#endif |
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.
can you add this if feasible?
https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md#connection-level-attributes-for-specific-technologies
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.
Created #782 for this.