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 + + + + + + + + + + +