diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3de0d141cb..60813f1378 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,6 +26,11 @@ jobs:
dotnet-version: |
6.0.x
7.0.x
+ - name: Install SQL Server (localdb)
+ uses: potatoqualitee/mssqlsuite@v1.7
+ if: ${{ runner.os == 'Windows' }}
+ with:
+ install: localdb
- run: ./build.cmd Workflow --containers ${{ matrix.containers }}
- name: Upload logs
uses: actions/upload-artifact@v3.1.1
diff --git a/OpenTelemetry.AutoInstrumentation.proj b/OpenTelemetry.AutoInstrumentation.proj
index c8a7694b5d..3bd5be863e 100644
--- a/OpenTelemetry.AutoInstrumentation.proj
+++ b/OpenTelemetry.AutoInstrumentation.proj
@@ -13,9 +13,7 @@
-
-
-
+
diff --git a/OpenTelemetry.AutoInstrumentation.sln b/OpenTelemetry.AutoInstrumentation.sln
index 9a7a614656..8836bd721f 100644
--- a/OpenTelemetry.AutoInstrumentation.sln
+++ b/OpenTelemetry.AutoInstrumentation.sln
@@ -159,6 +159,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.Modules", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.Http.NetFramework", "test\test-applications\integrations\TestApplication.Http.NetFramework\TestApplication.Http.NetFramework.csproj", "{5D0FECF8-1954-449D-8C42-5373D91154FA}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.SqlClient.NetFramework", "test\test-applications\integrations\TestApplication.SqlClient.NetFramework\TestApplication.SqlClient.NetFramework.csproj", "{C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -751,6 +753,18 @@ Global
{5D0FECF8-1954-449D-8C42-5373D91154FA}.Release|x64.Build.0 = Release|x64
{5D0FECF8-1954-449D-8C42-5373D91154FA}.Release|x86.ActiveCfg = Release|x86
{5D0FECF8-1954-449D-8C42-5373D91154FA}.Release|x86.Build.0 = Release|x86
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Debug|Any CPU.Build.0 = Debug|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Debug|x64.ActiveCfg = Debug|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Debug|x64.Build.0 = Debug|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Debug|x86.ActiveCfg = Debug|x86
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Debug|x86.Build.0 = Debug|x86
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Release|Any CPU.ActiveCfg = Release|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Release|Any CPU.Build.0 = Release|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Release|x64.ActiveCfg = Release|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Release|x64.Build.0 = Release|x64
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Release|x86.ActiveCfg = Release|x86
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -809,6 +823,7 @@ Global
{0A950D85-E813-4CA3-8927-334BB7787372} = {00F4C92D-6652-4BD8-A334-B35D3E711BE6}
{F8C3A1FF-2333-45C8-9174-75F12526DFDD} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
{5D0FECF8-1954-449D-8C42-5373D91154FA} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
+ {C5EB9F63-DE06-4444-A0DB-08F1CAC07B42} = {E409ADD3-9574-465C-AB09-4324D205CC7C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}
diff --git a/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs b/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs
index 0d1b27c861..d28b348ddc 100644
--- a/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs
+++ b/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs
@@ -143,7 +143,7 @@ public static TracerProviderBuilder AddMySqlClientInstrumentation(TracerProvider
[MethodImpl(MethodImplOptions.NoInlining)]
public static TracerProviderBuilder AddSqlClientInstrumentation(TracerProviderBuilder builder, PluginManager pluginManager, LazyInstrumentationLoader lazyInstrumentationLoader)
{
- lazyInstrumentationLoader.Add(new SqlClientInitializer(pluginManager));
+ new SqlClientInitializer(lazyInstrumentationLoader, pluginManager);
return builder.AddSource("OpenTelemetry.SqlClient");
}
diff --git a/src/OpenTelemetry.AutoInstrumentation/Loading/Initializers/SqlClientInitializer.cs b/src/OpenTelemetry.AutoInstrumentation/Loading/Initializers/SqlClientInitializer.cs
index 2275b5bdec..c2aeb89ce0 100644
--- a/src/OpenTelemetry.AutoInstrumentation/Loading/Initializers/SqlClientInitializer.cs
+++ b/src/OpenTelemetry.AutoInstrumentation/Loading/Initializers/SqlClientInitializer.cs
@@ -15,22 +15,35 @@
//
using System;
+using System.Threading;
using OpenTelemetry.AutoInstrumentation.Plugins;
namespace OpenTelemetry.AutoInstrumentation.Loading.Initializers;
-internal class SqlClientInitializer : InstrumentationInitializer
+internal class SqlClientInitializer
{
private readonly PluginManager _pluginManager;
- public SqlClientInitializer(PluginManager pluginManager)
- : base("Microsoft.Data.SqlClient")
+ private int _initialized;
+
+ public SqlClientInitializer(LazyInstrumentationLoader lazyInstrumentationLoader, PluginManager pluginManager)
{
_pluginManager = pluginManager;
+ lazyInstrumentationLoader.Add(new GenericInitializer("Microsoft.Data.SqlClient", InitializeOnFirstCall));
+
+#if NETFRAMEWORK
+ lazyInstrumentationLoader.Add(new GenericInitializer("System.Data", InitializeOnFirstCall));
+#endif
}
- public override void Initialize(ILifespanManager lifespanManager)
+ private void InitializeOnFirstCall(ILifespanManager lifespanManager)
{
+ if (Interlocked.Exchange(ref _initialized, value: 1) != default)
+ {
+ // InitializeOnFirstCall() was already called before
+ return;
+ }
+
var instrumentationType = Type.GetType("OpenTelemetry.Instrumentation.SqlClient.SqlClientInstrumentation, OpenTelemetry.Instrumentation.SqlClient");
var options = new OpenTelemetry.Instrumentation.SqlClient.SqlClientInstrumentationOptions();
diff --git a/test/IntegrationTests/Helpers/RuntimeHelper.cs b/test/IntegrationTests/Helpers/RuntimeHelper.cs
new file mode 100644
index 0000000000..7176282cf5
--- /dev/null
+++ b/test/IntegrationTests/Helpers/RuntimeHelper.cs
@@ -0,0 +1,74 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#if NETFRAMEWORK
+
+using Microsoft.Win32;
+
+namespace IntegrationTests.Helpers;
+
+internal static class RuntimeHelper
+{
+ // https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed
+ public static string GetRuntimeVersion()
+ {
+ const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
+
+ using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey))
+ {
+ if (ndpKey != null && ndpKey.GetValue("Release") != null)
+ {
+ return CheckFor45PlusVersion((int)ndpKey.GetValue("Release"));
+ }
+
+ return null;
+ }
+
+ // Checking the version using >= enables forward compatibility.
+ static string CheckFor45PlusVersion(int releaseKey)
+ {
+#pragma warning disable SA1503 // Braces should not be omitted
+ if (releaseKey >= 533320)
+ return "4.8.1+";
+ if (releaseKey >= 528040)
+ return "4.8";
+ if (releaseKey >= 461808)
+ return "4.7.2";
+ if (releaseKey >= 461308)
+ return "4.7.1";
+ if (releaseKey >= 460798)
+ return "4.7";
+ if (releaseKey >= 394802)
+ return "4.6.2";
+ if (releaseKey >= 394254)
+ return "4.6.1";
+ if (releaseKey >= 393295)
+ return "4.6";
+ if (releaseKey >= 379893)
+ return "4.5.2";
+ if (releaseKey >= 378675)
+ return "4.5.1";
+ if (releaseKey >= 378389)
+ return "4.5";
+ // This code should never execute. A non-null release key should mean
+ // that 4.5 or later is installed.
+ return "No 4.5+";
+#pragma warning restore SA1503 // Braces should not be omitted
+ }
+ }
+}
+
+#endif
diff --git a/test/IntegrationTests/SqlClientTests.cs b/test/IntegrationTests/SqlClientMicrosoftTests.cs
similarity index 85%
rename from test/IntegrationTests/SqlClientTests.cs
rename to test/IntegrationTests/SqlClientMicrosoftTests.cs
index c072e46992..bf0fb1b50d 100644
--- a/test/IntegrationTests/SqlClientTests.cs
+++ b/test/IntegrationTests/SqlClientMicrosoftTests.cs
@@ -1,4 +1,4 @@
-//
+//
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,11 +21,11 @@
namespace IntegrationTests;
[Collection(SqlServerCollection.Name)]
-public class SqlClientTests : TestHelper
+public class SqlClientMicrosoftTests : TestHelper
{
private readonly SqlServerFixture _sqlServerFixture;
- public SqlClientTests(ITestOutputHelper output, SqlServerFixture sqlServerFixture)
+ public SqlClientMicrosoftTests(ITestOutputHelper output, SqlServerFixture sqlServerFixture)
: base("SqlClient", output)
{
_sqlServerFixture = sqlServerFixture;
diff --git a/test/IntegrationTests/SqlClientSystemDataTests.cs b/test/IntegrationTests/SqlClientSystemDataTests.cs
new file mode 100644
index 0000000000..82a18dfa65
--- /dev/null
+++ b/test/IntegrationTests/SqlClientSystemDataTests.cs
@@ -0,0 +1,59 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#if NETFRAMEWORK
+
+using IntegrationTests.Helpers;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace IntegrationTests;
+
+public class SqlClientSystemDataTests : TestHelper
+{
+ public SqlClientSystemDataTests(ITestOutputHelper output)
+ : base("SqlClient.NetFramework", output)
+ {
+ }
+
+ [IgnoreRunningOnNet481Fact]
+ [Trait("Category", "EndToEnd")]
+ public void SubmitTraces()
+ {
+ using var collector = new MockSpansCollector(Output);
+ SetExporter(collector);
+ collector.Expect("OpenTelemetry.SqlClient");
+
+ RunTestApplication();
+
+ collector.AssertExpectations();
+ }
+}
+
+public sealed class IgnoreRunningOnNet481Fact : FactAttribute
+{
+ public IgnoreRunningOnNet481Fact()
+ {
+ var netVersion = RuntimeHelper.GetRuntimeVersion();
+ if (netVersion == "4.8.1+")
+ {
+ // https://github.com/open-telemetry/opentelemetry-dotnet/issues/3901
+ Skip = "NET Framework 4.8.1 is skipped due bug.";
+ }
+ }
+}
+
+#endif
diff --git a/test/test-applications/integrations/TestApplication.Http.NetFramework/Properties/launchSettings.json b/test/test-applications/integrations/TestApplication.Http.NetFramework/Properties/launchSettings.json
new file mode 100644
index 0000000000..07eae97444
--- /dev/null
+++ b/test/test-applications/integrations/TestApplication.Http.NetFramework/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "TestApplication.Http.NetFramework": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:63944"
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/test-applications/integrations/TestApplication.SqlClient.NetFramework/Program.cs b/test/test-applications/integrations/TestApplication.SqlClient.NetFramework/Program.cs
new file mode 100644
index 0000000000..0d244258a8
--- /dev/null
+++ b/test/test-applications/integrations/TestApplication.SqlClient.NetFramework/Program.cs
@@ -0,0 +1,133 @@
+//
+// Copyright The OpenTelemetry Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+using System;
+using System.Data.SqlClient;
+using System.Threading.Tasks;
+using TestApplication.Shared;
+
+namespace TestApplication.SqlClient;
+
+public class Program
+{
+ private const string CreateCommand = "CREATE TABLE MY_TABLE ( Id int, Value1 varchar(255), Value2 varchar(255) )";
+ private const string DropCommand = "DROP TABLE MY_TABLE";
+ private const string InsertCommand = "INSERT INTO MY_TABLE VALUES ( 1, 'value1', 'value2' )";
+ private const string SelectCommand = "SELECT * FROM MY_TABLE";
+
+ public static async Task Main(string[] args)
+ {
+ ConsoleHelper.WriteSplashScreen(args);
+
+ const string connectionString = "Data Source=(localdb)\\MSSQLLocalDB;Integrated Security=True;Connect Timeout=30;TrustServerCertificate=True;";
+
+ using (var connection = new SqlConnection(connectionString))
+ {
+ ExecuteCommands(connection);
+ }
+
+ using (var connection = new SqlConnection(connectionString))
+ {
+ await ExecuteAsyncCommands(connection);
+ }
+ }
+
+ private static void ExecuteCommands(SqlConnection connection)
+ {
+ connection.Open();
+ ExecuteCreate(connection);
+ ExecuteInsert(connection);
+ ExecuteSelect(connection);
+ ExecuteDrop(connection);
+ }
+
+ private static void ExecuteCreate(SqlConnection connection)
+ {
+ ExecuteCommand(CreateCommand, connection);
+ }
+
+ private static void ExecuteInsert(SqlConnection connection)
+ {
+ ExecuteCommand(InsertCommand, connection);
+ }
+
+ private static void ExecuteSelect(SqlConnection connection)
+ {
+ ExecuteCommand(SelectCommand, connection);
+ }
+
+ private static void ExecuteDrop(SqlConnection connection)
+ {
+ ExecuteCommand(DropCommand, connection);
+ }
+
+ private static void ExecuteCommand(string commandString, SqlConnection connection)
+ {
+ try
+ {
+ using var command = new SqlCommand(commandString, connection);
+ using var reader = command.ExecuteReader();
+ Console.WriteLine($"SQL query executed successfully: {commandString}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error while executing SQL query: {commandString}.\n{ex.Message}");
+ }
+ }
+
+ private static async Task ExecuteAsyncCommands(SqlConnection connection)
+ {
+ await connection.OpenAsync();
+ await ExecuteCreateAsync(connection);
+ await ExecuteInsertAsync(connection);
+ await ExecuteSelectAsync(connection);
+ await ExecuteDropAsync(connection);
+ }
+
+ private static async Task ExecuteCommandAsync(string commandString, SqlConnection connection)
+ {
+ try
+ {
+ using var command = new SqlCommand(commandString, connection);
+ using var reader = await command.ExecuteReaderAsync();
+ Console.WriteLine($"Async SQL query executed successfully: {commandString}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Error while executing async SQL query: {commandString}.\n{ex.Message}");
+ }
+ }
+
+ private static async Task ExecuteCreateAsync(SqlConnection connection)
+ {
+ await ExecuteCommandAsync(CreateCommand, connection);
+ }
+
+ private static async Task ExecuteInsertAsync(SqlConnection connection)
+ {
+ await ExecuteCommandAsync(InsertCommand, connection);
+ }
+
+ private static async Task ExecuteSelectAsync(SqlConnection connection)
+ {
+ await ExecuteCommandAsync(SelectCommand, connection);
+ }
+
+ private static async Task ExecuteDropAsync(SqlConnection connection)
+ {
+ await ExecuteCommandAsync(DropCommand, connection);
+ }
+}
diff --git a/test/test-applications/integrations/TestApplication.SqlClient.NetFramework/TestApplication.SqlClient.NetFramework.csproj b/test/test-applications/integrations/TestApplication.SqlClient.NetFramework/TestApplication.SqlClient.NetFramework.csproj
new file mode 100644
index 0000000000..8e338dfcfa
--- /dev/null
+++ b/test/test-applications/integrations/TestApplication.SqlClient.NetFramework/TestApplication.SqlClient.NetFramework.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net462
+
+
+
+
+
+
+
+
+
+
+