[Instrumentation.SqlClient] Move to contrib repository (#5559)
Co-authored-by: Reiley Yang <reyang@microsoft.com>
This commit is contained in:
parent
994b890be1
commit
98a5d3e5b2
|
|
@ -77,7 +77,6 @@
|
|||
<PackageVersion Include="Grpc.AspNetCore.Server" Version="[2.59.0, 3.0)" />
|
||||
<PackageVersion Include="Grpc.Tools" Version="[2.59.0,3.0)" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="[3.11.0-beta1.23525.2]" />
|
||||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[8.0.0,)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="[8.0.0,)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="[8.0.0,)" />
|
||||
|
|
|
|||
|
|
@ -130,10 +130,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.AspNetCore", "exam
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{DE9130A4-F30A-49D7-8834-41DE3021218B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.SqlClient.Tests", "test\OpenTelemetry.Instrumentation.SqlClient.Tests\OpenTelemetry.Instrumentation.SqlClient.Tests.csproj", "{0C606039-BE0A-4EE6-B8F7-F75B41E52CB8}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.SqlClient", "src\OpenTelemetry.Instrumentation.SqlClient\OpenTelemetry.Instrumentation.SqlClient.csproj", "{41C30DC1-8C57-4E14-A83A-7635A3C7960B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.Http.Tests", "test\OpenTelemetry.Instrumentation.Http.Tests\OpenTelemetry.Instrumentation.Http.Tests.csproj", "{DE9EB7D8-9CC5-4073-90B3-9FBF685B3305}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.Http", "src\OpenTelemetry.Instrumentation.Http\OpenTelemetry.Instrumentation.Http.csproj", "{412C64D1-43D6-4E4C-8AD8-E20E63B415BD}"
|
||||
|
|
@ -436,14 +432,6 @@ Global
|
|||
{DE9130A4-F30A-49D7-8834-41DE3021218B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE9130A4-F30A-49D7-8834-41DE3021218B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DE9130A4-F30A-49D7-8834-41DE3021218B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0C606039-BE0A-4EE6-B8F7-F75B41E52CB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0C606039-BE0A-4EE6-B8F7-F75B41E52CB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0C606039-BE0A-4EE6-B8F7-F75B41E52CB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0C606039-BE0A-4EE6-B8F7-F75B41E52CB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{41C30DC1-8C57-4E14-A83A-7635A3C7960B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{41C30DC1-8C57-4E14-A83A-7635A3C7960B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{41C30DC1-8C57-4E14-A83A-7635A3C7960B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{41C30DC1-8C57-4E14-A83A-7635A3C7960B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DE9EB7D8-9CC5-4073-90B3-9FBF685B3305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DE9EB7D8-9CC5-4073-90B3-9FBF685B3305}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE9EB7D8-9CC5-4073-90B3-9FBF685B3305}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
|
|||
|
|
@ -53,8 +53,6 @@ libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/ma
|
|||
[Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
|
||||
* HTTP clients: [System.Net.Http.HttpClient and
|
||||
System.Net.HttpWebRequest](./src/OpenTelemetry.Instrumentation.Http/README.md)
|
||||
* SQL clients: [Microsoft.Data.SqlClient and
|
||||
System.Data.SqlClient](./src/OpenTelemetry.Instrumentation.SqlClient/README.md)
|
||||
|
||||
Here are the [exporter
|
||||
libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#exporter-library):
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@
|
|||
|
||||
* Unstable:
|
||||
* `OpenTelemetry.Instrumentation.GrpcNetClient` (`Instrumentation.GrpcNetClient-`)
|
||||
* `OpenTelemetry.Instrumentation.SqlClient` (`Instrumentation.SqlClient-`)
|
||||
|
||||
* As of the `1.9.0` release cycle instrumentation packages and core
|
||||
unstable packages always depend on the stable versions of core
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ the library they instrument, and steps for enabling them.
|
|||
* [gRPC
|
||||
client](../../../src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
|
||||
* [HTTP clients](../../../src/OpenTelemetry.Instrumentation.Http/README.md)
|
||||
* [SQL client](../../../src/OpenTelemetry.Instrumentation.SqlClient/README.md)
|
||||
|
||||
More community contributed instrumentations are available in [OpenTelemetry .NET
|
||||
Contrib](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/tree/main/src).
|
||||
|
|
@ -145,7 +144,7 @@ Writing an instrumentation library typically involves 3 steps.
|
|||
itself. For example, System.Data.SqlClient when running on .NET Framework
|
||||
happens to publish events using an `EventSource` which the [SqlClient
|
||||
instrumentation
|
||||
library](../../../src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs)
|
||||
library](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs)
|
||||
listens to in order to trigger code as Sql commands are executed. The [.NET
|
||||
Framework HttpWebRequest
|
||||
instrumentation](../../../src/OpenTelemetry.Instrumentation.Http/Implementation/HttpWebRequestActivitySource.netfx.cs)
|
||||
|
|
@ -190,7 +189,7 @@ Writing an instrumentation library typically involves 3 steps.
|
|||
on the `TracerProviderBuilder` being configured.
|
||||
|
||||
An example instrumentation using this approach is [SqlClient
|
||||
instrumentation](../../../src/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs).
|
||||
instrumentation](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs).
|
||||
|
||||
> [!WARNING]
|
||||
> The instrumentation libraries requiring state management are
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.EnableConnectionLevelAttributes.get -> bool
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.EnableConnectionLevelAttributes.set -> void
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity, string, object>
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.Enrich.set -> void
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.Filter.get -> System.Func<object, bool>
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.Filter.set -> void
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.RecordException.get -> bool
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.RecordException.set -> void
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SetDbStatementForStoredProcedure.get -> bool
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SetDbStatementForStoredProcedure.set -> void
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SetDbStatementForText.get -> bool
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SetDbStatementForText.set -> void
|
||||
OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions.SqlClientTraceInstrumentationOptions() -> void
|
||||
OpenTelemetry.Trace.TracerProviderBuilderExtensions
|
||||
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddSqlClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddSqlClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, string name, System.Action<OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions> configureSqlClientTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddSqlClientInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, System.Action<OpenTelemetry.Instrumentation.SqlClient.SqlClientTraceInstrumentationOptions> configureSqlClientTraceInstrumentationOptions) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if SIGNED
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.SqlClient.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010051c1562a090fb0c9f391012a32198b5e5d9a60e9b80fa2d7b434c9e5ccb7259bd606e66f9660676afc6692b8cdc6793d190904551d2103b7b22fa636dcbb8208839785ba402ea08fc00c8f1500ccef28bbf599aa64ffb1e1d5dc1bf3420a3777badfe697856e9d52070a50c3ea5821c80bef17ca3acffa28f89dd413f096f898")]
|
||||
#else
|
||||
[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.SqlClient.Tests")]
|
||||
#endif
|
||||
|
|
@ -1,271 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
* `ActivitySource.Version` is set to NuGet package version.
|
||||
([#5498](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5498))
|
||||
|
||||
## 1.8.0-beta.1
|
||||
|
||||
Released 2024-Apr-04
|
||||
|
||||
## 1.7.0-beta.1
|
||||
|
||||
Released 2024-Feb-09
|
||||
|
||||
* Removed support for the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable
|
||||
which toggled the use of the new conventions for the
|
||||
[server, client, and shared network attributes](https://github.com/open-telemetry/semantic-conventions/blob/v1.23.0/docs/general/attributes.md#server-client-and-shared-network-attributes).
|
||||
Now that this suite of attributes are stable, this instrumentation will only
|
||||
emit the new attributes.
|
||||
([#5270](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5270))
|
||||
* **Breaking Change**: Renamed `SqlClientInstrumentationOptions` to
|
||||
`SqlClientTraceInstrumentationOptions`.
|
||||
([#5285](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5285))
|
||||
* **Breaking Change**: Stop emitting `db.statement_type` attribute.
|
||||
This attribute was never a part of the [semantic conventions](https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/database/database-spans.md#call-level-attributes).
|
||||
([#5301](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5301))
|
||||
|
||||
## 1.6.0-beta.3
|
||||
|
||||
Released 2023-Nov-17
|
||||
|
||||
* Updated `Microsoft.Extensions.Configuration` and
|
||||
`Microsoft.Extensions.Options` package version to `8.0.0`.
|
||||
([#5051](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5051))
|
||||
|
||||
## 1.6.0-beta.2
|
||||
|
||||
Released 2023-Oct-26
|
||||
|
||||
## 1.5.1-beta.1
|
||||
|
||||
Released 2023-Jul-20
|
||||
|
||||
* The new network semantic conventions can be opted in to by setting
|
||||
the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable. This allows for a
|
||||
transition period for users to experiment with the new semantic conventions
|
||||
and adapt as necessary. The environment variable supports the following
|
||||
values:
|
||||
* `http` - emit the new, frozen (proposed for stable) networking
|
||||
attributes, and stop emitting the old experimental networking
|
||||
attributes that the instrumentation emitted previously.
|
||||
* `http/dup` - emit both the old and the frozen (proposed for stable)
|
||||
networking attributes, allowing for a more seamless transition.
|
||||
* The default behavior (in the absence of one of these values) is to continue
|
||||
emitting the same network semantic conventions that were emitted in
|
||||
`1.5.0-beta.1`.
|
||||
* Note: this option will eventually be removed after the new
|
||||
network semantic conventions are marked stable. Refer to the
|
||||
specification for more information regarding the new network
|
||||
semantic conventions for
|
||||
[spans](https://github.com/open-telemetry/semantic-conventions/blob/v1.21.0/docs/database/database-spans.md).
|
||||
([#4644](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4644))
|
||||
|
||||
## 1.5.0-beta.1
|
||||
|
||||
Released 2023-Jun-05
|
||||
|
||||
* Bumped the package version to `1.5.0-beta.1` to keep its major and minor
|
||||
version in sync with that of the core packages. This would make it more
|
||||
intuitive for users to figure out what version of core packages would work
|
||||
with a given version of this package. The pre-release identifier has also been
|
||||
changed from `rc` to `beta` as we believe this more accurately reflects the
|
||||
status of this package. We believe the `rc` identifier will be more
|
||||
appropriate as semantic conventions reach stability.
|
||||
|
||||
## 1.0.0-rc9.14
|
||||
|
||||
Released 2023-Feb-24
|
||||
|
||||
* Updated OpenTelemetry.Api.ProviderBuilderExtensions dependency to 1.4.0
|
||||
|
||||
## 1.4.0-rc9.13
|
||||
|
||||
Released 2023-Feb-10
|
||||
|
||||
## 1.0.0-rc9.12
|
||||
|
||||
Released 2023-Feb-01
|
||||
|
||||
## 1.0.0-rc9.11
|
||||
|
||||
Released 2023-Jan-09
|
||||
|
||||
## 1.0.0-rc9.10
|
||||
|
||||
Released 2022-Dec-12
|
||||
|
||||
* **Breaking change**: The same API is now exposed for `net462` and
|
||||
`netstandard2.0` targets. `SetDbStatement` has been removed. Use
|
||||
`SetDbStatementForText` to capture command text and stored procedure names on
|
||||
.NET Framework. Note: `Enrich`, `Filter`, `RecordException`, and
|
||||
`SetDbStatementForStoredProcedure` options are NOT supported on .NET
|
||||
Framework.
|
||||
([#3900](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3900))
|
||||
|
||||
* Added overloads which accept a name to the `TracerProviderBuilder`
|
||||
`AddSqlClientInstrumentation` extension to allow for more fine-grained options
|
||||
management
|
||||
([#3994](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3994))
|
||||
|
||||
## 1.0.0-rc9.9
|
||||
|
||||
Released 2022-Nov-07
|
||||
|
||||
## 1.0.0-rc9.8
|
||||
|
||||
Released 2022-Oct-17
|
||||
|
||||
* Use `Activity.Status` and `Activity.StatusDescription` properties instead of
|
||||
`OpenTelemetry.Trace.Status` and `OpenTelemetry.Trace.Status.Description`
|
||||
respectively to set activity status.
|
||||
([#3118](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3118))
|
||||
([#3751](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3751))
|
||||
* Add support for Filter option for non .NET Framework Targets
|
||||
([#3743](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3743))
|
||||
|
||||
## 1.0.0-rc9.7
|
||||
|
||||
Released 2022-Sep-29
|
||||
|
||||
## 1.0.0-rc9.6
|
||||
|
||||
Released 2022-Aug-18
|
||||
|
||||
## 1.0.0-rc9.5
|
||||
|
||||
Released 2022-Aug-02
|
||||
|
||||
* Update the `ActivitySource.Name` from "OpenTelemetry.SqlClient" to
|
||||
"OpenTelemetry.Instrumentation.SqlClient".
|
||||
([#3435](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3435))
|
||||
|
||||
## 1.0.0-rc9.4
|
||||
|
||||
Released 2022-Jun-03
|
||||
|
||||
## 1.0.0-rc9.3
|
||||
|
||||
Released 2022-Apr-15
|
||||
|
||||
* Removes .NET Framework 4.6.1. The minimum .NET Framework version supported is
|
||||
.NET 4.6.2.
|
||||
([#3190](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3190))
|
||||
|
||||
## 1.0.0-rc9.2
|
||||
|
||||
Released 2022-Apr-12
|
||||
|
||||
## 1.0.0-rc9.1
|
||||
|
||||
Released 2022-Mar-30
|
||||
|
||||
## 1.0.0-rc10 (broken. use 1.0.0-rc9.1 and newer)
|
||||
|
||||
Released 2022-Mar-04
|
||||
|
||||
## 1.0.0-rc9
|
||||
|
||||
Released 2022-Feb-02
|
||||
|
||||
## 1.0.0-rc8
|
||||
|
||||
Released 2021-Oct-08
|
||||
|
||||
* Removes .NET Framework 4.5.2 support. The minimum .NET Framework version
|
||||
supported is .NET 4.6.1.
|
||||
([#2138](https://github.com/open-telemetry/opentelemetry-dotnet/issues/2138))
|
||||
|
||||
## 1.0.0-rc7
|
||||
|
||||
Released 2021-Jul-12
|
||||
|
||||
## 1.0.0-rc6
|
||||
|
||||
Released 2021-Jun-25
|
||||
|
||||
## 1.0.0-rc5
|
||||
|
||||
Released 2021-Jun-09
|
||||
|
||||
## 1.0.0-rc4
|
||||
|
||||
Released 2021-Apr-23
|
||||
|
||||
* Instrumentation modified to depend only on the API.
|
||||
* Activities are now created with the `db.system` attribute set for usage during
|
||||
sampling.
|
||||
([#1979](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1979))
|
||||
|
||||
## 1.0.0-rc3
|
||||
|
||||
Released 2021-Mar-19
|
||||
|
||||
## 1.0.0-rc2
|
||||
|
||||
Released 2021-Jan-29
|
||||
|
||||
* Microsoft.Data.SqlClient v2.0.0 and higher is now properly instrumented on
|
||||
.NET Framework.
|
||||
([#1599](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1599))
|
||||
* SqlClientInstrumentationOptions API changes: `SetStoredProcedureCommandName`
|
||||
and `SetTextCommandContent` have been renamed to
|
||||
`SetDbStatementForStoredProcedure` and `SetDbStatementForText`. They are now
|
||||
only available on .NET Core. On .NET Framework they are replaced by a single
|
||||
`SetDbStatement` property.
|
||||
* On .NET Framework, "db.statement_type" attribute is no longer set for
|
||||
activities created by the instrumentation.
|
||||
* New setting on SqlClientInstrumentationOptions on .NET Core: `RecordException`
|
||||
can be set to instruct the instrumentation to record SqlExceptions as Activity
|
||||
[events](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md).
|
||||
([#1592](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1592))
|
||||
|
||||
## 1.0.0-rc1.1
|
||||
|
||||
Released 2020-Nov-17
|
||||
|
||||
* SqlInstrumentation sets ActivitySource to activities created outside
|
||||
ActivitySource.
|
||||
([#1515](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1515/))
|
||||
|
||||
## 0.8.0-beta.1
|
||||
|
||||
Released 2020-Nov-5
|
||||
|
||||
## 0.7.0-beta.1
|
||||
|
||||
Released 2020-Oct-16
|
||||
|
||||
* Instrumentation no longer store raw objects like `object` in
|
||||
Activity.CustomProperty. To enrich activity, use the Enrich action on the
|
||||
instrumentation.
|
||||
([#1261](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1261))
|
||||
* Span Status is populated as per new spec
|
||||
([#1313](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1313))
|
||||
|
||||
## 0.6.0-beta.1
|
||||
|
||||
Released 2020-Sep-15
|
||||
|
||||
## 0.5.0-beta.2
|
||||
|
||||
Released 2020-08-28
|
||||
|
||||
* .NET Core SqlClient instrumentation will now add the raw Command object to the
|
||||
Activity it creates
|
||||
([#1099](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1099))
|
||||
* Renamed from `AddSqlClientDependencyInstrumentation` to
|
||||
`AddSqlClientInstrumentation`
|
||||
|
||||
## 0.4.0-beta.2
|
||||
|
||||
Released 2020-07-24
|
||||
|
||||
* First beta release
|
||||
|
||||
## 0.3.0-beta
|
||||
|
||||
Released 2020-07-23
|
||||
|
||||
* Initial release
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using OpenTelemetry.Internal;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class to hold common properties used by both SqlClientDiagnosticListener on .NET Core
|
||||
/// and SqlEventSourceListener on .NET Framework.
|
||||
/// </summary>
|
||||
internal sealed class SqlActivitySourceHelper
|
||||
{
|
||||
public const string MicrosoftSqlServerDatabaseSystemName = "mssql";
|
||||
|
||||
public static readonly Assembly Assembly = typeof(SqlActivitySourceHelper).Assembly;
|
||||
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[]
|
||||
{
|
||||
new KeyValuePair<string, object>(SemanticConventions.AttributeDbSystem, MicrosoftSqlServerDatabaseSystemName),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using OpenTelemetry.Trace;
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#endif
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
[RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)]
|
||||
#endif
|
||||
internal sealed class SqlClientDiagnosticListener : ListenerHandler
|
||||
{
|
||||
public const string SqlDataBeforeExecuteCommand = "System.Data.SqlClient.WriteCommandBefore";
|
||||
public const string SqlMicrosoftBeforeExecuteCommand = "Microsoft.Data.SqlClient.WriteCommandBefore";
|
||||
|
||||
public const string SqlDataAfterExecuteCommand = "System.Data.SqlClient.WriteCommandAfter";
|
||||
public const string SqlMicrosoftAfterExecuteCommand = "Microsoft.Data.SqlClient.WriteCommandAfter";
|
||||
|
||||
public const string SqlDataWriteCommandError = "System.Data.SqlClient.WriteCommandError";
|
||||
public const string SqlMicrosoftWriteCommandError = "Microsoft.Data.SqlClient.WriteCommandError";
|
||||
|
||||
private readonly PropertyFetcher<object> commandFetcher = new("Command");
|
||||
private readonly PropertyFetcher<object> connectionFetcher = new("Connection");
|
||||
private readonly PropertyFetcher<object> dataSourceFetcher = new("DataSource");
|
||||
private readonly PropertyFetcher<object> databaseFetcher = new("Database");
|
||||
private readonly PropertyFetcher<CommandType> commandTypeFetcher = new("CommandType");
|
||||
private readonly PropertyFetcher<object> commandTextFetcher = new("CommandText");
|
||||
private readonly PropertyFetcher<Exception> exceptionFetcher = new("Exception");
|
||||
private readonly SqlClientTraceInstrumentationOptions options;
|
||||
|
||||
public SqlClientDiagnosticListener(string sourceName, SqlClientTraceInstrumentationOptions options)
|
||||
: base(sourceName)
|
||||
{
|
||||
this.options = options ?? new SqlClientTraceInstrumentationOptions();
|
||||
}
|
||||
|
||||
public override bool SupportsNullActivity => true;
|
||||
|
||||
public override void OnEventWritten(string name, object payload)
|
||||
{
|
||||
var activity = Activity.Current;
|
||||
switch (name)
|
||||
{
|
||||
case SqlDataBeforeExecuteCommand:
|
||||
case SqlMicrosoftBeforeExecuteCommand:
|
||||
{
|
||||
// SqlClient does not create an Activity. So the activity coming in here will be null or the root span.
|
||||
activity = SqlActivitySourceHelper.ActivitySource.StartActivity(
|
||||
SqlActivitySourceHelper.ActivityName,
|
||||
ActivityKind.Client,
|
||||
default(ActivityContext),
|
||||
SqlActivitySourceHelper.CreationTags);
|
||||
|
||||
if (activity == null)
|
||||
{
|
||||
// There is no listener or it decided not to sample the current request.
|
||||
return;
|
||||
}
|
||||
|
||||
_ = this.commandFetcher.TryFetch(payload, out var command);
|
||||
if (command == null)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener), name);
|
||||
activity.Stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (activity.IsAllDataRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.options.Filter?.Invoke(command) == false)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.CommandIsFilteredOut(activity.OperationName);
|
||||
activity.IsAllDataRequested = false;
|
||||
activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.CommandFilterException(ex);
|
||||
activity.IsAllDataRequested = false;
|
||||
activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
|
||||
return;
|
||||
}
|
||||
|
||||
_ = this.connectionFetcher.TryFetch(command, out var connection);
|
||||
_ = this.databaseFetcher.TryFetch(connection, out var database);
|
||||
|
||||
activity.DisplayName = (string)database;
|
||||
|
||||
_ = this.dataSourceFetcher.TryFetch(connection, out var dataSource);
|
||||
_ = this.commandTextFetcher.TryFetch(command, out var commandText);
|
||||
|
||||
activity.SetTag(SemanticConventions.AttributeDbName, (string)database);
|
||||
|
||||
this.options.AddConnectionLevelDetailsToActivity((string)dataSource, activity);
|
||||
|
||||
if (this.commandTypeFetcher.TryFetch(command, out CommandType commandType))
|
||||
{
|
||||
switch (commandType)
|
||||
{
|
||||
case CommandType.StoredProcedure:
|
||||
if (this.options.SetDbStatementForStoredProcedure)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeDbStatement, (string)commandText);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CommandType.Text:
|
||||
if (this.options.SetDbStatementForText)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeDbStatement, (string)commandText);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CommandType.TableDirect:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.options.Enrich?.Invoke(activity, "OnCustom", command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.EnrichmentException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SqlDataAfterExecuteCommand:
|
||||
case SqlMicrosoftAfterExecuteCommand:
|
||||
{
|
||||
if (activity == null)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.NullActivity(name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activity.Source != SqlActivitySourceHelper.ActivitySource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
activity.Stop();
|
||||
}
|
||||
|
||||
break;
|
||||
case SqlDataWriteCommandError:
|
||||
case SqlMicrosoftWriteCommandError:
|
||||
{
|
||||
if (activity == null)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.NullActivity(name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (activity.Source != SqlActivitySourceHelper.ActivitySource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (activity.IsAllDataRequested)
|
||||
{
|
||||
if (this.exceptionFetcher.TryFetch(payload, out Exception exception) && exception != null)
|
||||
{
|
||||
activity.SetStatus(ActivityStatusCode.Error, exception.Message);
|
||||
|
||||
if (this.options.RecordException)
|
||||
{
|
||||
activity.RecordException(exception);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.NullPayload(nameof(SqlClientDiagnosticListener), name);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
activity.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using OpenTelemetry.Internal;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
|
||||
/// <summary>
|
||||
/// EventSource events emitted from the project.
|
||||
/// </summary>
|
||||
[EventSource(Name = "OpenTelemetry-Instrumentation-SqlClient")]
|
||||
internal sealed class SqlClientInstrumentationEventSource : EventSource
|
||||
{
|
||||
public static SqlClientInstrumentationEventSource Log = new();
|
||||
|
||||
[NonEvent]
|
||||
public void UnknownErrorProcessingEvent(string handlerName, string eventName, Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
|
||||
{
|
||||
this.UnknownErrorProcessingEvent(handlerName, eventName, ex.ToInvariantString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(1, 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(1, handlerName, eventName, ex);
|
||||
}
|
||||
|
||||
[Event(2, Message = "Current Activity is NULL in the '{0}' callback. Span will not be recorded.", Level = EventLevel.Warning)]
|
||||
public void NullActivity(string eventName)
|
||||
{
|
||||
this.WriteEvent(2, eventName);
|
||||
}
|
||||
|
||||
[Event(3, 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(3, handlerName, eventName);
|
||||
}
|
||||
|
||||
[Event(4, 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(4, handlerName, eventName);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void EnrichmentException(Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
|
||||
{
|
||||
this.EnrichmentException(ex.ToInvariantString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(5, Message = "Enrichment threw exception. Exception {0}.", Level = EventLevel.Error)]
|
||||
public void EnrichmentException(string exception)
|
||||
{
|
||||
this.WriteEvent(5, exception);
|
||||
}
|
||||
|
||||
[Event(6, Message = "Command is filtered out. Activity {0}", Level = EventLevel.Verbose)]
|
||||
public void CommandIsFilteredOut(string activityName)
|
||||
{
|
||||
this.WriteEvent(6, activityName);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void CommandFilterException(Exception ex)
|
||||
{
|
||||
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
|
||||
{
|
||||
this.CommandFilterException(ex.ToInvariantString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(7, Message = "Command filter threw exception. Command will not be collected. Exception {0}.", Level = EventLevel.Error)]
|
||||
public void CommandFilterException(string exception)
|
||||
{
|
||||
this.WriteEvent(7, exception);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#if NETFRAMEWORK
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
|
||||
/// <summary>
|
||||
/// On .NET Framework, neither System.Data.SqlClient nor Microsoft.Data.SqlClient emit DiagnosticSource events.
|
||||
/// Instead they use EventSource:
|
||||
/// For System.Data.SqlClient see: <a href="https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Data/System/Data/Common/SqlEventSource.cs#L29">reference source</a>.
|
||||
/// For Microsoft.Data.SqlClient see: <a href="https://github.com/dotnet/SqlClient/blob/ac8bb3f9132e6c104dc3e307fe2d569daed0776f/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs#L15">SqlClientEventSource</a>.
|
||||
///
|
||||
/// We hook into these event sources and process their BeginExecute/EndExecute events.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that before version 2.0.0, Microsoft.Data.SqlClient used
|
||||
/// "Microsoft-AdoNet-SystemData" (same as System.Data.SqlClient), but since
|
||||
/// 2.0.0 has switched to "Microsoft.Data.SqlClient.EventSource".
|
||||
/// </remarks>
|
||||
internal sealed class SqlEventSourceListener : EventListener
|
||||
{
|
||||
internal const string AdoNetEventSourceName = "Microsoft-AdoNet-SystemData";
|
||||
internal const string MdsEventSourceName = "Microsoft.Data.SqlClient.EventSource";
|
||||
|
||||
internal const int BeginExecuteEventId = 1;
|
||||
internal const int EndExecuteEventId = 2;
|
||||
|
||||
private readonly SqlClientTraceInstrumentationOptions options;
|
||||
private EventSource adoNetEventSource;
|
||||
private EventSource mdsEventSource;
|
||||
|
||||
public SqlEventSourceListener(SqlClientTraceInstrumentationOptions options = null)
|
||||
{
|
||||
this.options = options ?? new SqlClientTraceInstrumentationOptions();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (this.adoNetEventSource != null)
|
||||
{
|
||||
this.DisableEvents(this.adoNetEventSource);
|
||||
}
|
||||
|
||||
if (this.mdsEventSource != null)
|
||||
{
|
||||
this.DisableEvents(this.mdsEventSource);
|
||||
}
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
protected override void OnEventSourceCreated(EventSource eventSource)
|
||||
{
|
||||
if (eventSource?.Name.StartsWith(AdoNetEventSourceName, StringComparison.Ordinal) == true)
|
||||
{
|
||||
this.adoNetEventSource = eventSource;
|
||||
this.EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
|
||||
}
|
||||
else if (eventSource?.Name.StartsWith(MdsEventSourceName, StringComparison.Ordinal) == true)
|
||||
{
|
||||
this.mdsEventSource = eventSource;
|
||||
this.EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.UnknownErrorProcessingEvent(nameof(SqlEventSourceListener), nameof(this.OnEventWritten), exc);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBeginExecute(EventWrittenEventArgs eventData)
|
||||
{
|
||||
/*
|
||||
Expected payload:
|
||||
[0] -> ObjectId
|
||||
[1] -> DataSource
|
||||
[2] -> Database
|
||||
[3] -> CommandText
|
||||
|
||||
Note:
|
||||
- For "Microsoft-AdoNet-SystemData" v1.0: [3] CommandText = CommandType == CommandType.StoredProcedure ? CommandText : string.Empty; (so it is set for only StoredProcedure command types)
|
||||
(https://github.com/dotnet/SqlClient/blob/v1.0.19239.1/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs#L6369)
|
||||
- For "Microsoft-AdoNet-SystemData" v1.1: [3] CommandText = sqlCommand.CommandText (so it is set for all command types)
|
||||
(https://github.com/dotnet/SqlClient/blob/v1.1.0/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs#L7459)
|
||||
- For "Microsoft.Data.SqlClient.EventSource" v2.0+: [3] CommandText = sqlCommand.CommandText (so it is set for all command types).
|
||||
(https://github.com/dotnet/SqlClient/blob/f4568ce68da21db3fe88c0e72e1287368aaa1dc8/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs#L6641)
|
||||
*/
|
||||
|
||||
if ((eventData?.Payload?.Count ?? 0) < 4)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.InvalidPayload(nameof(SqlEventSourceListener), nameof(this.OnBeginExecute));
|
||||
return;
|
||||
}
|
||||
|
||||
var activity = SqlActivitySourceHelper.ActivitySource.StartActivity(
|
||||
SqlActivitySourceHelper.ActivityName,
|
||||
ActivityKind.Client,
|
||||
default(ActivityContext),
|
||||
SqlActivitySourceHelper.CreationTags);
|
||||
|
||||
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)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeDbName, databaseName);
|
||||
|
||||
this.options.AddConnectionLevelDetailsToActivity((string)eventData.Payload[1], activity);
|
||||
|
||||
string commandText = (string)eventData.Payload[3];
|
||||
if (!string.IsNullOrEmpty(commandText) && this.options.SetDbStatementForText)
|
||||
{
|
||||
activity.SetTag(SemanticConventions.AttributeDbStatement, 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)
|
||||
{
|
||||
SqlClientInstrumentationEventSource.Log.InvalidPayload(nameof(SqlEventSourceListener), nameof(this.OnEndExecute));
|
||||
return;
|
||||
}
|
||||
|
||||
var activity = Activity.Current;
|
||||
if (activity?.Source != SqlActivitySourceHelper.ActivitySource)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (activity.IsAllDataRequested)
|
||||
{
|
||||
int compositeState = (int)eventData.Payload[1];
|
||||
if ((compositeState & 0b001) != 0b001)
|
||||
{
|
||||
if ((compositeState & 0b010) == 0b010)
|
||||
{
|
||||
var errorText = $"SqlExceptionNumber {eventData.Payload[2]} thrown.";
|
||||
activity.SetStatus(ActivityStatusCode.Error, errorText);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.SetStatus(ActivityStatusCode.Error, "Unknown Sql failure.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
activity.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TargetFrameworksForLibraries)</TargetFrameworks>
|
||||
<Description>SqlClient instrumentation for OpenTelemetry .NET</Description>
|
||||
<PackageTags>$(PackageTags);distributed-tracing</PackageTags>
|
||||
<MinVerTagPrefix>Instrumentation.SqlClient-</MinVerTagPrefix>
|
||||
<IncludeDiagnosticSourceInstrumentationHelpers>true</IncludeDiagnosticSourceInstrumentationHelpers>
|
||||
|
||||
<!-- this is temporary. will remove in future PR. -->
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--Do not run Package Baseline Validation as this package has never released a stable version.
|
||||
Remove this property once we have released a stable version.-->
|
||||
<PropertyGroup>
|
||||
<DisablePackageBaselineValidation>true</DisablePackageBaselineValidation>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\Shared\AssemblyVersionExtensions.cs" Link="Includes\AssemblyVersionExtensions.cs" />
|
||||
<Compile Include="$(RepoRoot)\src\Shared\Shims\NullableAttributes.cs" Link="Includes\Shims\NullableAttributes.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RunningDotNetPack)' != 'true'">
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api.ProviderBuilderExtensions\OpenTelemetry.Api.ProviderBuilderExtensions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Instrumentation packages when published should take a dependency only on the latest stable version of the API/SDK. -->
|
||||
<ItemGroup Condition="'$(RunningDotNetPack)' == 'true'">
|
||||
<PackageReference Include="OpenTelemetry.Api.ProviderBuilderExtensions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
# SqlClient Instrumentation for OpenTelemetry
|
||||
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.SqlClient)
|
||||
[](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.SqlClient)
|
||||
|
||||
This is an [Instrumentation
|
||||
Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library),
|
||||
which instruments
|
||||
[Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient)
|
||||
and
|
||||
[System.Data.SqlClient](https://www.nuget.org/packages/System.Data.SqlClient)
|
||||
and collects traces about database operations.
|
||||
|
||||
> [!WARNING]
|
||||
> Instrumentation is not working with `Microsoft.Data.SqlClient` v3.* due to
|
||||
the [issue](https://github.com/dotnet/SqlClient/pull/1258). It was fixed in 4.0
|
||||
and later.
|
||||
<!-- This comment is to make sure the two notes above and below are not merged -->
|
||||
> [!CAUTION]
|
||||
> This component is based on the OpenTelemetry semantic conventions for
|
||||
[traces](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md).
|
||||
These conventions are
|
||||
[Experimental](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/document-status.md),
|
||||
and hence, this package is a [pre-release](../../VERSIONING.md#pre-releases).
|
||||
Until a [stable
|
||||
version](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/telemetry-stability.md)
|
||||
is released, there can be breaking changes. You can track the progress from
|
||||
[milestones](https://github.com/open-telemetry/opentelemetry-dotnet/milestone/23).
|
||||
|
||||
## Steps to enable OpenTelemetry.Instrumentation.SqlClient
|
||||
|
||||
### Step 1: Install Package
|
||||
|
||||
Add a reference to the
|
||||
[`OpenTelemetry.Instrumentation.SqlClient`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.SqlClient)
|
||||
package. Also, add any other instrumentations & exporters you will need.
|
||||
|
||||
```shell
|
||||
dotnet add package --prerelease OpenTelemetry.Instrumentation.SqlClient
|
||||
```
|
||||
|
||||
### Step 2: Enable SqlClient Instrumentation at application startup
|
||||
|
||||
SqlClient instrumentation must be enabled at application startup.
|
||||
|
||||
The following example demonstrates adding SqlClient instrumentation to a console
|
||||
application. This example also sets up the OpenTelemetry Console exporter, which
|
||||
requires adding the package
|
||||
[`OpenTelemetry.Exporter.Console`](../OpenTelemetry.Exporter.Console/README.md)
|
||||
to the application.
|
||||
|
||||
```csharp
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation()
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For an ASP.NET Core application, adding instrumentation is typically done in the
|
||||
`ConfigureServices` of your `Startup` class. Refer to documentation for
|
||||
[OpenTelemetry.Instrumentation.AspNetCore](../OpenTelemetry.Instrumentation.AspNetCore/README.md).
|
||||
|
||||
For an ASP.NET application, adding instrumentation is typically done in the
|
||||
`Global.asax.cs`. Refer to the documentation for
|
||||
[OpenTelemetry.Instrumentation.AspNet](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.AspNet/README.md).
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
This instrumentation can be configured to change the default behavior by using
|
||||
`SqlClientTraceInstrumentationOptions`.
|
||||
|
||||
### Capturing database statements
|
||||
|
||||
The `SqlClientTraceInstrumentationOptions` class exposes two properties that can
|
||||
be used to configure how the
|
||||
[`db.statement`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#call-level-attributes)
|
||||
attribute is captured upon execution of a query but the behavior depends on the
|
||||
runtime used.
|
||||
|
||||
#### .NET and .NET Core
|
||||
|
||||
On .NET and .NET Core, two properties are available:
|
||||
`SetDbStatementForStoredProcedure` and `SetDbStatementForText`. These properties
|
||||
control capturing of `CommandType.StoredProcedure` and `CommandType.Text`
|
||||
respectively.
|
||||
|
||||
`SetDbStatementForStoredProcedure` is _true_ by default and will set
|
||||
[`db.statement`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#call-level-attributes)
|
||||
attribute to the stored procedure command name.
|
||||
|
||||
`SetDbStatementForText` is _false_ by default (to prevent accidental capture of
|
||||
sensitive data that might be part of the SQL statement text). When set to
|
||||
`true`, the instrumentation will set
|
||||
[`db.statement`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#call-level-attributes)
|
||||
attribute to the text of the SQL command being executed.
|
||||
|
||||
To disable capturing stored procedure commands use configuration like below.
|
||||
|
||||
```csharp
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
options => options.SetDbStatementForStoredProcedure = false)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
```
|
||||
|
||||
To enable capturing of `sqlCommand.CommandText` for `CommandType.Text` use the
|
||||
following configuration.
|
||||
|
||||
```csharp
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
options => options.SetDbStatementForText = true)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
```
|
||||
|
||||
#### .NET Framework
|
||||
|
||||
On .NET Framework, the `SetDbStatementForText` property controls whether or not
|
||||
this instrumentation will set the
|
||||
[`db.statement`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md#call-level-attributes)
|
||||
attribute to the text of the `SqlCommand` being executed. This could either be
|
||||
the name of a stored procedure (when `CommandType.StoredProcedure` is used) or
|
||||
the full text of a `CommandType.Text` query. `SetDbStatementForStoredProcedure`
|
||||
is ignored because on .NET Framework there is no way to determine the type of
|
||||
command being executed.
|
||||
|
||||
Since `CommandType.Text` might contain sensitive data, all SQL capturing is
|
||||
_disabled_ by default to protect against accidentally sending full query text to
|
||||
a telemetry backend. If you are only using stored procedures or have no
|
||||
sensitive data in your `sqlCommand.CommandText`, you can enable SQL capturing
|
||||
using the options like below:
|
||||
|
||||
```csharp
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
options => options.SetDbStatementForText = true)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> When using the built-in `System.Data.SqlClient` only stored procedure
|
||||
command names will ever be captured. When using the `Microsoft.Data.SqlClient`
|
||||
NuGet package (v1.1+) stored procedure command names, full query text, and other
|
||||
command text will be captured.
|
||||
|
||||
### EnableConnectionLevelAttributes
|
||||
|
||||
> [!NOTE]
|
||||
> EnableConnectionLevelAttributes is supported on all runtimes.
|
||||
|
||||
By default, `EnabledConnectionLevelAttributes` is disabled and this
|
||||
instrumentation sets the `peer.service` attribute to the
|
||||
[`DataSource`](https://docs.microsoft.com/dotnet/api/system.data.common.dbconnection.datasource)
|
||||
property of the connection. If `EnabledConnectionLevelAttributes` is enabled,
|
||||
the `DataSource` will be parsed and the server name will be sent as the
|
||||
`net.peer.name` or `net.peer.ip` attribute, the instance name will be sent as
|
||||
the `db.mssql.instance_name` attribute, and the port will be sent as the
|
||||
`net.peer.port` attribute if it is not 1433 (the default port).
|
||||
|
||||
The following example shows how to use `EnableConnectionLevelAttributes`.
|
||||
|
||||
```csharp
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
options => options.EnableConnectionLevelAttributes = true)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
```
|
||||
|
||||
### Enrich
|
||||
|
||||
> [!NOTE]
|
||||
> Enrich is supported on .NET and .NET Core runtimes only.
|
||||
|
||||
This option can be used to enrich the activity with additional information from
|
||||
the raw `SqlCommand` object. The `Enrich` action is called only when
|
||||
`activity.IsAllDataRequested` is `true`. It contains the activity itself (which
|
||||
can be enriched), the name of the event, and the actual raw object.
|
||||
|
||||
Currently there is only one event name reported, "OnCustom". The actual object
|
||||
is `Microsoft.Data.SqlClient.SqlCommand` for `Microsoft.Data.SqlClient` and
|
||||
`System.Data.SqlClient.SqlCommand` for `System.Data.SqlClient`.
|
||||
|
||||
The following code snippet shows how to add additional tags using `Enrich`.
|
||||
|
||||
```csharp
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(opt => opt.Enrich
|
||||
= (activity, eventName, rawObject) =>
|
||||
{
|
||||
if (eventName.Equals("OnCustom"))
|
||||
{
|
||||
if (rawObject is SqlCommand cmd)
|
||||
{
|
||||
activity.SetTag("db.commandTimeout", cmd.CommandTimeout);
|
||||
}
|
||||
};
|
||||
})
|
||||
.Build();
|
||||
```
|
||||
|
||||
[Processor](../../docs/trace/extending-the-sdk/README.md#processor), is the
|
||||
general extensibility point to add additional properties to any activity. The
|
||||
`Enrich` option is specific to this instrumentation, and is provided to get
|
||||
access to `SqlCommand` object.
|
||||
|
||||
### RecordException
|
||||
|
||||
> [!NOTE]
|
||||
> RecordException is supported on .NET and .NET Core runtimes only.
|
||||
|
||||
This option can be set to instruct the instrumentation to record SqlExceptions
|
||||
as Activity
|
||||
[events](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md).
|
||||
|
||||
The default value is `false` and can be changed by the code like below.
|
||||
|
||||
```csharp
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
options => options.RecordException = true)
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
```
|
||||
|
||||
### Filter
|
||||
|
||||
> [!NOTE]
|
||||
> Filter is supported on .NET and .NET Core runtimes only.
|
||||
|
||||
This option can be used to filter out activities based on the properties of the
|
||||
`SqlCommand` object being instrumented using a `Func<object, bool>`. The
|
||||
function receives an instance of the raw `SqlCommand` and should return `true`
|
||||
if the telemetry is to be collected, and `false` if it should not. The parameter
|
||||
of the Func delegate is of type `object` and needs to be cast to the appropriate
|
||||
type of `SqlCommand`, either `Microsoft.Data.SqlClient.SqlCommand` or
|
||||
`System.Data.SqlClient.SqlCommand`. The example below filters out all commands
|
||||
that are not stored procedures.
|
||||
|
||||
```csharp
|
||||
using var traceProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
opt =>
|
||||
{
|
||||
opt.Filter = cmd =>
|
||||
{
|
||||
if (cmd is SqlCommand command)
|
||||
{
|
||||
return command.CommandType == CommandType.StoredProcedure;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
})
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
{
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
* [OpenTelemetry Project](https://opentelemetry.io/)
|
||||
|
||||
* [OpenTelemetry semantic conventions for database
|
||||
calls](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/database/database-spans.md)
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#endif
|
||||
using OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient;
|
||||
|
||||
/// <summary>
|
||||
/// SqlClient instrumentation.
|
||||
/// </summary>
|
||||
internal sealed class SqlClientInstrumentation : IDisposable
|
||||
{
|
||||
internal const string SqlClientDiagnosticListenerName = "SqlClientDiagnosticListener";
|
||||
#if NET6_0_OR_GREATER
|
||||
internal const string SqlClientTrimmingUnsupportedMessage = "Trimming is not yet supported with SqlClient instrumentation.";
|
||||
#endif
|
||||
#if NETFRAMEWORK
|
||||
private readonly SqlEventSourceListener sqlEventSourceListener;
|
||||
#else
|
||||
private static readonly HashSet<string> DiagnosticSourceEvents = new()
|
||||
{
|
||||
"System.Data.SqlClient.WriteCommandBefore",
|
||||
"Microsoft.Data.SqlClient.WriteCommandBefore",
|
||||
"System.Data.SqlClient.WriteCommandAfter",
|
||||
"Microsoft.Data.SqlClient.WriteCommandAfter",
|
||||
"System.Data.SqlClient.WriteCommandError",
|
||||
"Microsoft.Data.SqlClient.WriteCommandError",
|
||||
};
|
||||
|
||||
private readonly Func<string, object, object, bool> isEnabled = (eventName, _, _)
|
||||
=> DiagnosticSourceEvents.Contains(eventName);
|
||||
|
||||
private readonly DiagnosticSourceSubscriber diagnosticSourceSubscriber;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqlClientInstrumentation"/> class.
|
||||
/// </summary>
|
||||
/// <param name="options">Configuration options for sql instrumentation.</param>
|
||||
#if NET6_0_OR_GREATER
|
||||
[RequiresUnreferencedCode(SqlClientTrimmingUnsupportedMessage)]
|
||||
#endif
|
||||
public SqlClientInstrumentation(
|
||||
SqlClientTraceInstrumentationOptions options = null)
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
this.sqlEventSourceListener = new SqlEventSourceListener(options);
|
||||
#else
|
||||
this.diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(
|
||||
name => new SqlClientDiagnosticListener(name, options),
|
||||
listener => listener.Name == SqlClientDiagnosticListenerName,
|
||||
this.isEnabled,
|
||||
SqlClientInstrumentationEventSource.Log.UnknownErrorProcessingEvent);
|
||||
this.diagnosticSourceSubscriber.Subscribe();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
this.sqlEventSourceListener?.Dispose();
|
||||
#else
|
||||
this.diagnosticSourceSubscriber?.Dispose();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenTelemetry.Trace;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient;
|
||||
|
||||
/// <summary>
|
||||
/// Options for <see cref="SqlClientInstrumentation"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For help and examples see: <a href="https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry.Instrumentation.SqlClient/README.md#advanced-configuration" />.
|
||||
/// </remarks>
|
||||
public class SqlClientTraceInstrumentationOptions
|
||||
{
|
||||
/*
|
||||
* Match...
|
||||
* protocol[ ]:[ ]serverName
|
||||
* serverName
|
||||
* serverName[ ]\[ ]instanceName
|
||||
* serverName[ ],[ ]port
|
||||
* serverName[ ]\[ ]instanceName[ ],[ ]port
|
||||
*
|
||||
* [ ] can be any number of white-space, SQL allows it for some reason.
|
||||
*
|
||||
* Optional "protocol" can be "tcp", "lpc" (shared memory), or "np" (named pipes). See:
|
||||
* https://docs.microsoft.com/troubleshoot/sql/connect/use-server-name-parameter-connection-string, and
|
||||
* https://docs.microsoft.com/dotnet/api/system.data.sqlclient.sqlconnection.connectionstring?view=dotnet-plat-ext-5.0
|
||||
*
|
||||
* In case of named pipes the Data Source string can take form of:
|
||||
* np:serverName\instanceName, or
|
||||
* np:\\serverName\pipe\pipeName, or
|
||||
* np:\\serverName\pipe\MSSQL$instanceName\pipeName - in this case a separate regex (see NamedPipeRegex below)
|
||||
* is used to extract instanceName
|
||||
*/
|
||||
private static readonly Regex DataSourceRegex = new("^(.*\\s*:\\s*\\\\{0,2})?(.*?)\\s*(?:[\\\\,]|$)\\s*(.*?)\\s*(?:,|$)\\s*(.*)$", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// In a Data Source string like "np:\\serverName\pipe\MSSQL$instanceName\pipeName" match the
|
||||
/// "pipe\MSSQL$instanceName" segment to extract instanceName if it is available.
|
||||
/// </summary>
|
||||
/// <see>
|
||||
/// <a href="https://docs.microsoft.com/previous-versions/sql/sql-server-2016/ms189307(v=sql.130)"/>
|
||||
/// </see>
|
||||
private static readonly Regex NamedPipeRegex = new("pipe\\\\MSSQL\\$(.*?)\\\\", RegexOptions.Compiled);
|
||||
|
||||
private static readonly ConcurrentDictionary<string, SqlConnectionDetails> ConnectionDetailCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the <see
|
||||
/// cref="SqlClientInstrumentation"/> should add the names of <see
|
||||
/// cref="CommandType.StoredProcedure"/> commands as the <see
|
||||
/// cref="SemanticConventions.AttributeDbStatement"/> tag. Default
|
||||
/// value: <see langword="true"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>SetDbStatementForStoredProcedure is only supported on .NET
|
||||
/// and .NET Core runtimes.</b></para>
|
||||
/// </remarks>
|
||||
public bool SetDbStatementForStoredProcedure { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the <see
|
||||
/// cref="SqlClientInstrumentation"/> should add the text of commands as
|
||||
/// the <see cref="SemanticConventions.AttributeDbStatement"/> tag.
|
||||
/// Default value: <see langword="false"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>WARNING: SetDbStatementForText will capture the raw
|
||||
/// <c>CommandText</c>. Make sure your <c>CommandText</c> property never
|
||||
/// contains any sensitive data.</b>
|
||||
/// </para>
|
||||
/// <para><b>SetDbStatementForText is supported on all runtimes.</b>
|
||||
/// <list type="bullet">
|
||||
/// <item>On .NET and .NET Core SetDbStatementForText only applies to
|
||||
/// <c>SqlCommand</c>s with <see cref="CommandType.Text"/>.</item>
|
||||
/// <item>On .NET Framework SetDbStatementForText applies to all
|
||||
/// <c>SqlCommand</c>s regardless of <see cref="CommandType"/>.
|
||||
/// <list type="bullet">
|
||||
/// <item>When using <c>System.Data.SqlClient</c> use
|
||||
/// SetDbStatementForText to capture StoredProcedure command
|
||||
/// names.</item>
|
||||
/// <item>When using <c>Microsoft.Data.SqlClient</c> use
|
||||
/// SetDbStatementForText to capture Text, StoredProcedure, and all
|
||||
/// other command text.</item>
|
||||
/// </list></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool SetDbStatementForText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not the <see
|
||||
/// cref="SqlClientInstrumentation"/> should parse the DataSource on a
|
||||
/// SqlConnection into server name, instance name, and/or port
|
||||
/// connection-level attribute tags. Default value: <see
|
||||
/// langword="false"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>EnableConnectionLevelAttributes is supported on all runtimes.</b>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The default behavior is to set the SqlConnection DataSource as the <see cref="SemanticConventions.AttributePeerService"/> tag.
|
||||
/// If enabled, SqlConnection DataSource will be parsed and the server name will be sent as the
|
||||
/// <see cref="SemanticConventions.AttributeServerAddress"/> or <see cref="SemanticConventions.AttributeServerSocketAddress"/> tag,
|
||||
/// the instance name will be sent as the <see cref="SemanticConventions.AttributeDbMsSqlInstanceName"/> tag,
|
||||
/// and the port will be sent as the <see cref="SemanticConventions.AttributeServerPort"/> tag if it is not 1433 (the default port).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public bool EnableConnectionLevelAttributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an action to enrich an <see cref="Activity"/> with the
|
||||
/// raw <c>SqlCommand</c> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Enrich is only executed on .NET and .NET Core
|
||||
/// runtimes.</b></para>
|
||||
/// The parameters passed to the enrich action are:
|
||||
/// <list type="number">
|
||||
/// <item>The <see cref="Activity"/> being enriched.</item>
|
||||
/// <item>The name of the event. Currently only <c>"OnCustom"</c> is
|
||||
/// used but more events may be added in the future.</item>
|
||||
/// <item>The raw <c>SqlCommand</c> object from which additional
|
||||
/// information can be extracted to enrich the <see
|
||||
/// cref="Activity"/>.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public Action<Activity, string, object> Enrich { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a filter function that determines whether or not to
|
||||
/// collect telemetry about a command.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Filter is only executed on .NET and .NET Core
|
||||
/// runtimes.</b></para>
|
||||
/// Notes:
|
||||
/// <list type="bullet">
|
||||
/// <item>The first parameter passed to the filter function is the raw
|
||||
/// <c>SqlCommand</c> object for the command being executed.</item>
|
||||
/// <item>The return value for the filter function is interpreted as:
|
||||
/// <list type="bullet">
|
||||
/// <item>If filter returns <see langword="true" />, the command is
|
||||
/// collected.</item>
|
||||
/// <item>If filter returns <see langword="false" /> or throws an
|
||||
/// exception the command is NOT collected.</item>
|
||||
/// </list></item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public Func<object, bool> Filter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the exception will be
|
||||
/// recorded as <see cref="ActivityEvent"/> or not. Default value: <see
|
||||
/// langword="false"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>RecordException is only supported on .NET and .NET Core
|
||||
/// runtimes.</b></para>
|
||||
/// <para>For specification details see: <see
|
||||
/// href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/exceptions/exceptions-spans.md"/>.</para>
|
||||
/// </remarks>
|
||||
public bool RecordException { get; set; }
|
||||
|
||||
internal static SqlConnectionDetails ParseDataSource(string dataSource)
|
||||
{
|
||||
Match match = DataSourceRegex.Match(dataSource);
|
||||
|
||||
string serverHostName = match.Groups[2].Value;
|
||||
string serverIpAddress = null;
|
||||
|
||||
string instanceName;
|
||||
|
||||
var uriHostNameType = Uri.CheckHostName(serverHostName);
|
||||
if (uriHostNameType == UriHostNameType.IPv4 || uriHostNameType == UriHostNameType.IPv6)
|
||||
{
|
||||
serverIpAddress = serverHostName;
|
||||
serverHostName = null;
|
||||
}
|
||||
|
||||
string maybeProtocol = match.Groups[1].Value;
|
||||
bool isNamedPipe = maybeProtocol.Length > 0 &&
|
||||
maybeProtocol.StartsWith("np", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isNamedPipe)
|
||||
{
|
||||
string pipeName = match.Groups[3].Value;
|
||||
if (pipeName.Length > 0)
|
||||
{
|
||||
var namedInstancePipeMatch = NamedPipeRegex.Match(pipeName);
|
||||
if (namedInstancePipeMatch.Success)
|
||||
{
|
||||
instanceName = namedInstancePipeMatch.Groups[1].Value;
|
||||
return new SqlConnectionDetails
|
||||
{
|
||||
ServerHostName = serverHostName,
|
||||
ServerIpAddress = serverIpAddress,
|
||||
InstanceName = instanceName,
|
||||
Port = null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new SqlConnectionDetails
|
||||
{
|
||||
ServerHostName = serverHostName,
|
||||
ServerIpAddress = serverIpAddress,
|
||||
InstanceName = null,
|
||||
Port = null,
|
||||
};
|
||||
}
|
||||
|
||||
string port;
|
||||
if (match.Groups[4].Length > 0)
|
||||
{
|
||||
instanceName = match.Groups[3].Value;
|
||||
port = match.Groups[4].Value;
|
||||
if (port == "1433")
|
||||
{
|
||||
port = null;
|
||||
}
|
||||
}
|
||||
else if (int.TryParse(match.Groups[3].Value, out int parsedPort))
|
||||
{
|
||||
port = parsedPort == 1433 ? null : match.Groups[3].Value;
|
||||
instanceName = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
instanceName = match.Groups[3].Value;
|
||||
|
||||
if (string.IsNullOrEmpty(instanceName))
|
||||
{
|
||||
instanceName = null;
|
||||
}
|
||||
|
||||
port = null;
|
||||
}
|
||||
|
||||
return new SqlConnectionDetails
|
||||
{
|
||||
ServerHostName = serverHostName,
|
||||
ServerIpAddress = serverIpAddress,
|
||||
InstanceName = instanceName,
|
||||
Port = port,
|
||||
};
|
||||
}
|
||||
|
||||
internal void AddConnectionLevelDetailsToActivity(string dataSource, Activity sqlActivity)
|
||||
{
|
||||
if (!this.EnableConnectionLevelAttributes)
|
||||
{
|
||||
sqlActivity.SetTag(SemanticConventions.AttributePeerService, dataSource);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ConnectionDetailCache.TryGetValue(dataSource, out SqlConnectionDetails connectionDetails))
|
||||
{
|
||||
connectionDetails = ParseDataSource(dataSource);
|
||||
ConnectionDetailCache.TryAdd(dataSource, connectionDetails);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionDetails.InstanceName))
|
||||
{
|
||||
sqlActivity.SetTag(SemanticConventions.AttributeDbMsSqlInstanceName, connectionDetails.InstanceName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionDetails.ServerHostName))
|
||||
{
|
||||
sqlActivity.SetTag(SemanticConventions.AttributeServerAddress, connectionDetails.ServerHostName);
|
||||
}
|
||||
else
|
||||
{
|
||||
sqlActivity.SetTag(SemanticConventions.AttributeServerSocketAddress, connectionDetails.ServerIpAddress);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionDetails.Port))
|
||||
{
|
||||
// TODO: Should we continue to emit this if the default port (1433) is being used?
|
||||
sqlActivity.SetTag(SemanticConventions.AttributeServerPort, connectionDetails.Port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SqlConnectionDetails
|
||||
{
|
||||
public string ServerHostName { get; set; }
|
||||
|
||||
public string ServerIpAddress { get; set; }
|
||||
|
||||
public string InstanceName { get; set; }
|
||||
|
||||
public string Port { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#endif
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OpenTelemetry.Instrumentation.SqlClient;
|
||||
using OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
using OpenTelemetry.Internal;
|
||||
|
||||
namespace OpenTelemetry.Trace;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods to simplify registering of dependency instrumentation.
|
||||
/// </summary>
|
||||
public static class TracerProviderBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables SqlClient instrumentation.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="TracerProviderBuilder"/> being configured.</param>
|
||||
/// <returns>The instance of <see cref="TracerProviderBuilder"/> to chain the calls.</returns>
|
||||
#if NET6_0_OR_GREATER
|
||||
[RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)]
|
||||
#endif
|
||||
public static TracerProviderBuilder AddSqlClientInstrumentation(this TracerProviderBuilder builder)
|
||||
=> AddSqlClientInstrumentation(builder, name: null, configureSqlClientTraceInstrumentationOptions: null);
|
||||
|
||||
/// <summary>
|
||||
/// Enables SqlClient instrumentation.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="TracerProviderBuilder"/> being configured.</param>
|
||||
/// <param name="configureSqlClientTraceInstrumentationOptions">Callback action for configuring <see cref="SqlClientTraceInstrumentationOptions"/>.</param>
|
||||
/// <returns>The instance of <see cref="TracerProviderBuilder"/> to chain the calls.</returns>
|
||||
#if NET6_0_OR_GREATER
|
||||
[RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)]
|
||||
#endif
|
||||
public static TracerProviderBuilder AddSqlClientInstrumentation(
|
||||
this TracerProviderBuilder builder,
|
||||
Action<SqlClientTraceInstrumentationOptions> configureSqlClientTraceInstrumentationOptions)
|
||||
=> AddSqlClientInstrumentation(builder, name: null, configureSqlClientTraceInstrumentationOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Enables SqlClient instrumentation.
|
||||
/// </summary>
|
||||
/// <param name="builder"><see cref="TracerProviderBuilder"/> being configured.</param>
|
||||
/// <param name="name">Name which is used when retrieving options.</param>
|
||||
/// <param name="configureSqlClientTraceInstrumentationOptions">Callback action for configuring <see cref="SqlClientTraceInstrumentationOptions"/>.</param>
|
||||
/// <returns>The instance of <see cref="TracerProviderBuilder"/> to chain the calls.</returns>
|
||||
#if NET6_0_OR_GREATER
|
||||
[RequiresUnreferencedCode(SqlClientInstrumentation.SqlClientTrimmingUnsupportedMessage)]
|
||||
#endif
|
||||
|
||||
public static TracerProviderBuilder AddSqlClientInstrumentation(
|
||||
this TracerProviderBuilder builder,
|
||||
string name,
|
||||
Action<SqlClientTraceInstrumentationOptions> configureSqlClientTraceInstrumentationOptions)
|
||||
{
|
||||
Guard.ThrowIfNull(builder);
|
||||
|
||||
name ??= Options.DefaultName;
|
||||
|
||||
if (configureSqlClientTraceInstrumentationOptions != null)
|
||||
{
|
||||
builder.ConfigureServices(services => services.Configure(name, configureSqlClientTraceInstrumentationOptions));
|
||||
}
|
||||
|
||||
builder.AddInstrumentation(sp =>
|
||||
{
|
||||
var sqlOptions = sp.GetRequiredService<IOptionsMonitor<SqlClientTraceInstrumentationOptions>>().Get(name);
|
||||
|
||||
return new SqlClientInstrumentation(sqlOptions);
|
||||
});
|
||||
|
||||
builder.AddSource(SqlActivitySourceHelper.ActivitySourceName);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
<TrimmerRootAssembly Include="OpenTelemetry.Instrumentation.AspNetCore" />
|
||||
<TrimmerRootAssembly Include="OpenTelemetry.Instrumentation.GrpcNetClient" />
|
||||
<TrimmerRootAssembly Include="OpenTelemetry.Instrumentation.Http" />
|
||||
<TrimmerRootAssembly Include="OpenTelemetry.Instrumentation.SqlClient" />
|
||||
<TrimmerRootAssembly Include="OpenTelemetry.Shims.OpenTracing" />
|
||||
<TrimmerRootAssembly Include="OpenTelemetry" />
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using OpenTelemetry.Internal;
|
|||
using OpenTelemetry.Tests;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests;
|
||||
namespace OpenTelemetry.Extensions.Propagators.Tests;
|
||||
|
||||
public class EventSourceTest
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
using OpenTelemetry.Tests;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests;
|
||||
|
||||
public class EventSourceTest
|
||||
{
|
||||
[Fact]
|
||||
public void EventSourceTest_SqlClientInstrumentationEventSource()
|
||||
{
|
||||
EventSourceTestHelper.MethodsAreImplementedConsistentlyWithTheirAttributes(SqlClientInstrumentationEventSource.Log);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>Unit test project for OpenTelemetry SqlClient instrumentations</Description>
|
||||
<TargetFrameworks>$(TargetFrameworksForTests)</TargetFrameworks>
|
||||
<!-- this is temporary. will remove in future PR. -->
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\EnabledOnDockerPlatformTheoryAttribute.cs" Link="Includes\EnabledOnDockerPlatformTheoryAttribute.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\SkipUnlessEnvVarFoundTheoryAttribute.cs" Link="Includes\SkipUnlessEnvVarFoundTheoryAttribute.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\EventSourceTestHelper.cs" Link="Includes\EventSourceTestHelper.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\TestEventListener.cs" Link="Includes\TestEventListener.cs" />
|
||||
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\TestSampler.cs" Link="Includes\TestSampler.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="Testcontainers.MsSql" />
|
||||
<PackageReference Include="Testcontainers.SqlEdge" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="All">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.SqlClient\OpenTelemetry.Instrumentation.SqlClient.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
|
||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.InMemory\OpenTelemetry.Exporter.InMemory.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using OpenTelemetry.Tests;
|
||||
using OpenTelemetry.Trace;
|
||||
using Testcontainers.MsSql;
|
||||
using Testcontainers.SqlEdge;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests;
|
||||
|
||||
public sealed class SqlClientIntegrationTests : IAsyncLifetime
|
||||
{
|
||||
// The Microsoft SQL Server Docker image is not compatible with ARM devices, such as Macs with Apple Silicon.
|
||||
private readonly IContainer databaseContainer = Architecture.Arm64.Equals(RuntimeInformation.ProcessArchitecture) ? new SqlEdgeBuilder().Build() : new MsSqlBuilder().Build();
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
return this.databaseContainer.StartAsync();
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return this.databaseContainer.DisposeAsync().AsTask();
|
||||
}
|
||||
|
||||
[Trait("CategoryName", "SqlIntegrationTests")]
|
||||
[EnabledOnDockerPlatformTheory(EnabledOnDockerPlatformTheoryAttribute.DockerPlatform.Linux)]
|
||||
[InlineData(CommandType.Text, "select 1/1", false)]
|
||||
[InlineData(CommandType.Text, "select 1/1", false, true)]
|
||||
[InlineData(CommandType.Text, "select 1/0", false, false, true)]
|
||||
[InlineData(CommandType.Text, "select 1/0", false, false, true, false, false)]
|
||||
[InlineData(CommandType.Text, "select 1/0", false, false, true, true, false)]
|
||||
[InlineData(CommandType.StoredProcedure, "sp_who", false)]
|
||||
[InlineData(CommandType.StoredProcedure, "sp_who", true)]
|
||||
public void SuccessfulCommandTest(
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
bool captureStoredProcedureCommandName,
|
||||
bool captureTextCommandContent = false,
|
||||
bool isFailure = false,
|
||||
bool recordException = false,
|
||||
bool shouldEnrich = true)
|
||||
{
|
||||
#if NETFRAMEWORK
|
||||
// Disable things not available on netfx
|
||||
recordException = false;
|
||||
shouldEnrich = false;
|
||||
#endif
|
||||
|
||||
var sampler = new TestSampler();
|
||||
var activities = new List<Activity>();
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.SetSampler(sampler)
|
||||
.AddInMemoryExporter(activities)
|
||||
.AddSqlClientInstrumentation(options =>
|
||||
{
|
||||
#if !NETFRAMEWORK
|
||||
options.SetDbStatementForStoredProcedure = captureStoredProcedureCommandName;
|
||||
options.SetDbStatementForText = captureTextCommandContent;
|
||||
#else
|
||||
options.SetDbStatementForText = captureStoredProcedureCommandName || captureTextCommandContent;
|
||||
#endif
|
||||
options.RecordException = recordException;
|
||||
if (shouldEnrich)
|
||||
{
|
||||
options.Enrich = SqlClientTests.ActivityEnrichment;
|
||||
}
|
||||
})
|
||||
.Build();
|
||||
|
||||
using SqlConnection sqlConnection = new SqlConnection(this.GetConnectionString());
|
||||
|
||||
sqlConnection.Open();
|
||||
|
||||
string dataSource = sqlConnection.DataSource;
|
||||
|
||||
sqlConnection.ChangeDatabase("master");
|
||||
|
||||
using SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)
|
||||
{
|
||||
CommandType = commandType,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
sqlCommand.ExecuteNonQuery();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
Assert.Single(activities);
|
||||
var activity = activities[0];
|
||||
|
||||
SqlClientTests.VerifyActivityData(commandType, commandText, captureStoredProcedureCommandName, captureTextCommandContent, isFailure, recordException, shouldEnrich, dataSource, activity);
|
||||
SqlClientTests.VerifySamplingParameters(sampler.LatestSamplingParameters);
|
||||
}
|
||||
|
||||
private string GetConnectionString()
|
||||
{
|
||||
switch (this.databaseContainer)
|
||||
{
|
||||
case SqlEdgeContainer container:
|
||||
return container.GetConnectionString();
|
||||
case MsSqlContainer container:
|
||||
return container.GetConnectionString();
|
||||
default:
|
||||
throw new InvalidOperationException($"Container type ${this.databaseContainer.GetType().Name} not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,466 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
#if !NETFRAMEWORK
|
||||
using OpenTelemetry.Tests;
|
||||
#endif
|
||||
using OpenTelemetry.Trace;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests;
|
||||
|
||||
public class SqlClientTests : IDisposable
|
||||
{
|
||||
private const string TestConnectionString = "Data Source=(localdb)\\MSSQLLocalDB;Database=master";
|
||||
|
||||
private readonly FakeSqlClientDiagnosticSource fakeSqlClientDiagnosticSource;
|
||||
|
||||
public SqlClientTests()
|
||||
{
|
||||
this.fakeSqlClientDiagnosticSource = new FakeSqlClientDiagnosticSource();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.fakeSqlClientDiagnosticSource.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SqlClient_BadArgs()
|
||||
{
|
||||
TracerProviderBuilder builder = null;
|
||||
Assert.Throws<ArgumentNullException>(() => builder.AddSqlClientInstrumentation());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SqlClient_NamedOptions()
|
||||
{
|
||||
int defaultExporterOptionsConfigureOptionsInvocations = 0;
|
||||
int namedExporterOptionsConfigureOptionsInvocations = 0;
|
||||
|
||||
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.Configure<SqlClientTraceInstrumentationOptions>(o => defaultExporterOptionsConfigureOptionsInvocations++);
|
||||
|
||||
services.Configure<SqlClientTraceInstrumentationOptions>("Instrumentation2", o => namedExporterOptionsConfigureOptionsInvocations++);
|
||||
})
|
||||
.AddSqlClientInstrumentation()
|
||||
.AddSqlClientInstrumentation("Instrumentation2", configureSqlClientTraceInstrumentationOptions: null)
|
||||
.Build();
|
||||
|
||||
Assert.Equal(1, defaultExporterOptionsConfigureOptionsInvocations);
|
||||
Assert.Equal(1, namedExporterOptionsConfigureOptionsInvocations);
|
||||
}
|
||||
|
||||
// DiagnosticListener-based instrumentation is only available on .NET Core
|
||||
#if !NETFRAMEWORK
|
||||
[Theory]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataAfterExecuteCommand, CommandType.StoredProcedure, "SP_GetOrders", true, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataAfterExecuteCommand, CommandType.StoredProcedure, "SP_GetOrders", true, false, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataAfterExecuteCommand, CommandType.Text, "select * from sys.databases", true, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataAfterExecuteCommand, CommandType.Text, "select * from sys.databases", true, false, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftAfterExecuteCommand, CommandType.StoredProcedure, "SP_GetOrders", false, true)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftAfterExecuteCommand, CommandType.StoredProcedure, "SP_GetOrders", false, true, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftAfterExecuteCommand, CommandType.Text, "select * from sys.databases", false, true)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftAfterExecuteCommand, CommandType.Text, "select * from sys.databases", false, true, false)]
|
||||
public void SqlClientCallsAreCollectedSuccessfully(
|
||||
string beforeCommand,
|
||||
string afterCommand,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
bool captureStoredProcedureCommandName,
|
||||
bool captureTextCommandContent,
|
||||
bool shouldEnrich = true)
|
||||
{
|
||||
using var sqlConnection = new SqlConnection(TestConnectionString);
|
||||
using var sqlCommand = sqlConnection.CreateCommand();
|
||||
|
||||
var activities = new List<Activity>();
|
||||
using (Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
(opt) =>
|
||||
{
|
||||
opt.SetDbStatementForText = captureTextCommandContent;
|
||||
opt.SetDbStatementForStoredProcedure = captureStoredProcedureCommandName;
|
||||
if (shouldEnrich)
|
||||
{
|
||||
opt.Enrich = ActivityEnrichment;
|
||||
}
|
||||
})
|
||||
.AddInMemoryExporter(activities)
|
||||
.Build())
|
||||
{
|
||||
var operationId = Guid.NewGuid();
|
||||
sqlCommand.CommandType = commandType;
|
||||
sqlCommand.CommandText = commandText;
|
||||
|
||||
var beforeExecuteEventData = new
|
||||
{
|
||||
OperationId = operationId,
|
||||
Command = sqlCommand,
|
||||
Timestamp = (long?)1000000L,
|
||||
};
|
||||
|
||||
this.fakeSqlClientDiagnosticSource.Write(
|
||||
beforeCommand,
|
||||
beforeExecuteEventData);
|
||||
|
||||
var afterExecuteEventData = new
|
||||
{
|
||||
OperationId = operationId,
|
||||
Command = sqlCommand,
|
||||
Timestamp = 2000000L,
|
||||
};
|
||||
|
||||
this.fakeSqlClientDiagnosticSource.Write(
|
||||
afterCommand,
|
||||
afterExecuteEventData);
|
||||
}
|
||||
|
||||
Assert.Single(activities);
|
||||
var activity = activities[0];
|
||||
|
||||
VerifyActivityData(
|
||||
sqlCommand.CommandType,
|
||||
sqlCommand.CommandText,
|
||||
captureStoredProcedureCommandName,
|
||||
captureTextCommandContent,
|
||||
false,
|
||||
false,
|
||||
shouldEnrich,
|
||||
sqlConnection.DataSource,
|
||||
activity);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataWriteCommandError)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataWriteCommandError, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand, SqlClientDiagnosticListener.SqlDataWriteCommandError, false, true)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftWriteCommandError)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftWriteCommandError, false)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand, SqlClientDiagnosticListener.SqlMicrosoftWriteCommandError, false, true)]
|
||||
public void SqlClientErrorsAreCollectedSuccessfully(string beforeCommand, string errorCommand, bool shouldEnrich = true, bool recordException = false)
|
||||
{
|
||||
using var sqlConnection = new SqlConnection(TestConnectionString);
|
||||
using var sqlCommand = sqlConnection.CreateCommand();
|
||||
|
||||
var activities = new List<Activity>();
|
||||
using (Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(options =>
|
||||
{
|
||||
options.RecordException = recordException;
|
||||
if (shouldEnrich)
|
||||
{
|
||||
options.Enrich = ActivityEnrichment;
|
||||
}
|
||||
})
|
||||
.AddInMemoryExporter(activities)
|
||||
.Build())
|
||||
{
|
||||
var operationId = Guid.NewGuid();
|
||||
sqlCommand.CommandText = "SP_GetOrders";
|
||||
sqlCommand.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
var beforeExecuteEventData = new
|
||||
{
|
||||
OperationId = operationId,
|
||||
Command = sqlCommand,
|
||||
Timestamp = (long?)1000000L,
|
||||
};
|
||||
|
||||
this.fakeSqlClientDiagnosticSource.Write(
|
||||
beforeCommand,
|
||||
beforeExecuteEventData);
|
||||
|
||||
var commandErrorEventData = new
|
||||
{
|
||||
OperationId = operationId,
|
||||
Command = sqlCommand,
|
||||
Exception = new Exception("Boom!"),
|
||||
Timestamp = 2000000L,
|
||||
};
|
||||
|
||||
this.fakeSqlClientDiagnosticSource.Write(
|
||||
errorCommand,
|
||||
commandErrorEventData);
|
||||
}
|
||||
|
||||
Assert.Single(activities);
|
||||
var activity = activities[0];
|
||||
|
||||
VerifyActivityData(
|
||||
sqlCommand.CommandType,
|
||||
sqlCommand.CommandText,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
recordException,
|
||||
shouldEnrich,
|
||||
sqlConnection.DataSource,
|
||||
activity);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlDataBeforeExecuteCommand)]
|
||||
[InlineData(SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand)]
|
||||
public void SqlClientCreatesActivityWithDbSystem(
|
||||
string beforeCommand)
|
||||
{
|
||||
using var sqlConnection = new SqlConnection(TestConnectionString);
|
||||
using var sqlCommand = sqlConnection.CreateCommand();
|
||||
|
||||
var sampler = new TestSampler
|
||||
{
|
||||
SamplingAction = _ => new SamplingResult(SamplingDecision.Drop),
|
||||
};
|
||||
using (Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation()
|
||||
.SetSampler(sampler)
|
||||
.Build())
|
||||
{
|
||||
this.fakeSqlClientDiagnosticSource.Write(beforeCommand, new { });
|
||||
}
|
||||
|
||||
VerifySamplingParameters(sampler.LatestSamplingParameters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldCollectTelemetryWhenFilterEvaluatesToTrue()
|
||||
{
|
||||
var activities = this.RunCommandWithFilter(
|
||||
cmd =>
|
||||
{
|
||||
cmd.CommandText = "select 2";
|
||||
},
|
||||
cmd =>
|
||||
{
|
||||
if (cmd is SqlCommand command)
|
||||
{
|
||||
return command.CommandText == "select 2";
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Assert.Single(activities);
|
||||
Assert.True(activities[0].IsAllDataRequested);
|
||||
Assert.True(activities[0].ActivityTraceFlags.HasFlag(ActivityTraceFlags.Recorded));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotCollectTelemetryWhenFilterEvaluatesToFalse()
|
||||
{
|
||||
var activities = this.RunCommandWithFilter(
|
||||
cmd =>
|
||||
{
|
||||
cmd.CommandText = "select 1";
|
||||
},
|
||||
cmd =>
|
||||
{
|
||||
if (cmd is SqlCommand command)
|
||||
{
|
||||
return command.CommandText == "select 2";
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
Assert.Empty(activities);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotCollectTelemetryAndShouldNotPropagateExceptionWhenFilterThrowsException()
|
||||
{
|
||||
var activities = this.RunCommandWithFilter(
|
||||
cmd =>
|
||||
{
|
||||
cmd.CommandText = "select 1";
|
||||
},
|
||||
cmd => throw new InvalidOperationException("foobar"));
|
||||
|
||||
Assert.Empty(activities);
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void VerifyActivityData(
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
bool captureStoredProcedureCommandName,
|
||||
bool captureTextCommandContent,
|
||||
bool isFailure,
|
||||
bool recordException,
|
||||
bool shouldEnrich,
|
||||
string dataSource,
|
||||
Activity activity)
|
||||
{
|
||||
Assert.Equal("master", activity.DisplayName);
|
||||
Assert.Equal(ActivityKind.Client, activity.Kind);
|
||||
|
||||
if (!isFailure)
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
}
|
||||
else
|
||||
{
|
||||
var status = activity.GetStatus();
|
||||
Assert.Equal(ActivityStatusCode.Error, activity.Status);
|
||||
Assert.NotNull(activity.StatusDescription);
|
||||
|
||||
if (recordException)
|
||||
{
|
||||
var events = activity.Events.ToList();
|
||||
Assert.Single(events);
|
||||
|
||||
Assert.Equal(SemanticConventions.AttributeExceptionEventName, events[0].Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Empty(activity.Events);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldEnrich)
|
||||
{
|
||||
Assert.NotEmpty(activity.Tags.Where(tag => tag.Key == "enriched"));
|
||||
Assert.Equal("yes", activity.Tags.Where(tag => tag.Key == "enriched").FirstOrDefault().Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Empty(activity.Tags.Where(tag => tag.Key == "enriched"));
|
||||
}
|
||||
|
||||
Assert.Equal(SqlActivitySourceHelper.MicrosoftSqlServerDatabaseSystemName, activity.GetTagValue(SemanticConventions.AttributeDbSystem));
|
||||
Assert.Equal("master", activity.GetTagValue(SemanticConventions.AttributeDbName));
|
||||
|
||||
switch (commandType)
|
||||
{
|
||||
case CommandType.StoredProcedure:
|
||||
if (captureStoredProcedureCommandName)
|
||||
{
|
||||
Assert.Equal(commandText, activity.GetTagValue(SemanticConventions.AttributeDbStatement));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeDbStatement));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CommandType.Text:
|
||||
if (captureTextCommandContent)
|
||||
{
|
||||
Assert.Equal(commandText, activity.GetTagValue(SemanticConventions.AttributeDbStatement));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeDbStatement));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.Equal(dataSource, activity.GetTagValue(SemanticConventions.AttributePeerService));
|
||||
}
|
||||
|
||||
internal static void VerifySamplingParameters(SamplingParameters samplingParameters)
|
||||
{
|
||||
Assert.NotNull(samplingParameters.Tags);
|
||||
Assert.Contains(
|
||||
samplingParameters.Tags,
|
||||
kvp => kvp.Key == SemanticConventions.AttributeDbSystem
|
||||
&& (string)kvp.Value == SqlActivitySourceHelper.MicrosoftSqlServerDatabaseSystemName);
|
||||
}
|
||||
|
||||
internal static void ActivityEnrichment(Activity activity, string method, object obj)
|
||||
{
|
||||
activity.SetTag("enriched", "yes");
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case "OnCustom":
|
||||
Assert.True(obj is SqlCommand);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETFRAMEWORK
|
||||
private Activity[] RunCommandWithFilter(
|
||||
Action<SqlCommand> sqlCommandSetup,
|
||||
Func<object, bool> filter)
|
||||
{
|
||||
using var sqlConnection = new SqlConnection(TestConnectionString);
|
||||
using var sqlCommand = sqlConnection.CreateCommand();
|
||||
|
||||
var activities = new List<Activity>();
|
||||
using (Sdk.CreateTracerProviderBuilder()
|
||||
.AddSqlClientInstrumentation(
|
||||
options =>
|
||||
{
|
||||
options.Filter = filter;
|
||||
})
|
||||
.AddInMemoryExporter(activities)
|
||||
.Build())
|
||||
{
|
||||
var operationId = Guid.NewGuid();
|
||||
sqlCommandSetup(sqlCommand);
|
||||
|
||||
var beforeExecuteEventData = new
|
||||
{
|
||||
OperationId = operationId,
|
||||
Command = sqlCommand,
|
||||
Timestamp = (long?)1000000L,
|
||||
};
|
||||
|
||||
this.fakeSqlClientDiagnosticSource.Write(
|
||||
SqlClientDiagnosticListener.SqlMicrosoftBeforeExecuteCommand,
|
||||
beforeExecuteEventData);
|
||||
|
||||
var afterExecuteEventData = new
|
||||
{
|
||||
OperationId = operationId,
|
||||
Command = sqlCommand,
|
||||
Timestamp = 2000000L,
|
||||
};
|
||||
|
||||
this.fakeSqlClientDiagnosticSource.Write(
|
||||
SqlClientDiagnosticListener.SqlMicrosoftAfterExecuteCommand,
|
||||
afterExecuteEventData);
|
||||
}
|
||||
|
||||
return activities.ToArray();
|
||||
}
|
||||
#endif
|
||||
|
||||
private class FakeSqlClientDiagnosticSource : IDisposable
|
||||
{
|
||||
private readonly DiagnosticListener listener;
|
||||
|
||||
public FakeSqlClientDiagnosticSource()
|
||||
{
|
||||
this.listener = new DiagnosticListener(SqlClientInstrumentation.SqlClientDiagnosticListenerName);
|
||||
}
|
||||
|
||||
public void Write(string name, object value)
|
||||
{
|
||||
if (this.listener.IsEnabled(name))
|
||||
{
|
||||
this.listener.Write(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.listener.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics;
|
||||
using OpenTelemetry.Trace;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests;
|
||||
|
||||
public class SqlClientTraceInstrumentationOptionsTests
|
||||
{
|
||||
static SqlClientTraceInstrumentationOptionsTests()
|
||||
{
|
||||
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
|
||||
Activity.ForceDefaultIdFormat = true;
|
||||
|
||||
var listener = new ActivityListener
|
||||
{
|
||||
ShouldListenTo = _ => true,
|
||||
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
|
||||
};
|
||||
|
||||
ActivitySource.AddActivityListener(listener);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("localhost", "localhost", null, null, null)]
|
||||
[InlineData("127.0.0.1", null, "127.0.0.1", null, null)]
|
||||
[InlineData("127.0.0.1,1433", null, "127.0.0.1", null, null)]
|
||||
[InlineData("127.0.0.1, 1818", null, "127.0.0.1", null, "1818")]
|
||||
[InlineData("127.0.0.1 \\ instanceName", null, "127.0.0.1", "instanceName", null)]
|
||||
[InlineData("127.0.0.1\\instanceName, 1818", null, "127.0.0.1", "instanceName", "1818")]
|
||||
[InlineData("tcp:127.0.0.1\\instanceName, 1818", null, "127.0.0.1", "instanceName", "1818")]
|
||||
[InlineData("tcp:localhost", "localhost", null, null, null)]
|
||||
[InlineData("tcp : localhost", "localhost", null, null, null)]
|
||||
[InlineData("np : localhost", "localhost", null, null, null)]
|
||||
[InlineData("lpc:localhost", "localhost", null, null, null)]
|
||||
[InlineData("np:\\\\localhost\\pipe\\sql\\query", "localhost", null, null, null)]
|
||||
[InlineData("np : \\\\localhost\\pipe\\sql\\query", "localhost", null, null, null)]
|
||||
[InlineData("np:\\\\localhost\\pipe\\MSSQL$instanceName\\sql\\query", "localhost", null, "instanceName", null)]
|
||||
public void ParseDataSourceTests(
|
||||
string dataSource,
|
||||
string expectedServerHostName,
|
||||
string expectedServerIpAddress,
|
||||
string expectedInstanceName,
|
||||
string expectedPort)
|
||||
{
|
||||
var sqlConnectionDetails = SqlClientTraceInstrumentationOptions.ParseDataSource(dataSource);
|
||||
|
||||
Assert.NotNull(sqlConnectionDetails);
|
||||
Assert.Equal(expectedServerHostName, sqlConnectionDetails.ServerHostName);
|
||||
Assert.Equal(expectedServerIpAddress, sqlConnectionDetails.ServerIpAddress);
|
||||
Assert.Equal(expectedInstanceName, sqlConnectionDetails.InstanceName);
|
||||
Assert.Equal(expectedPort, sqlConnectionDetails.Port);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true, "localhost", "localhost", null, null, null)]
|
||||
[InlineData(true, "127.0.0.1,1433", null, "127.0.0.1", null, null)]
|
||||
[InlineData(true, "127.0.0.1,1434", null, "127.0.0.1", null, "1434")]
|
||||
[InlineData(true, "127.0.0.1\\instanceName, 1818", null, "127.0.0.1", "instanceName", "1818")]
|
||||
[InlineData(false, "localhost", "localhost", null, null, null)]
|
||||
public void SqlClientTraceInstrumentationOptions_EnableConnectionLevelAttributes(
|
||||
bool enableConnectionLevelAttributes,
|
||||
string dataSource,
|
||||
string expectedServerHostName,
|
||||
string expectedServerIpAddress,
|
||||
string expectedInstanceName,
|
||||
string expectedPort)
|
||||
{
|
||||
var source = new ActivitySource("sql-client-instrumentation");
|
||||
var activity = source.StartActivity("Test Sql Activity");
|
||||
var options = new SqlClientTraceInstrumentationOptions()
|
||||
{
|
||||
EnableConnectionLevelAttributes = enableConnectionLevelAttributes,
|
||||
};
|
||||
options.AddConnectionLevelDetailsToActivity(dataSource, activity);
|
||||
|
||||
if (!enableConnectionLevelAttributes)
|
||||
{
|
||||
Assert.Equal(expectedServerHostName, activity.GetTagValue(SemanticConventions.AttributePeerService));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedServerHostName, activity.GetTagValue(SemanticConventions.AttributeServerAddress));
|
||||
}
|
||||
|
||||
Assert.Equal(expectedServerIpAddress, activity.GetTagValue(SemanticConventions.AttributeServerSocketAddress));
|
||||
Assert.Equal(expectedInstanceName, activity.GetTagValue(SemanticConventions.AttributeDbMsSqlInstanceName));
|
||||
Assert.Equal(expectedPort, activity.GetTagValue(SemanticConventions.AttributeServerPort));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,369 +0,0 @@
|
|||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#if NETFRAMEWORK
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using OpenTelemetry.Instrumentation.SqlClient.Implementation;
|
||||
using OpenTelemetry.Tests;
|
||||
using OpenTelemetry.Trace;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Instrumentation.SqlClient.Tests;
|
||||
|
||||
public class SqlEventSourceTests
|
||||
{
|
||||
/*
|
||||
To run the integration tests, set the OTEL_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 OTEL_SQLCONNECTIONSTRING as: Data Source=127.0.0.1,5433; User ID=sa; Password=Pass@word
|
||||
*/
|
||||
|
||||
private const string SqlConnectionStringEnvVarName = "OTEL_SQLCONNECTIONSTRING";
|
||||
private static readonly string SqlConnectionString = SkipUnlessEnvVarFoundTheoryAttribute.GetEnvironmentVariable(SqlConnectionStringEnvVarName);
|
||||
|
||||
[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 exportedItems = new List<Activity>();
|
||||
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.AddSqlClientInstrumentation(options =>
|
||||
{
|
||||
options.SetDbStatementForText = captureText;
|
||||
})
|
||||
.Build();
|
||||
|
||||
using SqlConnection sqlConnection = new SqlConnection(SqlConnectionString);
|
||||
|
||||
await sqlConnection.OpenAsync();
|
||||
|
||||
string dataSource = sqlConnection.DataSource;
|
||||
|
||||
sqlConnection.ChangeDatabase("master");
|
||||
|
||||
using SqlCommand sqlCommand = new SqlCommand(commandText, sqlConnection)
|
||||
{
|
||||
CommandType = commandType,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await sqlCommand.ExecuteNonQueryAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var activity = exportedItems[0];
|
||||
|
||||
VerifyActivityData(commandText, captureText, isFailure, dataSource, activity);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(FakeBehavingAdoNetSqlEventSource), CommandType.Text, "select 1/1", false)]
|
||||
[InlineData(typeof(FakeBehavingAdoNetSqlEventSource), CommandType.Text, "select 1/0", false, true)]
|
||||
[InlineData(typeof(FakeBehavingAdoNetSqlEventSource), CommandType.StoredProcedure, "sp_who", false)]
|
||||
[InlineData(typeof(FakeBehavingAdoNetSqlEventSource), CommandType.StoredProcedure, "sp_who", true, false, 0, true)]
|
||||
[InlineData(typeof(FakeBehavingMdsSqlEventSource), CommandType.Text, "select 1/1", false)]
|
||||
[InlineData(typeof(FakeBehavingMdsSqlEventSource), CommandType.Text, "select 1/0", false, true)]
|
||||
[InlineData(typeof(FakeBehavingMdsSqlEventSource), CommandType.StoredProcedure, "sp_who", false)]
|
||||
[InlineData(typeof(FakeBehavingMdsSqlEventSource), CommandType.StoredProcedure, "sp_who", true, false, 0, true)]
|
||||
public void EventSourceFakeTests(
|
||||
Type eventSourceType,
|
||||
CommandType commandType,
|
||||
string commandText,
|
||||
bool captureText,
|
||||
bool isFailure = false,
|
||||
int sqlExceptionNumber = 0,
|
||||
bool enableConnectionLevelAttributes = false)
|
||||
{
|
||||
using IFakeBehavingSqlEventSource fakeSqlEventSource = (IFakeBehavingSqlEventSource)Activator.CreateInstance(eventSourceType);
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.AddSqlClientInstrumentation(options =>
|
||||
{
|
||||
options.SetDbStatementForText = captureText;
|
||||
options.EnableConnectionLevelAttributes = enableConnectionLevelAttributes;
|
||||
})
|
||||
.Build();
|
||||
|
||||
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);
|
||||
shutdownSignal.Dispose();
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
var activity = exportedItems[0];
|
||||
|
||||
VerifyActivityData(commandText, captureText, isFailure, "127.0.0.1", activity, enableConnectionLevelAttributes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(FakeMisbehavingAdoNetSqlEventSource))]
|
||||
[InlineData(typeof(FakeMisbehavingMdsSqlEventSource))]
|
||||
public void EventSourceFakeUnknownEventWithNullPayloadTest(Type eventSourceType)
|
||||
{
|
||||
using IFakeMisbehavingSqlEventSource fakeSqlEventSource = (IFakeMisbehavingSqlEventSource)Activator.CreateInstance(eventSourceType);
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.AddSqlClientInstrumentation()
|
||||
.Build();
|
||||
|
||||
fakeSqlEventSource.WriteUnknownEventWithNullPayload();
|
||||
|
||||
shutdownSignal.Dispose();
|
||||
|
||||
Assert.Empty(exportedItems);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(FakeMisbehavingAdoNetSqlEventSource))]
|
||||
[InlineData(typeof(FakeMisbehavingMdsSqlEventSource))]
|
||||
public void EventSourceFakeInvalidPayloadTest(Type eventSourceType)
|
||||
{
|
||||
using IFakeMisbehavingSqlEventSource fakeSqlEventSource = (IFakeMisbehavingSqlEventSource)Activator.CreateInstance(eventSourceType);
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.AddSqlClientInstrumentation()
|
||||
.Build();
|
||||
|
||||
fakeSqlEventSource.WriteBeginExecuteEvent("arg1");
|
||||
|
||||
fakeSqlEventSource.WriteEndExecuteEvent("arg1", "arg2", "arg3", "arg4");
|
||||
shutdownSignal.Dispose();
|
||||
|
||||
Assert.Empty(exportedItems);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(FakeBehavingAdoNetSqlEventSource))]
|
||||
[InlineData(typeof(FakeBehavingMdsSqlEventSource))]
|
||||
public void DefaultCaptureTextFalse(Type eventSourceType)
|
||||
{
|
||||
using IFakeBehavingSqlEventSource fakeSqlEventSource = (IFakeBehavingSqlEventSource)Activator.CreateInstance(eventSourceType);
|
||||
|
||||
var exportedItems = new List<Activity>();
|
||||
using var shutdownSignal = Sdk.CreateTracerProviderBuilder()
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.AddSqlClientInstrumentation()
|
||||
.Build();
|
||||
|
||||
int objectId = Guid.NewGuid().GetHashCode();
|
||||
|
||||
const string commandText = "TestCommandTest";
|
||||
fakeSqlEventSource.WriteBeginExecuteEvent(objectId, "127.0.0.1", "master", commandText);
|
||||
|
||||
// success is stored in the first bit in compositeState 0b001
|
||||
int successFlag = 1;
|
||||
|
||||
// isSqlException is stored in the second bit in compositeState 0b010
|
||||
int isSqlExceptionFlag = 2;
|
||||
|
||||
// synchronous state is stored in the third bit in compositeState 0b100
|
||||
int synchronousFlag = 4;
|
||||
|
||||
int compositeState = successFlag | isSqlExceptionFlag | synchronousFlag;
|
||||
|
||||
fakeSqlEventSource.WriteEndExecuteEvent(objectId, compositeState, 0);
|
||||
shutdownSignal.Dispose();
|
||||
Assert.Single(exportedItems);
|
||||
|
||||
var activity = exportedItems[0];
|
||||
|
||||
const bool captureText = false;
|
||||
VerifyActivityData(commandText, captureText, false, "127.0.0.1", activity, false);
|
||||
}
|
||||
|
||||
private static void VerifyActivityData(
|
||||
string commandText,
|
||||
bool captureText,
|
||||
bool isFailure,
|
||||
string dataSource,
|
||||
Activity activity,
|
||||
bool enableConnectionLevelAttributes = false)
|
||||
{
|
||||
Assert.Equal("master", activity.DisplayName);
|
||||
Assert.Equal(ActivityKind.Client, activity.Kind);
|
||||
Assert.Equal(SqlActivitySourceHelper.MicrosoftSqlServerDatabaseSystemName, activity.GetTagValue(SemanticConventions.AttributeDbSystem));
|
||||
|
||||
if (!enableConnectionLevelAttributes)
|
||||
{
|
||||
Assert.Equal(dataSource, activity.GetTagValue(SemanticConventions.AttributePeerService));
|
||||
}
|
||||
else
|
||||
{
|
||||
var connectionDetails = SqlClientTraceInstrumentationOptions.ParseDataSource(dataSource);
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionDetails.ServerHostName))
|
||||
{
|
||||
Assert.Equal(connectionDetails.ServerHostName, activity.GetTagValue(SemanticConventions.AttributeNetPeerName));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(connectionDetails.ServerIpAddress, activity.GetTagValue(SemanticConventions.AttributeServerSocketAddress));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionDetails.InstanceName))
|
||||
{
|
||||
Assert.Equal(connectionDetails.InstanceName, activity.GetTagValue(SemanticConventions.AttributeDbMsSqlInstanceName));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(connectionDetails.Port))
|
||||
{
|
||||
Assert.Equal(connectionDetails.Port, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal("master", activity.GetTagValue(SemanticConventions.AttributeDbName));
|
||||
|
||||
if (captureText)
|
||||
{
|
||||
Assert.Equal(commandText, activity.GetTagValue(SemanticConventions.AttributeDbStatement));
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(activity.GetTagValue(SemanticConventions.AttributeDbStatement));
|
||||
}
|
||||
|
||||
if (!isFailure)
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Unset, activity.Status);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(ActivityStatusCode.Error, activity.Status);
|
||||
Assert.NotNull(activity.StatusDescription);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable SA1201 // Elements should appear in the correct order
|
||||
|
||||
// Helper interface to be able to have single test method for multiple EventSources, want to keep it close to the event sources themselves.
|
||||
private interface IFakeBehavingSqlEventSource : IDisposable
|
||||
#pragma warning restore SA1201 // Elements should appear in the correct order
|
||||
{
|
||||
void WriteBeginExecuteEvent(int objectId, string dataSource, string databaseName, string commandText);
|
||||
|
||||
void WriteEndExecuteEvent(int objectId, int compositeState, int sqlExceptionNumber);
|
||||
}
|
||||
|
||||
private interface IFakeMisbehavingSqlEventSource : IDisposable
|
||||
{
|
||||
void WriteBeginExecuteEvent(string arg1);
|
||||
|
||||
void WriteEndExecuteEvent(string arg1, string arg2, string arg3, string arg4);
|
||||
|
||||
void WriteUnknownEventWithNullPayload();
|
||||
}
|
||||
|
||||
[EventSource(Name = SqlEventSourceListener.AdoNetEventSourceName + "-FakeFriendly")]
|
||||
private class FakeBehavingAdoNetSqlEventSource : EventSource, IFakeBehavingSqlEventSource
|
||||
{
|
||||
[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.MdsEventSourceName + "-FakeFriendly")]
|
||||
private class FakeBehavingMdsSqlEventSource : EventSource, IFakeBehavingSqlEventSource
|
||||
{
|
||||
[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 FakeMisbehavingAdoNetSqlEventSource : EventSource, IFakeMisbehavingSqlEventSource
|
||||
{
|
||||
[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);
|
||||
}
|
||||
}
|
||||
|
||||
[EventSource(Name = SqlEventSourceListener.MdsEventSourceName + "-FakeEvil")]
|
||||
private class FakeMisbehavingMdsSqlEventSource : EventSource, IFakeMisbehavingSqlEventSource
|
||||
{
|
||||
[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
|
||||
Loading…
Reference in New Issue