diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c45ab62ce3..ff91495c03 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -248,6 +248,12 @@ updates: - ".NET" - "do NOT merge" + - package-ecosystem: nuget + directory: /test/test-applications/integrations/TestApplication.StackExchangeRedis + schedule: + interval: "daily" + open-pull-requests-limit: 20 + - package-ecosystem: nuget directory: /test/test-applications/mocks/OpenTelemetry.AutoInstrumentation.Mock schedule: diff --git a/CHANGELOG.md b/CHANGELOG.md index b392362b09..ed3d8f5979 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Add MySql.Data traces instrumentation. - Add Npgsql traces instrumentation. +- Add StackExchange.Redis traces instrumentation. - Add configuration option `none` to `OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS` and `OTEL_DOTNET_AUTO_METRICS_ENABLED_INSTRUMENTATIONS`. diff --git a/OpenTelemetry.AutoInstrumentation.sln b/OpenTelemetry.AutoInstrumentation.sln index 1ce1cbde14..c641943da1 100644 --- a/OpenTelemetry.AutoInstrumentation.sln +++ b/OpenTelemetry.AutoInstrumentation.sln @@ -133,6 +133,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.Shared", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApplication.MySqlData", "test\test-applications\integrations\TestApplication.MySqlData\TestApplication.MySqlData.csproj", "{E7C2D2CF-C965-449D-A02C-02F7837D0C6D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApplication.StackExchangeRedis", "test\test-applications\integrations\TestApplication.StackExchangeRedis\TestApplication.StackExchangeRedis.csproj", "{671EB8F0-E164-4E9F-B423-27AF4B59D360}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -557,6 +559,18 @@ Global {E7C2D2CF-C965-449D-A02C-02F7837D0C6D}.Release|x64.Build.0 = Release|x64 {E7C2D2CF-C965-449D-A02C-02F7837D0C6D}.Release|x86.ActiveCfg = Release|x86 {E7C2D2CF-C965-449D-A02C-02F7837D0C6D}.Release|x86.Build.0 = Release|x86 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Debug|Any CPU.ActiveCfg = Debug|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Debug|Any CPU.Build.0 = Debug|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Debug|x64.ActiveCfg = Debug|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Debug|x64.Build.0 = Debug|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Debug|x86.ActiveCfg = Debug|x86 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Debug|x86.Build.0 = Debug|x86 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Release|Any CPU.ActiveCfg = Release|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Release|Any CPU.Build.0 = Release|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Release|x64.ActiveCfg = Release|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Release|x64.Build.0 = Release|x64 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Release|x86.ActiveCfg = Release|x86 + {671EB8F0-E164-4E9F-B423-27AF4B59D360}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -602,6 +616,7 @@ Global {DC54F01E-1D5C-4ECF-B5B9-14EECD2B4CF6} = {E409ADD3-9574-465C-AB09-4324D205CC7C} {CB4EA9F4-EE1B-4009-B3CD-215DCE8BE214} = {82A3CE96-0935-45E5-A9AA-A93A5B63500B} {E7C2D2CF-C965-449D-A02C-02F7837D0C6D} = {E409ADD3-9574-465C-AB09-4324D205CC7C} + {671EB8F0-E164-4E9F-B423-27AF4B59D360} = {E409ADD3-9574-465C-AB09-4324D205CC7C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F} diff --git a/docs/config.md b/docs/config.md index 741335ed4c..720b9d7a00 100644 --- a/docs/config.md +++ b/docs/config.md @@ -43,6 +43,7 @@ for more details. | `MySqlData` | [MySql.Data](https://www.nuget.org/packages/MySql.Data) **Not supported on .NET Framework** | ≥6.10.7 | source | | `Npgsql` | [Npgsql](https://www.nuget.org/packages/Npgsql) | ≥6.0.0 | source | | `SqlClient` | [Microsoft.Data.SqlClient](https://www.nuget.org/packages/Microsoft.Data.SqlClient) and [System.Data.SqlClient](https://www.nuget.org/packages/System.Data.SqlClient) | * | source | +| `StackExchangeRedis` | [StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis) **Not supported on .NET Framework** | ≥2.0.405 < 3.0.0 | source & binary | ### Instrumented metrics libraries and frameworks diff --git a/integrations.json b/integrations.json index e1243f5876..bbdce81bca 100644 --- a/integrations.json +++ b/integrations.json @@ -37,7 +37,7 @@ "type": "GraphQL.Execution.ExecutionStrategy", "method": "ExecuteAsync", "signature_types": [ - "System.Threading.Tasks.Task`1", + "System.Threading.Tasks.Task`1[GraphQL.ExecutionResult]", "GraphQL.Execution.ExecutionContext" ], "minimum_major": 2, @@ -60,7 +60,7 @@ "type": "GraphQL.Execution.SubscriptionExecutionStrategy", "method": "ExecuteAsync", "signature_types": [ - "System.Threading.Tasks.Task`1", + "System.Threading.Tasks.Task`1[GraphQL.ExecutionResult]", "GraphQL.Execution.ExecutionContext" ], "minimum_major": 2, @@ -105,5 +105,156 @@ } } ] + }, + { + "name": "StackExchangeRedis", + "method_replacements": [ + { + "caller": {}, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ConnectImpl", + "signature_types": [ + "StackExchange.Redis.ConnectionMultiplexer", + "System.Object", + "System.IO.TextWriter" + ], + "minimum_major": 2, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "OpenTelemetry.AutoInstrumentation", + "type": "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegration", + "action": "CallTargetModification" + } + }, + { + "caller": {}, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ConnectImpl", + "signature_types": [ + "StackExchange.Redis.ConnectionMultiplexer", + "StackExchange.Redis.ConfigurationOptions", + "System.IO.TextWriter" + ], + "minimum_major": 2, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "OpenTelemetry.AutoInstrumentation", + "type": "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegration", + "action": "CallTargetModification" + } + }, + { + "caller": {}, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ConnectImpl", + "signature_types": [ + "StackExchange.Redis.ConnectionMultiplexer", + "StackExchange.Redis.ConfigurationOptions", + "System.IO.TextWriter", + "System.Nullable`1[StackExchange.Redis.ServerType]" + ], + "minimum_major": 2, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "OpenTelemetry.AutoInstrumentation", + "type": "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegration", + "action": "CallTargetModification" + } + }, + { + "caller": {}, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ConnectImplAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1[StackExchange.Redis.ConnectionMultiplexer]", + "System.Object", + "System.IO.TextWriter" + ], + "minimum_major": 2, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "OpenTelemetry.AutoInstrumentation", + "type": "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync", + "action": "CallTargetModification" + } + }, + { + "caller": {}, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ConnectImplAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1[StackExchange.Redis.ConnectionMultiplexer]", + "StackExchange.Redis.ConfigurationOptions", + "System.IO.TextWriter" + ], + "minimum_major": 2, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "OpenTelemetry.AutoInstrumentation", + "type": "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync", + "action": "CallTargetModification" + } + }, + { + "caller": {}, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ConnectImplAsync", + "signature_types": [ + "System.Threading.Tasks.Task`1[StackExchange.Redis.ConnectionMultiplexer]", + "StackExchange.Redis.ConfigurationOptions", + "System.IO.TextWriter", + "System.Nullable`1[StackExchange.Redis.ServerType]" + ], + "minimum_major": 2, + "minimum_minor": 0, + "minimum_patch": 0, + "maximum_major": 2, + "maximum_minor": 65535, + "maximum_patch": 65535 + }, + "wrapper": { + "assembly": "OpenTelemetry.AutoInstrumentation", + "type": "OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis.StackExchangeRedisIntegrationAsync", + "action": "CallTargetModification" + } + } + ] } ] diff --git a/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs b/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs index 6873b2a142..299d86afcd 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configuration/EnvironmentConfigurationTracerHelper.cs @@ -17,7 +17,9 @@ using System; using System.Collections.Generic; using System.Linq; +#if NETCOREAPP3_1_OR_GREATER using OpenTelemetry.AutoInstrumentation.Util; +#endif using OpenTelemetry.Trace; namespace OpenTelemetry.AutoInstrumentation.Configuration; @@ -32,6 +34,7 @@ internal static class EnvironmentConfigurationTracerHelper #if NETCOREAPP3_1_OR_GREATER [TracerInstrumentation.MongoDB] = builder => builder.AddSource("MongoDB.Driver.Core.Extensions.DiagnosticSources"), [TracerInstrumentation.MySqlData] = builder => builder.AddSource("OpenTelemetry.Instrumentation.MySqlData"), + [TracerInstrumentation.StackExchangeRedis] = builder => builder.AddSource("OpenTelemetry.Instrumentation.StackExchangeRedis"), #endif [TracerInstrumentation.Npgsql] = builder => builder.AddSource("Npgsql") }; diff --git a/src/OpenTelemetry.AutoInstrumentation/Configuration/TracerInstrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Configuration/TracerInstrumentation.cs index eff609bfa8..65cb36260c 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Configuration/TracerInstrumentation.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Configuration/TracerInstrumentation.cs @@ -57,6 +57,11 @@ public enum TracerInstrumentation /// /// MySqlData instrumentation. /// - MySqlData = 6 + MySqlData = 6, + + /// + /// StackExchangeRedis instrumentation. + /// + StackExchangeRedis = 7 #endif } diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs index 1cd912223c..ac4534ad17 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentation.cs @@ -71,6 +71,10 @@ public static bool ProfilerAttached } } +#if NETCOREAPP3_1_OR_GREATER + internal static ILifespanManager LifespanManager => LazyInstrumentationLoader; +#endif + internal static TracerSettings TracerSettings { get; } = TracerSettings.FromDefaultSources(); internal static MeterSettings MeterSettings { get; } = MeterSettings.FromDefaultSources(); diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/GraphQL/GraphQLExecuteAsyncAttribute.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/GraphQL/GraphQLExecuteAsyncAttribute.cs index 40fe715cd6..7dc4f3fe9d 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/GraphQL/GraphQLExecuteAsyncAttribute.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/GraphQL/GraphQLExecuteAsyncAttribute.cs @@ -22,7 +22,7 @@ public GraphQLExecuteAsyncAttribute() { IntegrationName = GraphQLCommon.IntegrationName; MethodName = "ExecuteAsync"; - ReturnTypeName = "System.Threading.Tasks.Task`1"; + ReturnTypeName = "System.Threading.Tasks.Task`1[GraphQL.ExecutionResult]"; ParameterTypeNames = new[] { "GraphQL.Execution.ExecutionContext" }; } } diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisConstants.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisConstants.cs new file mode 100644 index 0000000000..72161d3a91 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisConstants.cs @@ -0,0 +1,40 @@ +// +// 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 NETCOREAPP3_1_OR_GREATER + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis; + +internal static class StackExchangeRedisConstants +{ + public const string AssemblyName = "StackExchange.Redis"; + + public const string MinimumVersion = "2.0.0"; // this is AssemblyVersion, all 2.* versions are released with this value + public const string MaximumVersion = "2.65535.65535"; + public const string IntegrationName = "StackExchangeRedis"; + + public const string ConnectionMultiplexerTypeName = "StackExchange.Redis.ConnectionMultiplexer"; + public const string ConfigurationOptionsTypeName = "StackExchange.Redis.ConfigurationOptions"; + public const string TextWriterTypeName = "System.IO.TextWriter"; + public const string TaskConnectionMultiplexerTypeName = $"System.Threading.Tasks.Task`1[{ConnectionMultiplexerTypeName}]"; + public const string NullableServerType = $"System.Nullable`1[{ServerTypeTypeName}]"; + + public const string ConnectImplMethodName = "ConnectImpl"; + public const string ConnectImplAsyncMethodName = "ConnectImplAsync"; + + private const string ServerTypeTypeName = "StackExchange.Redis.ServerType"; +} +#endif diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisInitializer.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisInitializer.cs new file mode 100644 index 0000000000..8796882d01 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisInitializer.cs @@ -0,0 +1,40 @@ +// +// 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 NETCOREAPP3_1_OR_GREATER + +using System; +using OpenTelemetry.AutoInstrumentation.Configuration; + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis; + +internal static class StackExchangeRedisInitializer +{ + public static void Initialize(object connection) + { + if (connection != null && Instrumentation.TracerSettings.EnabledInstrumentations.Contains(TracerInstrumentation.StackExchangeRedis)) + { + var instrumentationType = Type.GetType("OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentation, OpenTelemetry.Instrumentation.StackExchangeRedis"); + var optionsInstrumentationType = Type.GetType("OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions, OpenTelemetry.Instrumentation.StackExchangeRedis"); + + var options = Activator.CreateInstance(optionsInstrumentationType); + var instrumentation = Activator.CreateInstance(instrumentationType, connection, options); + + Instrumentation.LifespanManager.Track(instrumentation); + } + } +} +#endif diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisIntegration.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisIntegration.cs new file mode 100644 index 0000000000..9b0feccbfd --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisIntegration.cs @@ -0,0 +1,72 @@ +// +// 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 NETCOREAPP3_1_OR_GREATER + +using System; +using OpenTelemetry.AutoInstrumentation.CallTarget; + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis; + +/// +/// StackExchange.Redis.ConnectionMultiplexer calltarget instrumentation +/// +[InstrumentMethod(// releases 2.0.495 - 2.1.39 + AssemblyName = StackExchangeRedisConstants.AssemblyName, + TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + MethodName = StackExchangeRedisConstants.ConnectImplMethodName, + ReturnTypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + ParameterTypeNames = new[] { ClrNames.Object, StackExchangeRedisConstants.TextWriterTypeName }, + MinimumVersion = StackExchangeRedisConstants.MinimumVersion, + MaximumVersion = StackExchangeRedisConstants.MaximumVersion, + IntegrationName = StackExchangeRedisConstants.IntegrationName)] +[InstrumentMethod(// releases 2.1.50 - 2.5.43 + AssemblyName = StackExchangeRedisConstants.AssemblyName, + TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + MethodName = StackExchangeRedisConstants.ConnectImplMethodName, + ReturnTypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName }, + MinimumVersion = StackExchangeRedisConstants.MinimumVersion, + MaximumVersion = StackExchangeRedisConstants.MaximumVersion, + IntegrationName = StackExchangeRedisConstants.IntegrationName)] +[InstrumentMethod(// releases 2.5.61+ + AssemblyName = StackExchangeRedisConstants.AssemblyName, + TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + MethodName = StackExchangeRedisConstants.ConnectImplMethodName, + ReturnTypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName, StackExchangeRedisConstants.NullableServerType }, + MinimumVersion = StackExchangeRedisConstants.MinimumVersion, + MaximumVersion = StackExchangeRedisConstants.MaximumVersion, + IntegrationName = StackExchangeRedisConstants.IntegrationName)] +public class StackExchangeRedisIntegration +{ + /// + /// OnMethodEnd callback + /// + /// Return value + /// Exception value + /// CallTarget state + /// Type of the target + /// Return type + /// A response value, in an async scenario will be T of Task of T + public static CallTargetReturn OnMethodEnd(TReturn returnValue, Exception exception, CallTargetState state) + { + StackExchangeRedisInitializer.Initialize(returnValue); + + return new CallTargetReturn(returnValue); + } +} +#endif diff --git a/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisIntegrationAsync.cs b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisIntegrationAsync.cs new file mode 100644 index 0000000000..63b4f1f5a9 --- /dev/null +++ b/src/OpenTelemetry.AutoInstrumentation/Instrumentations/StackExchangeRedis/StackExchangeRedisIntegrationAsync.cs @@ -0,0 +1,73 @@ +// +// 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 NETCOREAPP3_1_OR_GREATER + +using System; +using OpenTelemetry.AutoInstrumentation.CallTarget; + +namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis; + +/// +/// StackExchange.Redis.ConnectionMultiplexer calltarget instrumentation +/// +[InstrumentMethod(// releases 2.0.495 - 2.1.39 + AssemblyName = StackExchangeRedisConstants.AssemblyName, + TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + MethodName = StackExchangeRedisConstants.ConnectImplAsyncMethodName, + ReturnTypeName = StackExchangeRedisConstants.TaskConnectionMultiplexerTypeName, + ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName }, + MinimumVersion = StackExchangeRedisConstants.MinimumVersion, + MaximumVersion = StackExchangeRedisConstants.MaximumVersion, + IntegrationName = StackExchangeRedisConstants.IntegrationName)] +[InstrumentMethod(// releases 2.1.50 - 2.5.43 + AssemblyName = StackExchangeRedisConstants.AssemblyName, + TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + MethodName = StackExchangeRedisConstants.ConnectImplAsyncMethodName, + ReturnTypeName = StackExchangeRedisConstants.TaskConnectionMultiplexerTypeName, + ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName }, + MinimumVersion = StackExchangeRedisConstants.MinimumVersion, + MaximumVersion = StackExchangeRedisConstants.MaximumVersion, + IntegrationName = StackExchangeRedisConstants.IntegrationName)] +[InstrumentMethod(// releases 2.5.61+ + AssemblyName = StackExchangeRedisConstants.AssemblyName, + TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName, + MethodName = StackExchangeRedisConstants.ConnectImplAsyncMethodName, + ReturnTypeName = StackExchangeRedisConstants.TaskConnectionMultiplexerTypeName, + ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName, StackExchangeRedisConstants.NullableServerType }, + MinimumVersion = StackExchangeRedisConstants.MinimumVersion, + MaximumVersion = StackExchangeRedisConstants.MaximumVersion, + IntegrationName = StackExchangeRedisConstants.IntegrationName)] +public class StackExchangeRedisIntegrationAsync +{ + /// + /// OnAsyncMethodEnd callback + /// + /// Instance value, aka `this` of the instrumented method. + /// Return value + /// Exception value + /// CallTarget state + /// Type of the target + /// Return type + /// A response value, in an async scenario will be T of Task of T + public static TReturn OnAsyncMethodEnd(TTarget instance, TReturn returnValue, Exception exception, CallTargetState state) + { + StackExchangeRedisInitializer.Initialize(returnValue); + + return returnValue; + } +} +#endif diff --git a/src/OpenTelemetry.AutoInstrumentation/Loading/LazyInstrumentationLoader.cs b/src/OpenTelemetry.AutoInstrumentation/Loading/LazyInstrumentationLoader.cs index c1b5191d1b..51547fc582 100644 --- a/src/OpenTelemetry.AutoInstrumentation/Loading/LazyInstrumentationLoader.cs +++ b/src/OpenTelemetry.AutoInstrumentation/Loading/LazyInstrumentationLoader.cs @@ -51,7 +51,7 @@ public void Add(InstrumentationInitializer loader) _ = new OnAssemblyLoadInitializer(this, loader); } - void ILifespanManager.Track(object instance) + public void Track(object instance) { _instrumentations.Add(instance); } diff --git a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj index f9783decec..6c5bffd6e8 100644 --- a/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj +++ b/src/OpenTelemetry.AutoInstrumentation/OpenTelemetry.AutoInstrumentation.csproj @@ -11,21 +11,27 @@ - - - - - + + + + + + + + + + + diff --git a/test/IntegrationTests/RedisCollection.cs b/test/IntegrationTests/RedisCollection.cs new file mode 100644 index 0000000000..e0798b1410 --- /dev/null +++ b/test/IntegrationTests/RedisCollection.cs @@ -0,0 +1,80 @@ +// +// 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 NETCOREAPP3_1_OR_GREATER + +using System.Threading.Tasks; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; +using IntegrationTests.Helpers; +using Xunit; + +namespace IntegrationTests; + +[CollectionDefinition(Name)] +public class RedisCollection : ICollectionFixture +{ + public const string Name = nameof(RedisCollection); +} + +public class RedisFixture : IAsyncLifetime +{ + private const int RedisPort = 6379; + private const string RedisImage = "redis:7.0.4"; + + private TestcontainersContainer _container; + + public RedisFixture() + { + Port = TcpPortProvider.GetOpenPort(); + } + + public int Port { get; } + + public async Task InitializeAsync() + { + _container = await LaunchRedisContainerAsync(Port); + } + + public async Task DisposeAsync() + { + if (_container != null) + { + await ShutdownRedisContainerAsync(_container); + } + } + + private async Task LaunchRedisContainerAsync(int port) + { + var containersBuilder = new TestcontainersBuilder() + .WithImage(RedisImage) + .WithName($"redis-{port}") + .WithPortBinding(port, RedisPort) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(RedisPort)); + + var container = containersBuilder.Build(); + await container.StartAsync(); + + return container; + } + + private async Task ShutdownRedisContainerAsync(TestcontainersContainer container) + { + await container.CleanUpAsync(); + await container.DisposeAsync(); + } +} +#endif diff --git a/test/IntegrationTests/StackExchangeRedisTests.cs b/test/IntegrationTests/StackExchangeRedisTests.cs new file mode 100644 index 0000000000..3e30c4024c --- /dev/null +++ b/test/IntegrationTests/StackExchangeRedisTests.cs @@ -0,0 +1,65 @@ +// +// 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 NETCOREAPP3_1_OR_GREATER + +using System; +using FluentAssertions; +using FluentAssertions.Execution; +using IntegrationTests.Helpers; +using Xunit; +using Xunit.Abstractions; + +namespace IntegrationTests; + +[Collection(RedisCollection.Name)] +public class StackExchangeRedisTests : TestHelper +{ + private const string ServiceName = "TestApplication.StackExchangeRedis"; + private readonly RedisFixture _redis; + + public StackExchangeRedisTests(ITestOutputHelper output, RedisFixture redis) + : base("StackExchangeRedis", output) + { + _redis = redis; + SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName); + } + + [Fact] + [Trait("Category", "EndToEnd")] + [Trait("Containers", "Linux")] + public void SubmitsTraces() + { + using var agent = new MockZipkinCollector(Output); + + RunTestApplication(agent.Port, arguments: $"--redis {_redis.Port}"); + + const int expectedSpansCount = 8; + + var spans = agent.WaitForSpans(expectedSpansCount, TimeSpan.FromSeconds(5)); + + using (new AssertionScope()) + { + spans.Count.Should().Be(expectedSpansCount); + + foreach (var span in spans) + { + span.Tags["db.system"].Should().Be("redis"); + } + } + } +} +#endif diff --git a/test/OpenTelemetry.AutoInstrumentation.Tests/Configuration/SettingsTests.cs b/test/OpenTelemetry.AutoInstrumentation.Tests/Configuration/SettingsTests.cs index 2b99953c65..4953156afe 100644 --- a/test/OpenTelemetry.AutoInstrumentation.Tests/Configuration/SettingsTests.cs +++ b/test/OpenTelemetry.AutoInstrumentation.Tests/Configuration/SettingsTests.cs @@ -119,6 +119,7 @@ public void MetricExporter_SupportedValues(string metricExporter, MetricsExporte #if NETCOREAPP3_1_OR_GREATER [InlineData(nameof(TracerInstrumentation.MongoDB), TracerInstrumentation.MongoDB)] [InlineData(nameof(TracerInstrumentation.MySqlData), TracerInstrumentation.MySqlData)] + [InlineData(nameof(TracerInstrumentation.StackExchangeRedis), TracerInstrumentation.StackExchangeRedis)] #endif [InlineData(nameof(TracerInstrumentation.Npgsql), TracerInstrumentation.Npgsql)] [InlineData(nameof(TracerInstrumentation.SqlClient), TracerInstrumentation.SqlClient)] diff --git a/test/test-applications/integrations/TestApplication.StackExchangeRedis/Program.cs b/test/test-applications/integrations/TestApplication.StackExchangeRedis/Program.cs new file mode 100644 index 0000000000..c34ebcc6da --- /dev/null +++ b/test/test-applications/integrations/TestApplication.StackExchangeRedis/Program.cs @@ -0,0 +1,100 @@ +// +// 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.Threading.Tasks; +using StackExchange.Redis; +using TestApplication.Shared; + +namespace TestApplication.StackExchangeRedis; + +public static class Program +{ + public static async Task Main(string[] args) + { + ConsoleHelper.WriteSplashScreen(args); + + var redisPort = GetRedisPort(args); + + var connectionString = $@"127.0.0.1:{redisPort}"; + + using (var connection = await ConnectionMultiplexer.ConnectAsync(connectionString)) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + using (var connection = await ConnectionMultiplexer.ConnectAsync(ConfigurationOptions.Parse(connectionString))) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + using (var connection = ConnectionMultiplexer.Connect(connectionString)) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + using (var connection = ConnectionMultiplexer.Connect(ConfigurationOptions.Parse(connectionString))) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + // SentinelConnect and SentinelConnectAsync introduced in 2.1.50 + using (var connection = ConnectionMultiplexer.SentinelConnect(connectionString)) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + using (var connection = ConnectionMultiplexer.SentinelConnect(ConfigurationOptions.Parse(connectionString))) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + using (var connection = await ConnectionMultiplexer.SentinelConnectAsync(connectionString)) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + + using (var connection = await ConnectionMultiplexer.SentinelConnectAsync(ConfigurationOptions.Parse(connectionString))) + { + var db = connection.GetDatabase(); + + db.Ping(); + } + } + + private static string GetRedisPort(string[] args) + { + if (args.Length > 1) + { + return args[1]; + } + + return "6379"; + } +} diff --git a/test/test-applications/integrations/TestApplication.StackExchangeRedis/TestApplication.StackExchangeRedis.csproj b/test/test-applications/integrations/TestApplication.StackExchangeRedis/TestApplication.StackExchangeRedis.csproj new file mode 100644 index 0000000000..c13dfc3d99 --- /dev/null +++ b/test/test-applications/integrations/TestApplication.StackExchangeRedis/TestApplication.StackExchangeRedis.csproj @@ -0,0 +1,15 @@ + + + + Exe + + + + + + + + + + +