diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln
index a56b9373dbc..e7d3b00c479 100644
--- a/OpenTelemetry.sln
+++ b/OpenTelemetry.sln
@@ -101,6 +101,38 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentati
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests", "test\OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests\OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj", "{7C4026CA-6434-4762-8B77-D657EAEE1325}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{F1D0972B-38CF-49C2-9F4B-4C5DE02FB71D}"
+ ProjectSection(SolutionItems) = preProject
+ .github\CODEOWNERS = .github\CODEOWNERS
+ .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{A533C800-3DC3-4D04-90A7-0CE7A1E6BDB3}"
+ ProjectSection(SolutionItems) = preProject
+ .github\ISSUE_TEMPLATE\bug_report.md = .github\ISSUE_TEMPLATE\bug_report.md
+ .github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md
+ .github\ISSUE_TEMPLATE\question.md = .github\ISSUE_TEMPLATE\question.md
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{E69578EB-B456-4062-A645-877CD964528B}"
+ ProjectSection(SolutionItems) = preProject
+ .github\workflows\dotnet-core-linux.yml = .github\workflows\dotnet-core-linux.yml
+ .github\workflows\dotnet-core-win.yml = .github\workflows\dotnet-core-win.yml
+ .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C1542297-8763-4DF4-957C-489ED771C21D}"
+ ProjectSection(SolutionItems) = preProject
+ src\Directory.Build.props = src\Directory.Build.props
+ src\Directory.Build.targets = src\Directory.Build.targets
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D2E73927-5966-445C-94E9-EFE6F269C8D5}"
+ ProjectSection(SolutionItems) = preProject
+ test\Directory.Build.props = test\Directory.Build.props
+ test\Directory.Build.targets = test\Directory.Build.targets
+ EndProjectSection
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.GrpcClient.Tests", "test\OpenTelemetry.Instrumentation.GrpcClient.Tests\OpenTelemetry.Instrumentation.GrpcClient.Tests.csproj", "{305E9DFD-E73B-4A28-8769-795C25551020}"
EndProject
Global
@@ -243,6 +275,10 @@ Global
{47318988-CA8B-4C81-B55D-2FA11D295A49} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
{25C06046-C7D0-46B4-AAAC-90C50C43DE7A} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
{9A4E3A68-904B-4835-A3C8-F664B73098DB} = {E359BB2B-9AEC-497D-B321-7DF2450C3B8E}
+ {A533C800-3DC3-4D04-90A7-0CE7A1E6BDB3} = {F1D0972B-38CF-49C2-9F4B-4C5DE02FB71D}
+ {E69578EB-B456-4062-A645-877CD964528B} = {F1D0972B-38CF-49C2-9F4B-4C5DE02FB71D}
+ {C1542297-8763-4DF4-957C-489ED771C21D} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
+ {D2E73927-5966-445C-94E9-EFE6F269C8D5} = {7CB2F02E-03FA-4FFF-89A5-C51F107623FD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
diff --git a/src/OpenTelemetry.Api/Trace/SpanAttributeConstants.cs b/src/OpenTelemetry.Api/Trace/SpanAttributeConstants.cs
index 196c3ef4191..5e028a609be 100644
--- a/src/OpenTelemetry.Api/Trace/SpanAttributeConstants.cs
+++ b/src/OpenTelemetry.Api/Trace/SpanAttributeConstants.cs
@@ -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";
public const string RpcSystem = "rpc.system";
public const string RpcService = "rpc.service";
diff --git a/src/OpenTelemetry.Api/Trace/SpanExtensions.cs b/src/OpenTelemetry.Api/Trace/SpanExtensions.cs
index 7b63b3108c3..00466429ef0 100644
--- a/src/OpenTelemetry.Api/Trace/SpanExtensions.cs
+++ b/src/OpenTelemetry.Api/Trace/SpanExtensions.cs
@@ -207,34 +207,34 @@ public static TelemetrySpan PutHttpFlavorAttribute(this TelemetrySpan span, stri
}
///
- /// Helper method that populates database type
- /// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md.
+ /// Helper method that populates database system
+ /// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md.
///
/// Span to fill out.
- /// Database type.
+ /// Database system.
/// Span with populated properties.
- public static TelemetrySpan PutDatabaseTypeAttribute(this TelemetrySpan span, string type)
+ public static TelemetrySpan PutDatabaseSystemAttribute(this TelemetrySpan span, string system)
{
- span.SetAttribute(SpanAttributeConstants.DatabaseTypeKey, type);
+ span.SetAttribute(SpanAttributeConstants.DatabaseSystemKey, system);
return span;
}
///
- /// Helper method that populates database instance
- /// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md.
+ /// Helper method that populates database name
+ /// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md.
///
/// Span to fill out.
- /// Database instance.
+ /// Database name.
/// Span with populated properties.
- public static TelemetrySpan PutDatabaseInstanceAttribute(this TelemetrySpan span, string instance)
+ public static TelemetrySpan PutDatabaseNameAttribute(this TelemetrySpan span, string name)
{
- span.SetAttribute(SpanAttributeConstants.DatabaseInstanceKey, instance);
+ span.SetAttribute(SpanAttributeConstants.DatabaseNameKey, name);
return span;
}
///
/// Helper method that populates database statement
- /// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md.
+ /// to https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md.
///
/// Span to fill out.
/// Database statement.
diff --git a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs
index b3bd0369d72..f384a5ead17 100644
--- a/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs
+++ b/src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs
@@ -98,7 +98,7 @@ public static JaegerSpan ToJaegerSpan(this Activity activity)
PooledList.Add(ref jaegerTags.Tags, new JaegerTag("library.name", JaegerTagType.STRING, vStr: activitySource.Name));
if (!string.IsNullOrEmpty(activitySource.Version))
{
- PooledList.Add(ref jaegerTags.Tags, new JaegerTag("library.version", JaegerTagType.STRING, vStr: activitySource.Name));
+ PooledList.Add(ref jaegerTags.Tags, new JaegerTag("library.version", JaegerTagType.STRING, vStr: activitySource.Version));
}
}
diff --git a/src/OpenTelemetry.Instrumentation.AspNet.Win/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet.Win/Implementation/HttpInListener.cs
index 5f4399132c5..db4c44657f4 100644
--- a/src/OpenTelemetry.Instrumentation.AspNet.Win/Implementation/HttpInListener.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNet.Win/Implementation/HttpInListener.cs
@@ -40,12 +40,10 @@ public HttpInListener(string name, AspNetInstrumentationOptions options, Activit
public override void OnStartActivity(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnStartActivity";
-
var context = HttpContext.Current;
if (context == null)
{
- InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity));
return;
}
@@ -114,8 +112,6 @@ public override void OnStartActivity(Activity activity, object payload)
public override void OnStopActivity(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnStopActivity";
-
Activity activityToEnrich = activity;
if (!(this.options.TextFormat is TraceContextFormatActivity))
@@ -139,7 +135,7 @@ public override void OnStopActivity(Activity activity, object payload)
var context = HttpContext.Current;
if (context == null)
{
- InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity));
return;
}
diff --git a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
index cf4d655ee44..ab5ecd6324b 100644
--- a/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
+++ b/src/OpenTelemetry.Instrumentation.AspNetCore/Implementation/HttpInListener.cs
@@ -48,12 +48,11 @@ public HttpInListener(string name, AspNetCoreInstrumentationOptions options, Act
public override void OnStartActivity(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnStartActivity";
var context = this.startContextFetcher.Fetch(payload) as HttpContext;
if (context == null)
{
- InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStartActivity));
return;
}
@@ -120,13 +119,11 @@ public override void OnStartActivity(Activity activity, object payload)
public override void OnStopActivity(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnStopActivity";
-
if (activity.IsAllDataRequested)
{
if (!(this.stopContextFetcher.Fetch(payload) is HttpContext context))
{
- InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(HttpInListener), nameof(this.OnStopActivity));
return;
}
diff --git a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/GrpcClientDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/GrpcClientDiagnosticListener.cs
index 85f0ba2bce6..a00fe5930f8 100644
--- a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/GrpcClientDiagnosticListener.cs
+++ b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/GrpcClientDiagnosticListener.cs
@@ -41,10 +41,9 @@ public GrpcClientDiagnosticListener(ActivitySourceAdapter activitySource)
public override void OnStartActivity(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnStartActivity";
if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request))
{
- InstrumentationEventSource.Log.NullPayload(nameof(GrpcClientDiagnosticListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(GrpcClientDiagnosticListener), nameof(this.OnStartActivity));
return;
}
diff --git a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs
index 8ea527473da..1938fb7f315 100644
--- a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs
+++ b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/HttpHandlerDiagnosticListener.cs
@@ -62,10 +62,9 @@ public HttpHandlerDiagnosticListener(HttpClientInstrumentationOptions options, A
public override void OnStartActivity(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnStartActivity";
if (!(this.startRequestFetcher.Fetch(payload) is HttpRequestMessage request))
{
- InstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnStartActivity));
return;
}
@@ -141,13 +140,11 @@ public override void OnStopActivity(Activity activity, object payload)
public override void OnException(Activity activity, object payload)
{
- const string EventNameSuffix = ".OnException";
-
if (activity.IsAllDataRequested)
{
if (!(this.stopExceptionFetcher.Fetch(payload) is Exception exc))
{
- InstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener) + EventNameSuffix);
+ InstrumentationEventSource.Log.NullPayload(nameof(HttpHandlerDiagnosticListener), nameof(this.OnException));
return;
}
diff --git a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlClientDiagnosticListener.cs b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlClientDiagnosticListener.cs
index 6cb246d2019..cbb44ebed0c 100644
--- a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlClientDiagnosticListener.cs
+++ b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlClientDiagnosticListener.cs
@@ -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");
@@ -66,7 +65,7 @@ public override void OnCustom(string name, Activity activity, object payload)
if (command == null)
{
- InstrumentationEventSource.Log.NullPayload($"{nameof(SqlClientDiagnosticListener)}-{name}");
+ InstrumentationEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener), name);
return;
}
@@ -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());
-
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;
}
}
}
@@ -136,7 +139,7 @@ public override void OnCustom(string name, Activity activity, object payload)
}
else
{
- InstrumentationEventSource.Log.NullPayload($"{nameof(SqlClientDiagnosticListener)}-{name}");
+ InstrumentationEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener), name);
}
}
diff --git a/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs
new file mode 100644
index 00000000000..f9b84f84aaa
--- /dev/null
+++ b/src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs
@@ -0,0 +1,190 @@
+//
+// 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.
+//
+#if NETFRAMEWORK
+using System;
+using System.Data;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Instrumentation.Dependencies.Implementation
+{
+ ///
+ /// .NET Framework SqlClient doesn't emit DiagnosticSource events.
+ /// We hook into its EventSource if it is available:
+ /// See: reference source.
+ ///
+ internal class SqlEventSourceListener : EventListener
+ {
+ internal const string ActivitySourceName = "System.Data.SqlClient";
+ internal const string ActivityName = ActivitySourceName + ".Execute";
+ internal const string AdoNetEventSourceName = "Microsoft-AdoNet-SystemData";
+ internal const int BeginExecuteEventId = 1;
+ internal 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.StartsWith(AdoNetEventSourceName) == true)
+ {
+ this.eventSource = eventSource;
+ this.EnableEvents(eventSource, EventLevel.Informational, (EventKeywords)1);
+ }
+
+ base.OnEventSourceCreated(eventSource);
+ }
+
+ protected override void OnEventWritten(EventWrittenEventArgs eventData)
+ {
+ 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 ?? 0) < 4)
+ {
+ InstrumentationEventSource.Log.InvalidPayload(nameof(SqlEventSourceListener), nameof(this.OnBeginExecute));
+ return;
+ }
+
+ var activity = SqlClientActivitySource.StartActivity(ActivityName, ActivityKind.Client);
+ if (activity == null)
+ {
+ return;
+ }
+
+ string databaseName = (string)eventData.Payload[2];
+
+ activity.DisplayName = databaseName;
+
+ if (activity.IsAllDataRequested)
+ {
+ activity.AddTag(SpanAttributeConstants.ComponentKey, "sql");
+
+ 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 ?? 0) < 3)
+ {
+ InstrumentationEventSource.Log.InvalidPayload(nameof(SqlEventSourceListener), nameof(this.OnEndExecute));
+ 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
diff --git a/src/OpenTelemetry.Instrumentation.Dependencies/OpenTelemetryBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Dependencies/OpenTelemetryBuilderExtensions.cs
index 9d95444b1bf..82c07e5138d 100644
--- a/src/OpenTelemetry.Instrumentation.Dependencies/OpenTelemetryBuilderExtensions.cs
+++ b/src/OpenTelemetry.Instrumentation.Dependencies/OpenTelemetryBuilderExtensions.cs
@@ -135,6 +135,9 @@ public static OpenTelemetryBuilder AddSqlClientDependencyInstrumentation(
configureSqlClientInstrumentationOptions?.Invoke(sqlOptions);
builder.AddInstrumentation((activitySource) => new SqlClientInstrumentation(activitySource, sqlOptions));
+#if NETFRAMEWORK
+ builder.AddActivitySource(SqlEventSourceListener.ActivitySourceName);
+#endif
return builder;
}
diff --git a/src/OpenTelemetry.Instrumentation.Dependencies/SqlClientInstrumentation.cs b/src/OpenTelemetry.Instrumentation.Dependencies/SqlClientInstrumentation.cs
index 93d13b69b4d..5adabc048c0 100644
--- a/src/OpenTelemetry.Instrumentation.Dependencies/SqlClientInstrumentation.cs
+++ b/src/OpenTelemetry.Instrumentation.Dependencies/SqlClientInstrumentation.cs
@@ -27,6 +27,9 @@ internal class SqlClientInstrumentation : IDisposable
internal const string SqlClientDiagnosticListenerName = "SqlClientDiagnosticListener";
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
+#if NETFRAMEWORK
+ private readonly SqlEventSourceListener sqlEventSourceListener;
+#endif
///
/// Initializes a new instance of the class.
@@ -49,12 +52,19 @@ public SqlClientInstrumentation(ActivitySourceAdapter activitySource, SqlClientI
listener => listener.Name == SqlClientDiagnosticListenerName,
null);
this.diagnosticSourceSubscriber.Subscribe();
+
+#if NETFRAMEWORK
+ this.sqlEventSourceListener = new SqlEventSourceListener(options);
+#endif
}
///
public void Dispose()
{
this.diagnosticSourceSubscriber?.Dispose();
+#if NETFRAMEWORK
+ this.sqlEventSourceListener?.Dispose();
+#endif
}
}
}
diff --git a/src/OpenTelemetry/Instrumentation/InstrumentationEventSource.cs b/src/OpenTelemetry/Instrumentation/InstrumentationEventSource.cs
index f2ce6d4b0c6..3637d68a509 100644
--- a/src/OpenTelemetry/Instrumentation/InstrumentationEventSource.cs
+++ b/src/OpenTelemetry/Instrumentation/InstrumentationEventSource.cs
@@ -67,16 +67,16 @@ public void UnknownErrorProcessingEvent(string handlerName, string eventName, Ex
this.UnknownErrorProcessingEvent(handlerName, eventName, ToInvariantString(ex));
}
- [Event(4, Message = "Unknown error processing event '{0}' from handler '{1}', Exception: {2}", Level = EventLevel.Error)]
+ [Event(4, Message = "Unknown error processing event '{1}' from handler '{0}', Exception: {2}", Level = EventLevel.Error)]
public void UnknownErrorProcessingEvent(string handlerName, string eventName, string ex)
{
this.WriteEvent(4, handlerName, eventName, ex);
}
- [Event(5, Message = "Payload is NULL in '{0}' callback. Span will not be recorded.", Level = EventLevel.Warning)]
- public void NullPayload(string eventName)
+ [Event(5, Message = "Payload is NULL in event '{1}' from handler '{0}', span will not be recorded.", Level = EventLevel.Warning)]
+ public void NullPayload(string handlerName, string eventName)
{
- this.WriteEvent(5, eventName);
+ this.WriteEvent(5, handlerName, eventName);
}
[Event(6, Message = "Request is filtered out.", Level = EventLevel.Verbose)]
@@ -100,6 +100,12 @@ public void ExceptionInitializingInstrumentation(string instrumentationType, str
this.WriteEvent(7, instrumentationType, ex);
}
+ [Event(8, Message = "Payload is invalid in event '{1}' from handler '{0}', span will not be recorded.", Level = EventLevel.Warning)]
+ public void InvalidPayload(string handlerName, string eventName)
+ {
+ this.WriteEvent(8, handlerName, eventName);
+ }
+
///
/// Returns a culture-independent string representation of the given object,
/// appropriate for diagnostics tracing.
diff --git a/test/OpenTelemetry.Instrumentation.Dependencies.Tests/HttpWebRequestTests.Basic.netfx.cs b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/HttpWebRequestTests.Basic.netfx.cs
index cf89f21066f..ea3b3f59076 100644
--- a/test/OpenTelemetry.Instrumentation.Dependencies.Tests/HttpWebRequestTests.Basic.netfx.cs
+++ b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/HttpWebRequestTests.Basic.netfx.cs
@@ -165,16 +165,8 @@ public async Task HttpDependenciesInstrumentationBacksOffIfAlreadyInstrumented()
request.Headers.Add("traceparent", "00-0123456789abcdef0123456789abcdef-0123456789abcdef-01");
- using (var activityListener = new ActivityListener
- {
- ShouldListenTo = (activitySource) => activitySource.Name == HttpWebRequestActivitySource.ActivitySourceName,
- })
- {
- ActivitySource.AddActivityListener(activityListener);
-
- using var c = new HttpClient();
- await c.SendAsync(request);
- }
+ using var c = new HttpClient();
+ await c.SendAsync(request);
Assert.Equal(0, activityProcessor.Invocations.Count);
}
@@ -193,16 +185,8 @@ public async Task HttpDependenciesInstrumentationFiltersOutRequests()
arg1 is HttpWebRequest request &&
request.RequestUri.OriginalString.Contains(this.url)));
- using (var activityListener = new ActivityListener
- {
- ShouldListenTo = (activitySource) => activitySource.Name == HttpWebRequestActivitySource.ActivitySourceName,
- })
- {
- ActivitySource.AddActivityListener(activityListener);
-
- using var c = new HttpClient();
- await c.GetAsync(this.url);
- }
+ using var c = new HttpClient();
+ await c.GetAsync(this.url);
Assert.Equal(0, spanProcessor.Invocations.Count);
}
diff --git a/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SkipUnlessEnvVarFoundTheoryAttribute.cs b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SkipUnlessEnvVarFoundTheoryAttribute.cs
new file mode 100644
index 00000000000..5bb7169e567
--- /dev/null
+++ b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SkipUnlessEnvVarFoundTheoryAttribute.cs
@@ -0,0 +1,31 @@
+//
+// 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.
+//
+using System;
+using Xunit;
+
+namespace OpenTelemetry.Instrumentation.Dependencies.Tests
+{
+ public class SkipUnlessEnvVarFoundTheoryAttribute : TheoryAttribute
+ {
+ public SkipUnlessEnvVarFoundTheoryAttribute(string environmentVariable)
+ {
+ if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(environmentVariable, EnvironmentVariableTarget.Machine)))
+ {
+ this.Skip = $"Skipped because {environmentVariable} environment variable was not configured.";
+ }
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlClientTests.cs b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlClientTests.cs
index 4f4b51b28c8..6d64b914969 100644
--- a/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlClientTests.cs
+++ b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlClientTests.cs
@@ -117,8 +117,8 @@ public void SqlClientCallsAreCollectedSuccessfully(
Assert.Null(span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.StatusDescriptionKey).Value);
Assert.Equal("sql", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.ComponentKey).Value);
- Assert.Equal("sql", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseTypeKey).Value);
- Assert.Equal("master", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseInstanceKey).Value);
+ Assert.Equal(SqlClientDiagnosticListener.MicrosoftSqlServerDatabaseSystemName, span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseSystemKey).Value);
+ Assert.Equal("master", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseNameKey).Value);
switch (commandType)
{
@@ -205,8 +205,8 @@ public void SqlClientErrorsAreCollectedSuccessfully(string beforeCommand, string
Assert.Equal("Unknown", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.StatusCodeKey).Value);
Assert.Equal("Boom!", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.StatusDescriptionKey).Value);
Assert.Equal("sql", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.ComponentKey).Value);
- Assert.Equal("sql", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseTypeKey).Value);
- Assert.Equal("master", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseInstanceKey).Value);
+ Assert.Equal(SqlClientDiagnosticListener.MicrosoftSqlServerDatabaseSystemName, span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseSystemKey).Value);
+ Assert.Equal("master", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseNameKey).Value);
Assert.Equal("SP_GetOrders", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.DatabaseStatementKey).Value);
Assert.Equal("(localdb)\\MSSQLLocalDB", span.Tags.FirstOrDefault(i => i.Key == SpanAttributeConstants.PeerServiceKey).Value);
}
diff --git a/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlEventSourceTests.netfx.cs b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlEventSourceTests.netfx.cs
new file mode 100644
index 00000000000..4936adda95e
--- /dev/null
+++ b/test/OpenTelemetry.Instrumentation.Dependencies.Tests/SqlEventSourceTests.netfx.cs
@@ -0,0 +1,244 @@
+//
+// 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.
+//
+#if NETFRAMEWORK
+using System;
+using System.Data;
+using System.Data.SqlClient;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Linq;
+using System.Threading.Tasks;
+using Moq;
+using OpenTelemetry.Instrumentation.Dependencies.Implementation;
+using OpenTelemetry.Trace;
+using OpenTelemetry.Trace.Configuration;
+using OpenTelemetry.Trace.Export;
+using Xunit;
+
+namespace OpenTelemetry.Instrumentation.Dependencies.Tests
+{
+ public class SqlEventSourceTests
+ {
+ /*
+ To run the integration tests, set the ot.SqlConnectionString machine-level environment variable to a valid Sql Server connection string.
+
+ To use Docker...
+ 1) Run: docker run -d --name sql2019 -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Pass@word" -p 5433:1433 mcr.microsoft.com/mssql/server:2019-latest
+ 2) Set ot.SqlConnectionString as: Data Source=127.0.0.1,5433; User ID=sa; Password=Pass@word
+ */
+
+ private const string SqlConnectionStringEnvVarName = "ot.SqlConnectionString";
+ private static readonly string SqlConnectionString = Environment.GetEnvironmentVariable(SqlConnectionStringEnvVarName, EnvironmentVariableTarget.Machine);
+
+ [Trait("CategoryName", "SqlIntegrationTests")]
+ [SkipUnlessEnvVarFoundTheory(SqlConnectionStringEnvVarName)]
+ [InlineData(CommandType.Text, "select 1/1", false)]
+ [InlineData(CommandType.Text, "select 1/0", false, true)]
+ [InlineData(CommandType.StoredProcedure, "sp_who", false)]
+ [InlineData(CommandType.StoredProcedure, "sp_who", true)]
+ public async Task SuccessfulCommandTest(CommandType commandType, string commandText, bool captureText, bool isFailure = false)
+ {
+ var activityProcessor = new Mock();
+ using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
+ {
+ b.AddProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
+ b.AddSqlClientDependencyInstrumentation(options =>
+ {
+ options.CaptureStoredProcedureCommandName = captureText;
+ });
+ });
+
+ using SqlConnection sqlConnection = new SqlConnection(SqlConnectionString);
+
+ await sqlConnection.OpenAsync().ConfigureAwait(false);
+
+ string dataSource = sqlConnection.DataSource;
+
+ sqlConnection.ChangeDatabase("master");
+
+ using SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)
+ {
+ CommandType = commandType,
+ };
+
+ try
+ {
+ await sqlCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
+ }
+ catch
+ {
+ }
+
+ Assert.Equal(2, activityProcessor.Invocations.Count);
+
+ var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];
+
+ VerifyActivityData(commandType, commandText, captureText, isFailure, dataSource, activity);
+ }
+
+ [Theory]
+ [InlineData(CommandType.Text, "select 1/1", false)]
+ [InlineData(CommandType.Text, "select 1/0", false, true)]
+ [InlineData(CommandType.StoredProcedure, "sp_who", false)]
+ [InlineData(CommandType.StoredProcedure, "sp_who", true)]
+ public void EventSourceFakeTests(CommandType commandType, string commandText, bool captureText, bool isFailure = false, int sqlExceptionNumber = 0)
+ {
+ using FakeBehavingSqlEventSource fakeSqlEventSource = new FakeBehavingSqlEventSource();
+
+ var activityProcessor = new Mock();
+ using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
+ {
+ b.AddProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
+ b.AddSqlClientDependencyInstrumentation(options =>
+ {
+ options.CaptureStoredProcedureCommandName = captureText;
+ });
+ });
+
+ int objectId = Guid.NewGuid().GetHashCode();
+
+ fakeSqlEventSource.WriteBeginExecuteEvent(objectId, "127.0.0.1", "master", commandType == CommandType.StoredProcedure ? commandText : string.Empty);
+
+ // success is stored in the first bit in compositeState 0b001
+ int successFlag = !isFailure ? 1 : 0;
+
+ // isSqlException is stored in the second bit in compositeState 0b010
+ int isSqlExceptionFlag = sqlExceptionNumber > 0 ? 2 : 0;
+
+ // synchronous state is stored in the third bit in compositeState 0b100
+ int synchronousFlag = false ? 4 : 0;
+
+ int compositeState = successFlag | isSqlExceptionFlag | synchronousFlag;
+
+ fakeSqlEventSource.WriteEndExecuteEvent(objectId, compositeState, sqlExceptionNumber);
+
+ Assert.Equal(2, activityProcessor.Invocations.Count);
+
+ var activity = (Activity)activityProcessor.Invocations[1].Arguments[0];
+
+ VerifyActivityData(commandType, commandText, captureText, isFailure, "127.0.0.1", activity);
+ }
+
+ [Fact]
+ public void EventSourceFakeUnknownEventWithNullPayloadTest()
+ {
+ using FakeMisbehavingSqlEventSource fakeSqlEventSource = new FakeMisbehavingSqlEventSource();
+
+ var activityProcessor = new Mock();
+ using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
+ {
+ b.AddProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
+ b.AddSqlClientDependencyInstrumentation();
+ });
+
+ fakeSqlEventSource.WriteUnknownEventWithNullPayload();
+
+ Assert.Equal(0, activityProcessor.Invocations.Count);
+ }
+
+ [Fact]
+ public void EventSourceFakeInvalidPayloadTest()
+ {
+ using FakeMisbehavingSqlEventSource fakeSqlEventSource = new FakeMisbehavingSqlEventSource();
+
+ var activityProcessor = new Mock();
+ using var shutdownSignal = OpenTelemetrySdk.EnableOpenTelemetry(b =>
+ {
+ b.AddProcessorPipeline(c => c.AddProcessor(ap => activityProcessor.Object));
+ b.AddSqlClientDependencyInstrumentation();
+ });
+
+ fakeSqlEventSource.WriteBeginExecuteEvent("arg1");
+
+ fakeSqlEventSource.WriteEndExecuteEvent("arg1", "arg2", "arg3", "arg4");
+
+ Assert.Equal(0, activityProcessor.Invocations.Count);
+ }
+
+ private static void VerifyActivityData(CommandType commandType, string commandText, bool captureText, bool isFailure, string dataSource, Activity activity)
+ {
+ Assert.Equal("master", activity.DisplayName);
+ Assert.Equal(ActivityKind.Client, activity.Kind);
+ Assert.Equal("sql", activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.ComponentKey).Value);
+ Assert.Equal(SqlClientDiagnosticListener.MicrosoftSqlServerDatabaseSystemName, activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.DatabaseSystemKey).Value);
+ Assert.Equal(dataSource, activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.PeerServiceKey).Value);
+ Assert.Equal("master", activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.DatabaseNameKey).Value);
+ Assert.Equal(commandType.ToString(), activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.DatabaseStatementTypeKey).Value);
+ if (commandType == CommandType.StoredProcedure)
+ {
+ if (captureText)
+ {
+ Assert.Equal(commandText, activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.DatabaseStatementKey).Value);
+ }
+ else
+ {
+ Assert.DoesNotContain(activity.Tags, t => t.Key == SpanAttributeConstants.DatabaseStatementKey);
+ }
+ }
+
+ if (!isFailure)
+ {
+ Assert.Equal("Ok", activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value);
+ }
+ else
+ {
+ Assert.Equal("Unknown", activity.Tags.FirstOrDefault(t => t.Key == SpanAttributeConstants.StatusCodeKey).Value);
+ Assert.Contains(activity.Tags, t => t.Key == SpanAttributeConstants.StatusDescriptionKey);
+ }
+ }
+
+ [EventSource(Name = SqlEventSourceListener.AdoNetEventSourceName + "-FakeFriendly")]
+ private class FakeBehavingSqlEventSource : EventSource
+ {
+ [Event(SqlEventSourceListener.BeginExecuteEventId)]
+ public void WriteBeginExecuteEvent(int objectId, string dataSource, string databaseName, string commandText)
+ {
+ this.WriteEvent(SqlEventSourceListener.BeginExecuteEventId, objectId, dataSource, databaseName, commandText);
+ }
+
+ [Event(SqlEventSourceListener.EndExecuteEventId)]
+ public void WriteEndExecuteEvent(int objectId, int compositeState, int sqlExceptionNumber)
+ {
+ this.WriteEvent(SqlEventSourceListener.EndExecuteEventId, objectId, compositeState, sqlExceptionNumber);
+ }
+ }
+
+ [EventSource(Name = SqlEventSourceListener.AdoNetEventSourceName + "-FakeEvil")]
+ private class FakeMisbehavingSqlEventSource : EventSource
+ {
+ [Event(SqlEventSourceListener.BeginExecuteEventId)]
+ public void WriteBeginExecuteEvent(string arg1)
+ {
+ this.WriteEvent(SqlEventSourceListener.BeginExecuteEventId, arg1);
+ }
+
+ [Event(SqlEventSourceListener.EndExecuteEventId)]
+ public void WriteEndExecuteEvent(string arg1, string arg2, string arg3, string arg4)
+ {
+ this.WriteEvent(SqlEventSourceListener.EndExecuteEventId, arg1, arg2, arg3, arg4);
+ }
+
+ [Event(3)]
+ public void WriteUnknownEventWithNullPayload()
+ {
+ object[] args = null;
+
+ this.WriteEvent(3, args);
+ }
+ }
+ }
+}
+#endif