Remove StackExchangeRedis Instrumenation (#3346)

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Utkarsh Umesan Pillai 2022-06-07 09:35:43 -07:00 committed by GitHub
parent 8df82a2d7f
commit c90ab4a2f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 31 additions and 1682 deletions

View File

@ -14,14 +14,6 @@ on:
- '**.md'
jobs:
redis-test:
runs-on: ubuntu-latest
strategy:
matrix:
version: [netcoreapp3.1,net6.0]
steps:
- run: 'echo "No build required"'
sql-test:
runs-on: ubuntu-latest
strategy:

View File

@ -11,18 +11,6 @@ on:
- '**.md'
jobs:
redis-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version: [netcoreapp3.1,net6.0]
steps:
- uses: actions/checkout@v3
- name: Run redis docker-compose.integration
run: docker-compose --file=test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/docker-compose.yml --file=build/docker-compose.${{ matrix.version }}.yml --project-directory=. up --exit-code-from=tests --build
sql-test:
runs-on: ubuntu-latest
strategy:

View File

@ -52,10 +52,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testdata", "testdata", "{77
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp.AspNetCore.3.1", "test\TestApp.AspNetCore.3.1\TestApp.AspNetCore.3.1.csproj", "{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.StackExchangeRedis", "src\OpenTelemetry.Instrumentation.StackExchangeRedis\OpenTelemetry.Instrumentation.StackExchangeRedis.csproj", "{6B681D72-D68A-44CC-8C75-53B9A322E6EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.StackExchangeRedis.Tests", "test\OpenTelemetry.Instrumentation.StackExchangeRedis.Tests\OpenTelemetry.Instrumentation.StackExchangeRedis.Tests.csproj", "{CA98AF29-0852-4ADD-A66B-7E96266EE7B7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{E359BB2B-9AEC-497D-B321-7DF2450C3B8E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Jaeger", "src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj", "{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}"
@ -274,14 +270,6 @@ Global
{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Release|Any CPU.Build.0 = Release|Any CPU
{6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B681D72-D68A-44CC-8C75-53B9A322E6EC}.Release|Any CPU.Build.0 = Release|Any CPU
{CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA98AF29-0852-4ADD-A66B-7E96266EE7B7}.Release|Any CPU.Build.0 = Release|Any CPU
{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@ -57,7 +57,6 @@ libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/ma
* [ASP.NET Core](./src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
* [Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
* [HTTP clients](./src/OpenTelemetry.Instrumentation.Http/README.md)
* [Redis client](./src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md)
* [SQL client](./src/OpenTelemetry.Instrumentation.SqlClient/README.md)
Here are the [exporter

View File

@ -47,7 +47,6 @@
<NewtonsoftJsonPkgVer>[12.0.2,13.0)</NewtonsoftJsonPkgVer>
<MoqPkgVer>[4.14.5,5.0)</MoqPkgVer>
<RabbitMQClientPkgVer>[6.1.0,7.0)</RabbitMQClientPkgVer>
<StackExchangeRedisPkgVer>[2.1.58,3.0)</StackExchangeRedisPkgVer>
<SwashbuckleAspNetCorePkgVer>[6.2.3]</SwashbuckleAspNetCorePkgVer>
<XUnitRunnerVisualStudioPkgVer>[2.4.3,3.0)</XUnitRunnerVisualStudioPkgVer>
<XUnitPkgVer>[2.4.1,3.0)</XUnitPkgVer>

View File

@ -40,7 +40,6 @@
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
<OpenTracingPkgVer>[0.12.1,0.13)</OpenTracingPkgVer>
<OTelPreviousStableVer>1.3.0</OTelPreviousStableVer>
<StackExchangeRedisPkgVer>[2.1.58,3.0)</StackExchangeRedisPkgVer>
<StyleCopAnalyzersPkgVer>[1.2.0-beta.354,2.0)</StyleCopAnalyzersPkgVer>
<SystemCollectionsImmutablePkgVer>1.4.0</SystemCollectionsImmutablePkgVer>
<SystemDiagnosticSourcePkgVer>6.0.0</SystemDiagnosticSourcePkgVer>

View File

@ -83,8 +83,8 @@ may be used as a reference.
The [inspiration of the OpenTelemetry
project](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#instrumentation-libraries)
is to make every library observable out of the box by having
them call OpenTelemetry API directly. However, many libraries will not have such
is to make every library observable out of the box by having them call
OpenTelemetry API directly. However, many libraries will not have such
integration, and as such there is a need for a separate library which would
inject such calls, using mechanisms such as wrapping interfaces, subscribing to
library-specific callbacks, or translating existing telemetry into OpenTelemetry
@ -103,11 +103,11 @@ the following instrumentation libraries. The individual docs for them describes
the library they instrument, and steps for enabling them.
* [ASP.NET](../../../src/OpenTelemetry.Instrumentation.AspNet/README.md)
* [ASP.NET Core](../../../src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
* [gRPC client](../../../src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
* [ASP.NET
Core](../../../src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
* [gRPC
client](../../../src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
* [HTTP clients](../../../src/OpenTelemetry.Instrumentation.Http/README.md)
* [Redis
client](../../../src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md)
* [SQL client](../../../src/OpenTelemetry.Instrumentation.SqlClient/README.md)
More community contributed instrumentations are available in [OpenTelemetry .NET
@ -135,13 +135,9 @@ modify to emit activities directly.*
Writing an instrumentation library typically involves 3 steps.
1. First step involves "hijacking" into the target library. The exact mechanism
of this depends on the target library itself. For example, StackExchangeRedis
library allows hooks into the library, and the [StackExchangeRedis
instrumentation
library](../../../src/OpenTelemetry.Instrumentation.StackExchangeRedis/README.md)
in this case, leverages them. Another example is System.Data.SqlClient for
.NET Framework, which publishes events using `EventSource`. The [SqlClient
instrumentation
of this depends on the target library itself. For example,
System.Data.SqlClient for .NET Framework, which publishes events using
`EventSource`. The [SqlClient instrumentation
library](../../../src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs),
in this case subscribes to the `EventSource` callbacks.
@ -151,20 +147,19 @@ Writing an instrumentation library typically involves 3 steps.
target instrumented library. Irrespective of the actual mechanism used in
first step, this should be uniform across all instrumentation libraries. The
`ActivitySource` must be created using the name and version of the
instrumentation library (eg:
"OpenTelemetry.Instrumentation.StackExchangeRedis") and *not* the
instrumented library (eg: "StackExchange.Redis")
1. [Context Propagation](../../../src/OpenTelemetry.Api/README.md#context-propagation):
If your library initiates out of process requests or
accepts them, the library needs to
[inject the `PropagationContext`](../../../examples/MicroserviceExample/Utils/Messaging/MessageSender.cs)
to outgoing requests and
[extract the context](../../../examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs)
and hydrate the Activity/Baggage upon receiving incoming requests.
This is only required if you're using your own protocol to
communicate over the wire.
(i.e. If you're using an already instrumented HttpClient or GrpcClient,
this is already provided to you and **do not require**
instrumentation library (eg: "OpenTelemetry.Instrumentation.Http") and *not*
the instrumented library (eg: "System.Net.Http")
1. [Context
Propagation](../../../src/OpenTelemetry.Api/README.md#context-propagation):
If your library initiates out of process requests or accepts them, the
library needs to [inject the
`PropagationContext`](../../../examples/MicroserviceExample/Utils/Messaging/MessageSender.cs)
to outgoing requests and [extract the
context](../../../examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs)
and hydrate the Activity/Baggage upon receiving incoming requests. This is
only required if you're using your own protocol to communicate over the
wire. (i.e. If you're using an already instrumented HttpClient or
GrpcClient, this is already provided to you and **do not require**
injecting/extracting `PropagationContext` explicitly again.)
3. Third step is an optional step, and involves providing extension methods on
@ -179,8 +174,8 @@ Writing an instrumentation library typically involves 3 steps.
extension method on `TracerProviderBuilder`. Inside this extension
method, it should call the `AddInstrumentation` method, and `AddSource`
method to enable its ActivitySource for the provider. An example
instrumentation using this approach is [StackExchangeRedis
instrumentation](../../../src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs)
instrumentation using this approach is [SqlClient
instrumentation](../../../src/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs)
2. If the instrumentation library does not requires any state management
tied to that of `TracerProvider`, then providing `TracerProviderBuilder`
@ -326,16 +321,16 @@ A demo sampler is shown [here](./MySampler.cs).
## Resource Detector
OpenTelemetry .NET SDK provides a resource detector for detecting
resource information from the `OTEL_RESOURCE_ATTRIBUTES` and
`OTEL_SERVICE_NAME` environment variables.
OpenTelemetry .NET SDK provides a resource detector for detecting resource
information from the `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME`
environment variables.
Custom resource detectors can be implemented:
* ResourceDetectors should inherit from
`OpenTelemetry.Resources.IResourceDetector`, (which belongs
to the [OpenTelemetry](../../../src/OpenTelemetry/README.md)
package), and implement the `Detect` method.
`OpenTelemetry.Resources.IResourceDetector`, (which belongs to the
[OpenTelemetry](../../../src/OpenTelemetry/README.md) package), and implement
the `Detect` method.
A demo ResourceDetector is shown [here](./MyResourceDetector.cs).

View File

@ -19,7 +19,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StackExchange.Redis" Version="$(StackExchangeRedisPkgVer)" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\SpanAttributeConstants.cs" Link="Includes\SpanAttributeConstants.cs" />
</ItemGroup>
@ -27,7 +26,6 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.GrpcNetClient\OpenTelemetry.Instrumentation.GrpcNetClient.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.Http\OpenTelemetry.Instrumentation.Http.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.StackExchangeRedis\OpenTelemetry.Instrumentation.StackExchangeRedis.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.ZPages\OpenTelemetry.Exporter.ZPages.csproj" />

View File

@ -46,7 +46,7 @@ namespace Examples.Console
/// <param name="args">Arguments from command line.</param>
public static void Main(string[] args)
{
Parser.Default.ParseArguments<JaegerOptions, ZipkinOptions, PrometheusOptions, MetricsOptions, LogsOptions, GrpcNetClientOptions, HttpClientOptions, RedisOptions, ZPagesOptions, ConsoleOptions, OpenTelemetryShimOptions, OpenTracingShimOptions, OtlpOptions, InMemoryOptions>(args)
Parser.Default.ParseArguments<JaegerOptions, ZipkinOptions, PrometheusOptions, MetricsOptions, LogsOptions, GrpcNetClientOptions, HttpClientOptions, ZPagesOptions, ConsoleOptions, OpenTelemetryShimOptions, OpenTracingShimOptions, OtlpOptions, InMemoryOptions>(args)
.MapResult(
(JaegerOptions options) => TestJaegerExporter.Run(options.Host, options.Port),
(ZipkinOptions options) => TestZipkinExporter.Run(options.Uri),
@ -55,7 +55,6 @@ namespace Examples.Console
(LogsOptions options) => TestLogs.Run(options),
(GrpcNetClientOptions options) => TestGrpcNetClient.Run(),
(HttpClientOptions options) => TestHttpClient.Run(),
(RedisOptions options) => TestRedis.Run(options.Uri),
(ZPagesOptions options) => TestZPagesExporter.Run(),
(ConsoleOptions options) => TestConsoleExporter.Run(options),
(OpenTelemetryShimOptions options) => TestOTelShimWithConsoleExporter.Run(options),
@ -127,13 +126,6 @@ namespace Examples.Console
{
}
[Verb("redis", HelpText = "Specify the options required to test Redis with Zipkin")]
internal class RedisOptions
{
[Option('u', "uri", HelpText = "Please specify the uri of Zipkin backend", Required = true)]
public string Uri { get; set; }
}
[Verb("zpages", HelpText = "Specify the options required to test ZPages")]
internal class ZPagesOptions
{

View File

@ -1,111 +0,0 @@
// <copyright file="TestRedis.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using OpenTelemetry;
using OpenTelemetry.Trace;
using StackExchange.Redis;
namespace Examples.Console
{
internal class TestRedis
{
internal static object Run(string zipkinUri)
{
/*
* Setup redis service inside local docker.
* docker run --name opentelemetry-redis-test -d -p 6379:6379 redis
*
* If you face any issue with the first command, do the following ones:
* docker exec -it opentelemetry-redis-test sh
* redis-cli
* set bind 0.0.0.0
* save
*/
// connect to the redis server. The default port 6379 will be used.
var connection = ConnectionMultiplexer.Connect("localhost");
// Configure exporter to export traces to Zipkin
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddZipkinExporter(o =>
{
o.Endpoint = new Uri(zipkinUri);
})
.AddRedisInstrumentation(connection, options =>
{
// changing flushinterval from 10s to 5s
options.FlushInterval = TimeSpan.FromSeconds(5);
})
.AddSource("redis-test")
.Build();
ActivitySource activitySource = new ActivitySource("redis-test");
// select a database (by default, DB = 0)
var db = connection.GetDatabase();
// Create a scoped activity. It will end automatically when using statement ends
using (activitySource.StartActivity("Main"))
{
System.Console.WriteLine("About to do a busy work");
for (var i = 0; i < 10; i++)
{
DoWork(db, activitySource);
}
}
System.Console.Write("Press ENTER to stop.");
System.Console.ReadLine();
return null;
}
private static void DoWork(IDatabase db, ActivitySource activitySource)
{
// Start another activity. If another activity was already started, it'll use that activity as the parent activity.
// In this example, the main method already started a activity, so that'll be the parent activity, and this will be
// a child activity.
using Activity activity = activitySource.StartActivity("DoWork");
try
{
db.StringSet("key", "value " + DateTime.Now.ToLongDateString());
System.Console.WriteLine("Doing busy work");
Thread.Sleep(1000);
// run a command, in this case a GET
var myVal = db.StringGet("key");
System.Console.WriteLine(myVal);
}
catch (ArgumentOutOfRangeException e)
{
activity.SetStatus(Status.Error.WithDescription(e.ToString()));
}
// Annotate our activity to capture metadata about our operation
var attributes = new Dictionary<string, object>
{
{ "use", "demo" },
};
ActivityTagsCollection eventTags = new ActivityTagsCollection(attributes);
activity.AddEvent(new ActivityEvent("Invoking DoWork", default, eventTags));
}
}
}

View File

@ -1,10 +0,0 @@
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity, StackExchange.Redis.Profiling.IProfiledCommand>
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.get -> System.TimeSpan
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.set -> void
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.get -> bool
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.set -> void
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.StackExchangeRedisCallsInstrumentationOptions() -> void
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, StackExchange.Redis.IConnectionMultiplexer connection = null, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions> configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder

View File

@ -1,10 +0,0 @@
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity, StackExchange.Redis.Profiling.IProfiledCommand>
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.set -> void
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.get -> System.TimeSpan
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.set -> void
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.get -> bool
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetVerboseDatabaseStatements.set -> void
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.StackExchangeRedisCallsInstrumentationOptions() -> void
OpenTelemetry.Trace.TracerProviderBuilderExtensions
static OpenTelemetry.Trace.TracerProviderBuilderExtensions.AddRedisInstrumentation(this OpenTelemetry.Trace.TracerProviderBuilder builder, StackExchange.Redis.IConnectionMultiplexer connection = null, System.Action<OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions> configure = null) -> OpenTelemetry.Trace.TracerProviderBuilder

View File

@ -1,34 +0,0 @@
// <copyright file="AssemblyInfo.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("OpenTelemetry.Instrumentation.StackExchangeRedis.Tests" + AssemblyInfo.PublicKey)]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2" + AssemblyInfo.MoqPublicKey)]
#if SIGNED
internal static class AssemblyInfo
{
public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898";
public const string MoqPublicKey = ", PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7";
}
#else
internal static class AssemblyInfo
{
public const string PublicKey = "";
public const string MoqPublicKey = "";
}
#endif

View File

@ -1,113 +0,0 @@
# Changelog
## Unreleased
## 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))
* Bumped minimum required version of `Microsoft.Extensions.Options` to 3.1.0.
([#2582](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3196))
## 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
* Adds SetVerboseDatabaseStatements option to allow setting more detailed
database statement tag values.
* Adds Enrich option to allow enriching activities from the source profiled
command objects.
* Removes upper constraint for Microsoft.Extensions.Options dependency.
([#2179](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2179))
## 1.0.0-rc7
Released 2021-Jul-12
## 1.0.0-rc6
Released 2021-Jun-25
* `AddRedisInstrumentation` extension will now resolve `IConnectionMultiplexer`
& `StackExchangeRedisCallsInstrumentationOptions` through DI when
OpenTelemetry.Extensions.Hosting is in use.
([#2110](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2110))
## 1.0.0-rc5
Released 2021-Jun-09
## 1.0.0-rc4
Released 2021-Apr-23
* Activities are now created with the `db.system` attribute set for usage during
sampling.
([#1984](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1984))
## 1.0.0-rc3
Released 2021-Mar-19
## 1.0.0-rc2
Released 2021-Jan-29
## 1.0.0-rc1.1
Released 2020-Nov-17
## 0.8.0-beta.1
Released 2020-Nov-5
## 0.7.0-beta.1
Released 2020-Oct-16
* 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
## 0.4.0-beta.2
Released 2020-07-24
* First beta release
## 0.3.0-beta
Released 2020-07-23
* Initial release

View File

@ -1,209 +0,0 @@
// <copyright file="RedisProfilerEntryToActivityConverter.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using OpenTelemetry.Trace;
using StackExchange.Redis.Profiling;
namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation
{
internal static class RedisProfilerEntryToActivityConverter
{
private static readonly Lazy<Func<object, (string, string)>> MessageDataGetter = new(() =>
{
var redisAssembly = typeof(IProfiledCommand).Assembly;
Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand");
Type messageType = redisAssembly.GetType("StackExchange.Redis.Message");
Type scriptMessageType = redisAssembly.GetType("StackExchange.Redis.RedisDatabase+ScriptEvalMessage");
var messageDelegate = CreateFieldGetter<object>(profiledCommandType, "Message", BindingFlags.NonPublic | BindingFlags.Instance);
var scriptDelegate = CreateFieldGetter<string>(scriptMessageType, "script", BindingFlags.NonPublic | BindingFlags.Instance);
var commandAndKeyFetcher = new PropertyFetcher<string>("CommandAndKey");
if (messageDelegate == null)
{
return new Func<object, (string, string)>(source => (null, null));
}
return new Func<object, (string, string)>(source =>
{
if (source == null)
{
return (null, null);
}
var message = messageDelegate(source);
if (message == null)
{
return (null, null);
}
string script = null;
if (message.GetType() == scriptMessageType)
{
script = scriptDelegate.Invoke(message);
}
if (commandAndKeyFetcher.TryFetch(message, out var value))
{
return (value, script);
}
return (null, script);
});
});
public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command, StackExchangeRedisCallsInstrumentationOptions options)
{
var name = command.Command; // Example: SET;
if (string.IsNullOrEmpty(name))
{
name = StackExchangeRedisCallsInstrumentation.ActivityName;
}
var activity = StackExchangeRedisCallsInstrumentation.ActivitySource.StartActivity(
name,
ActivityKind.Client,
parentActivity?.Context ?? default,
StackExchangeRedisCallsInstrumentation.CreationTags,
startTime: command.CommandCreated);
if (activity == null)
{
return null;
}
activity.SetEndTime(command.CommandCreated + command.ElapsedTime);
if (activity.IsAllDataRequested == true)
{
// see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md
// Timing example:
// command.CommandCreated; //2019-01-10 22:18:28Z
// command.CreationToEnqueued; // 00:00:32.4571995
// command.EnqueuedToSending; // 00:00:00.0352838
// command.SentToResponse; // 00:00:00.0060586
// command.ResponseToCompletion; // 00:00:00.0002601
// Total:
// command.ElapsedTime; // 00:00:32.4988020
activity.SetStatus(Status.Unset);
activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName, command.Flags.ToString());
if (options.SetVerboseDatabaseStatements)
{
var (commandAndKey, script) = MessageDataGetter.Value.Invoke(command);
if (!string.IsNullOrEmpty(commandAndKey) && !string.IsNullOrEmpty(script))
{
activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey + " " + script);
}
else if (!string.IsNullOrEmpty(commandAndKey))
{
activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey);
}
else if (command.Command != null)
{
// Example: "db.statement": SET;
activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command);
}
}
else if (command.Command != null)
{
// Example: "db.statement": SET;
activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command);
}
if (command.EndPoint != null)
{
if (command.EndPoint is IPEndPoint ipEndPoint)
{
activity.SetTag(SemanticConventions.AttributeNetPeerIp, ipEndPoint.Address.ToString());
activity.SetTag(SemanticConventions.AttributeNetPeerPort, ipEndPoint.Port);
}
else if (command.EndPoint is DnsEndPoint dnsEndPoint)
{
activity.SetTag(SemanticConventions.AttributeNetPeerName, dnsEndPoint.Host);
activity.SetTag(SemanticConventions.AttributeNetPeerPort, dnsEndPoint.Port);
}
else
{
activity.SetTag(SemanticConventions.AttributePeerService, command.EndPoint.ToString());
}
}
activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisDatabaseIndexKeyName, command.Db);
// TODO: deal with the re-transmission
// command.RetransmissionOf;
// command.RetransmissionReason;
var enqueued = command.CommandCreated.Add(command.CreationToEnqueued);
var send = enqueued.Add(command.EnqueuedToSending);
var response = send.Add(command.SentToResponse);
activity.AddEvent(new ActivityEvent("Enqueued", enqueued));
activity.AddEvent(new ActivityEvent("Sent", send));
activity.AddEvent(new ActivityEvent("ResponseReceived", response));
options.Enrich?.Invoke(activity, command);
}
activity.Stop();
return activity;
}
public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCommand> sessionCommands, StackExchangeRedisCallsInstrumentationOptions options)
{
foreach (var command in sessionCommands)
{
ProfilerCommandToActivity(parentActivity, command, options);
}
}
/// <summary>
/// Creates getter for a field defined in private or internal type
/// repesented with classType variable.
/// </summary>
private static Func<object, TField> CreateFieldGetter<TField>(Type classType, string fieldName, BindingFlags flags)
{
FieldInfo field = classType.GetField(fieldName, flags);
if (field != null)
{
string methodName = classType.FullName + ".get_" + field.Name;
DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(TField), new[] { typeof(object) }, true);
ILGenerator generator = getterMethod.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Castclass, classType);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Ret);
return (Func<object, TField>)getterMethod.CreateDelegate(typeof(Func<object, TField>));
}
return null;
}
}
}

View File

@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
<TargetFrameworks>netstandard2.0;net462</TargetFrameworks>
<Description>StackExchange.Redis instrumentation for OpenTelemetry .NET</Description>
<PackageTags>$(PackageTags);distributed-tracing;Redis;StackExchange.Redis</PackageTags>
<IncludeDiagnosticSourceInstrumentationHelpers>true</IncludeDiagnosticSourceInstrumentationHelpers>
<IncludeInstrumentationHelpers>true</IncludeInstrumentationHelpers>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(RepoRoot)\src\OpenTelemetry\Internal\ServiceProviderExtensions.cs" Link="Includes\ServiceProviderExtensions.cs" />
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\Guard.cs" Link="Includes\Guard.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api\OpenTelemetry.Api.csproj" />
<PackageReference Include="StackExchange.Redis" Version="$(StackExchangeRedisPkgVer)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPkgVer)" />
</ItemGroup>
</Project>

View File

@ -1,137 +0,0 @@
# StackExchange.Redis Instrumentation for OpenTelemetry
[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Instrumentation.StackExchangeRedis.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.StackExchangeRedis)
[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Instrumentation.StackExchangeRedis.svg)](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.StackExchangeRedis)
This is an
[Instrumentation Library](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library),
which instruments
[StackExchange.Redis](https://www.nuget.org/packages/StackExchange.Redis/)
and collects traces about outgoing calls to Redis.
**Note: This component is based on the OpenTelemetry semantic conventions for
[traces](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions).
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.StackExchangeRedis
## Step 1: Install Package
Add a reference to the
[`OpenTelemetry.Instrumentation.StackExchangeRedis`](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.StackExchangeRedis)
package. Also, add any other instrumentations & exporters you will need.
```shell
dotnet add package OpenTelemetry.Instrumentation.StackExchangeRedis
```
## Step 2: Enable StackExchange.Redis Instrumentation at application startup
StackExchange.Redis instrumentation must be enabled at application startup.
`AddRedisInstrumentation` method on `TracerProviderBuilder` must be called to
enable Redis instrumentation, passing the `IConnectionMultiplexer` instance used
to make Redis calls. Only those Redis calls made using the same instance of the
`IConnectionMultiplexer` will be instrumented.
The following example demonstrates adding StackExchange.Redis 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)
{
// Connect to the server.
using var connection = ConnectionMultiplexer.Connect("localhost:6379");
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddRedisInstrumentation(connection)
.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 documentation for [OpenTelemetry.Instrumentation.AspNet](../OpenTelemetry.Instrumentation.AspNet/README.md).
## Advanced configuration
This instrumentation can be configured to change the default behavior by using
`StackExchangeRedisCallsInstrumentationOptions`.
### FlushInterval
StackExchange.Redis has its own internal profiler. OpenTelemetry converts each
profiled command from the internal profiler to an Activity for collection. By
default, this conversion process flushes profiled commands on a 10 second
interval. The `FlushInterval` option can be used to adjust this interval.
The following example shows how to use `FlushInterval`.
```csharp
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddRedisInstrumentation(
connection,
options => options.FlushInterval = TimeSpan.FromSeconds(5))
.AddConsoleExporter()
.Build();
```
### SetVerboseDatabaseStatements
StackExchange.Redis by default does not give detailed database statements like
what key or script was used during an operation. The `SetVerboseDatabaseStatements`
option can be used to enable gathering this more detailed information.
The following example shows how to use `SetVerboseDatabaseStatements`.
```csharp
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddRedisInstrumentation(
connection,
options => options.SetVerboseDatabaseStatements = true)
.AddConsoleExporter()
.Build();
```
## Enrich
This option allows one to enrich the activity with additional information from the
raw `IProfiledCommand` object. The `Enrich` action is called only when
`activity.IsAllDataRequested` is `true`. It contains the activity itself (which can
be enriched), and the source profiled command object.
The following code snippet shows how to add additional tags using `Enrich`.
```csharp
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddRedisInstrumentation(opt => opt.Enrich = (activity, command) =>
{
if (command.ElapsedTime < TimeSpan.FromMilliseconds(100))
{
activity.SetTag("is_fast", true);
}
})
.Build();
```
## References
* [OpenTelemetry Project](https://opentelemetry.io/)
* [StackExchange.Redis Profiling](https://stackexchange.github.io/StackExchange.Redis/Profiling_v1.html)

View File

@ -1,151 +0,0 @@
// <copyright file="StackExchangeRedisCallsInstrumentation.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
using StackExchange.Redis;
using StackExchange.Redis.Profiling;
namespace OpenTelemetry.Instrumentation.StackExchangeRedis
{
/// <summary>
/// Redis calls instrumentation.
/// </summary>
internal class StackExchangeRedisCallsInstrumentation : IDisposable
{
internal const string RedisDatabaseIndexKeyName = "db.redis.database_index";
internal const string RedisFlagsKeyName = "db.redis.flags";
internal const string ActivitySourceName = "OpenTelemetry.StackExchange.Redis";
internal const string ActivityName = ActivitySourceName + ".Execute";
internal static readonly Version Version = typeof(StackExchangeRedisCallsInstrumentation).Assembly.GetName().Version;
internal static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString());
internal static readonly IEnumerable<KeyValuePair<string, object>> CreationTags = new[]
{
new KeyValuePair<string, object>(SemanticConventions.AttributeDbSystem, "redis"),
};
internal readonly ConcurrentDictionary<(ActivityTraceId TraceId, ActivitySpanId SpanId), (Activity Activity, ProfilingSession Session)> Cache
= new();
private readonly StackExchangeRedisCallsInstrumentationOptions options;
private readonly EventWaitHandle stopHandle = new(false, EventResetMode.ManualReset);
private readonly Thread drainThread;
private readonly ProfilingSession defaultSession = new();
/// <summary>
/// Initializes a new instance of the <see cref="StackExchangeRedisCallsInstrumentation"/> class.
/// </summary>
/// <param name="connection"><see cref="IConnectionMultiplexer"/> to instrument.</param>
/// <param name="options">Configuration options for redis instrumentation.</param>
public StackExchangeRedisCallsInstrumentation(IConnectionMultiplexer connection, StackExchangeRedisCallsInstrumentationOptions options)
{
Guard.ThrowIfNull(connection);
this.options = options ?? new StackExchangeRedisCallsInstrumentationOptions();
this.drainThread = new Thread(this.DrainEntries)
{
Name = "OpenTelemetry.Redis",
};
this.drainThread.Start();
connection.RegisterProfiler(this.GetProfilerSessionsFactory());
}
/// <summary>
/// Returns session for the Redis calls recording.
/// </summary>
/// <returns>Session associated with the current span context to record Redis calls.</returns>
public Func<ProfilingSession> GetProfilerSessionsFactory()
{
return () =>
{
if (this.stopHandle.WaitOne(0))
{
return null;
}
Activity parent = Activity.Current;
// If no parent use the default session.
if (parent == null || parent.IdFormat != ActivityIdFormat.W3C)
{
return this.defaultSession;
}
// Try to reuse a session for all activities created under the same TraceId+SpanId.
var cacheKey = (parent.TraceId, parent.SpanId);
if (!this.Cache.TryGetValue(cacheKey, out var session))
{
session = (parent, new ProfilingSession());
this.Cache.TryAdd(cacheKey, session);
}
return session.Session;
};
}
/// <inheritdoc/>
public void Dispose()
{
this.stopHandle.Set();
this.drainThread.Join();
this.Flush();
this.stopHandle.Dispose();
}
internal void Flush()
{
RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.options);
foreach (var entry in this.Cache)
{
var parent = entry.Value.Activity;
if (parent.Duration == TimeSpan.Zero)
{
// Activity is still running, don't drain.
continue;
}
ProfilingSession session = entry.Value.Session;
RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling(), this.options);
this.Cache.TryRemove((entry.Key.TraceId, entry.Key.SpanId), out _);
}
}
private void DrainEntries(object state)
{
while (true)
{
if (this.stopHandle.WaitOne(this.options.FlushInterval))
{
break;
}
this.Flush();
}
}
}
}

View File

@ -1,48 +0,0 @@
// <copyright file="StackExchangeRedisCallsInstrumentationOptions.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Diagnostics;
using OpenTelemetry.Trace;
using StackExchange.Redis.Profiling;
namespace OpenTelemetry.Instrumentation.StackExchangeRedis
{
/// <summary>
/// Options for StackExchange.Redis instrumentation.
/// </summary>
public class StackExchangeRedisCallsInstrumentationOptions
{
/// <summary>
/// Gets or sets the maximum time that should elapse between flushing the internal buffer of Redis profiling sessions and creating <see cref="Activity"/> objects. Default value: 00:00:10.
/// </summary>
public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(10);
/// <summary>
/// Gets or sets a value indicating whether or not the <see cref="StackExchangeRedisCallsInstrumentation"/> should use reflection to get more detailed <see cref="SemanticConventions.AttributeDbStatement"/> tag values. Default value: False.
/// </summary>
public bool SetVerboseDatabaseStatements { get; set; }
/// <summary>
/// Gets or sets an action to enrich an Activity.
/// </summary>
/// <remarks>
/// <para><see cref="Activity"/>: the activity being enriched.</para>
/// <para><see cref="IProfiledCommand"/>: the profiled redis command from which additional information can be extracted to enrich the activity.</para>
/// </remarks>
public Action<Activity, IProfiledCommand> Enrich { get; set; }
}
}

View File

@ -1,90 +0,0 @@
// <copyright file="TracerProviderBuilderExtensions.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using OpenTelemetry.Instrumentation.StackExchangeRedis;
using OpenTelemetry.Internal;
using StackExchange.Redis;
namespace OpenTelemetry.Trace
{
/// <summary>
/// Extension methods to simplify registering of dependency instrumentation.
/// </summary>
public static class TracerProviderBuilderExtensions
{
/// <summary>
/// Enables automatic data collection of outgoing requests to Redis.
/// </summary>
/// <remarks>
/// Note: If an <see cref="IConnectionMultiplexer"/> is not supplied
/// using the <paramref name="connection"/> parameter it will be
/// resolved using the application <see cref="IServiceProvider"/>.
/// </remarks>
/// <param name="builder"><see cref="TracerProviderBuilder"/> being configured.</param>
/// <param name="connection">Optional <see cref="IConnectionMultiplexer"/> to instrument.</param>
/// <param name="configure">Optional callback to configure options.</param>
/// <returns>The instance of <see cref="TracerProviderBuilder"/> to chain the calls.</returns>
public static TracerProviderBuilder AddRedisInstrumentation(
this TracerProviderBuilder builder,
IConnectionMultiplexer connection = null,
Action<StackExchangeRedisCallsInstrumentationOptions> configure = null)
{
Guard.ThrowIfNull(builder);
if (builder is not IDeferredTracerProviderBuilder deferredTracerProviderBuilder)
{
if (connection == null)
{
throw new NotSupportedException($"StackExchange.Redis {nameof(IConnectionMultiplexer)} must be supplied when dependency injection is unavailable - to enable dependency injection use the OpenTelemetry.Extensions.Hosting package");
}
return AddRedisInstrumentation(builder, connection, new StackExchangeRedisCallsInstrumentationOptions(), configure);
}
return deferredTracerProviderBuilder.Configure((sp, builder) =>
{
if (connection == null)
{
connection = (IConnectionMultiplexer)sp.GetService(typeof(IConnectionMultiplexer));
if (connection == null)
{
throw new InvalidOperationException($"StackExchange.Redis {nameof(IConnectionMultiplexer)} could not be resolved through application {nameof(IServiceProvider)}");
}
}
AddRedisInstrumentation(
builder,
connection,
sp.GetOptions<StackExchangeRedisCallsInstrumentationOptions>(),
configure);
});
}
private static TracerProviderBuilder AddRedisInstrumentation(
TracerProviderBuilder builder,
IConnectionMultiplexer connection,
StackExchangeRedisCallsInstrumentationOptions options,
Action<StackExchangeRedisCallsInstrumentationOptions> configure = null)
{
configure?.Invoke(options);
return builder
.AddInstrumentation(() => new StackExchangeRedisCallsInstrumentation(connection, options))
.AddSource(StackExchangeRedisCallsInstrumentation.ActivitySourceName);
}
}
}

View File

@ -1,17 +0,0 @@
# Create a container for running the OpenTelemetry Redis integration tests.
# This should be run from the root of the repo:
# docker build --file test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Dockerfile .
ARG SDK_VERSION=6.0
FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS build
ARG PUBLISH_CONFIGURATION=Release
ARG PUBLISH_FRAMEWORK=net6.0
WORKDIR /repo
COPY . ./
WORKDIR "/repo/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests"
RUN dotnet publish "OpenTelemetry.Instrumentation.StackExchangeRedis.Tests.csproj" -c "${PUBLISH_CONFIGURATION}" -f "${PUBLISH_FRAMEWORK}" -o /drop -p:IntegrationBuild=true -p:TARGET_FRAMEWORK=${PUBLISH_FRAMEWORK}
FROM mcr.microsoft.com/dotnet/sdk:${SDK_VERSION} AS final
WORKDIR /test
COPY --from=build /drop .
ENTRYPOINT ["dotnet", "vstest", "OpenTelemetry.Instrumentation.StackExchangeRedis.Tests.dll"]

View File

@ -1,181 +0,0 @@
// <copyright file="RedisProfilerEntryToActivityConverterTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using Moq;
using OpenTelemetry.Trace;
using StackExchange.Redis;
using StackExchange.Redis.Profiling;
using Xunit;
namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation
{
[Collection("Redis")]
public class RedisProfilerEntryToActivityConverterTests : IDisposable
{
private readonly ConnectionMultiplexer connection;
private readonly IDisposable tracerProvider;
public RedisProfilerEntryToActivityConverterTests()
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = false,
};
connectionOptions.EndPoints.Add("localhost:6379");
this.connection = ConnectionMultiplexer.Connect(connectionOptions);
this.tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddRedisInstrumentation(this.connection)
.Build();
}
public void Dispose()
{
this.tracerProvider.Dispose();
this.connection.Dispose();
GC.SuppressFinalize(this);
}
[Fact]
public void ProfilerCommandToActivity_UsesCommandAsName()
{
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.Command).Returns("SET");
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.Equal("SET", result.DisplayName);
}
[Fact]
public void ProfilerCommandToActivity_UsesTimestampAsStartTime()
{
var now = DateTimeOffset.Now;
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(now.DateTime);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.Equal(now, result.StartTimeUtc);
}
[Fact]
public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis()
{
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbSystem));
Assert.Equal("redis", result.GetTagValue(SemanticConventions.AttributeDbSystem));
}
[Fact]
public void ProfilerCommandToActivity_UsesCommandAsDbStatementAttribute()
{
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.Command).Returns("SET");
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbStatement));
Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbStatement));
}
[Fact]
public void ProfilerCommandToActivity_UsesFlagsForFlagsAttribute()
{
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
var expectedFlags = CommandFlags.FireAndForget |
CommandFlags.NoRedirect;
profiledCommand.Setup(m => m.Flags).Returns(expectedFlags);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.NotNull(result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName));
Assert.Equal("PreferMaster, FireAndForget, NoRedirect", result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName));
}
[Fact]
public void ProfilerCommandToActivity_UsesIpEndPointAsEndPoint()
{
long address = 1;
int port = 2;
var activity = new Activity("redis-profiler");
IPEndPoint ipLocalEndPoint = new IPEndPoint(address, port);
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.EndPoint).Returns(ipLocalEndPoint);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerIp));
Assert.Equal($"{address}.0.0.0", result.GetTagValue(SemanticConventions.AttributeNetPeerIp));
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerPort));
Assert.Equal(port, result.GetTagValue(SemanticConventions.AttributeNetPeerPort));
}
[Fact]
public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint()
{
var dnsEndPoint = new DnsEndPoint("https://opentelemetry.io/", 443);
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.EndPoint).Returns(dnsEndPoint);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerName));
Assert.Equal(dnsEndPoint.Host, result.GetTagValue(SemanticConventions.AttributeNetPeerName));
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerPort));
Assert.Equal(dnsEndPoint.Port, result.GetTagValue(SemanticConventions.AttributeNetPeerPort));
}
#if !NETFRAMEWORK
[Fact]
public void ProfilerCommandToActivity_UsesOtherEndPointAsEndPoint()
{
var unixEndPoint = new UnixDomainSocketEndPoint("https://opentelemetry.io/");
var activity = new Activity("redis-profiler");
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.EndPoint).Returns(unixEndPoint);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService));
Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService));
}
#endif
}
}

View File

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Unit test project for OpenTelemetry StackExchangeRedis instrumentation</Description>
<!-- OmniSharp/VS Code requires TargetFrameworks to be in descending order for IntelliSense and analysis. -->
<TargetFrameworks Condition="$(TARGET_FRAMEWORK) == ''">net6.0;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks Condition="$(TARGET_FRAMEWORK) == '' and $(OS) == 'Windows_NT'">$(TargetFrameworks);net462</TargetFrameworks>
<TargetFrameworks Condition="$(TARGET_FRAMEWORK) != ''">$(TARGET_FRAMEWORK)</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\SkipUnlessEnvVarFoundTheoryAttribute.cs" Link="Implementation\SkipUnlessEnvVarFoundTheoryAttribute.cs" />
<Compile Include="$(RepoRoot)\test\OpenTelemetry.Tests\Shared\TestSampler.cs" Link="TestSampler.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.StackExchangeRedis\OpenTelemetry.Instrumentation.StackExchangeRedis.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Extensions.Hosting\OpenTelemetry.Extensions.Hosting.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPkgVer)" />
<PackageReference Include="Moq" Version="$(MoqPkgVer)" />
<PackageReference Include="xunit" Version="$(XUnitPkgVer)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioPkgVer)">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsHostingPkgVer)" />
<DotNetCliToolReference Include="dotnet-xunit" Version="$(DotNetXUnitCliVer)" />
</ItemGroup>
</Project>

View File

@ -1,417 +0,0 @@
// <copyright file="StackExchangeRedisCallsInstrumentationTests.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>
using System;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenTelemetry.Tests;
using OpenTelemetry.Trace;
using StackExchange.Redis;
using StackExchange.Redis.Profiling;
using Xunit;
namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Tests
{
[Collection("Redis")]
public class StackExchangeRedisCallsInstrumentationTests
{
/*
To run the integration tests, set the OTEL_REDISENDPOINT machine-level environment variable to a valid Redis endpoint.
To use Docker...
1) Run: docker run -d --name redis -p 6379:6379 redis
2) Set OTEL_REDISENDPOINT as: localhost:6379
*/
private const string RedisEndPointEnvVarName = "OTEL_REDISENDPOINT";
private static readonly string RedisEndPoint = SkipUnlessEnvVarFoundTheoryAttribute.GetEnvironmentVariable(RedisEndPointEnvVarName);
[Trait("CategoryName", "RedisIntegrationTests")]
[SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)]
[InlineData("value1")]
public void SuccessfulCommandTestWithKey(string value)
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = true,
};
connectionOptions.EndPoints.Add(RedisEndPoint);
using var connection = ConnectionMultiplexer.Connect(connectionOptions);
var db = connection.GetDatabase();
db.KeyDelete("key1");
var activityProcessor = new Mock<BaseProcessor<Activity>>();
var sampler = new TestSampler();
using (Sdk.CreateTracerProviderBuilder()
.AddProcessor(activityProcessor.Object)
.SetSampler(sampler)
.AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = true)
.Build())
{
var prepared = LuaScript.Prepare("redis.call('set', @key, @value)");
db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 });
var redisValue = db.StringGet("key1");
Assert.False(redisValue.HasValue);
bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60));
Assert.True(set);
redisValue = db.StringGet("key1");
Assert.True(redisValue.HasValue);
Assert.Equal(value, redisValue.ToString());
}
// Disposing SDK should flush the Redis profiling session immediately.
Assert.Equal(11, activityProcessor.Invocations.Count);
var scriptActivity = (Activity)activityProcessor.Invocations[1].Arguments[0];
Assert.Equal("EVAL", scriptActivity.DisplayName);
Assert.Equal("EVAL redis.call('set', ARGV[1], ARGV[2])", scriptActivity.GetTagValue(SemanticConventions.AttributeDbStatement));
VerifyActivityData((Activity)activityProcessor.Invocations[3].Arguments[0], false, connection.GetEndPoints()[0], true);
VerifyActivityData((Activity)activityProcessor.Invocations[5].Arguments[0], true, connection.GetEndPoints()[0], true);
VerifyActivityData((Activity)activityProcessor.Invocations[7].Arguments[0], false, connection.GetEndPoints()[0], true);
VerifySamplingParameters(sampler.LatestSamplingParameters);
}
[Trait("CategoryName", "RedisIntegrationTests")]
[SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)]
[InlineData("value1")]
public void SuccessfulCommandTest(string value)
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = true,
};
connectionOptions.EndPoints.Add(RedisEndPoint);
using var connection = ConnectionMultiplexer.Connect(connectionOptions);
var activityProcessor = new Mock<BaseProcessor<Activity>>();
var sampler = new TestSampler();
using (Sdk.CreateTracerProviderBuilder()
.AddProcessor(activityProcessor.Object)
.SetSampler(sampler)
.AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = false)
.Build())
{
var db = connection.GetDatabase();
bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60));
Assert.True(set);
var redisValue = db.StringGet("key1");
Assert.True(redisValue.HasValue);
Assert.Equal(value, redisValue.ToString());
}
// Disposing SDK should flush the Redis profiling session immediately.
Assert.Equal(7, activityProcessor.Invocations.Count);
VerifyActivityData((Activity)activityProcessor.Invocations[1].Arguments[0], true, connection.GetEndPoints()[0], false);
VerifyActivityData((Activity)activityProcessor.Invocations[3].Arguments[0], false, connection.GetEndPoints()[0], false);
VerifySamplingParameters(sampler.LatestSamplingParameters);
}
[Fact]
public async void ProfilerSessionUsesTheSameDefault()
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = false,
};
connectionOptions.EndPoints.Add("localhost:6379");
var connection = ConnectionMultiplexer.Connect(connectionOptions);
using var instrumentation = new StackExchangeRedisCallsInstrumentation(connection, new StackExchangeRedisCallsInstrumentationOptions());
var profilerFactory = instrumentation.GetProfilerSessionsFactory();
var first = profilerFactory();
var second = profilerFactory();
ProfilingSession third = null;
await Task.Delay(1).ContinueWith((t) => { third = profilerFactory(); });
Assert.Equal(first, second);
Assert.Equal(second, third);
}
[Trait("CategoryName", "RedisIntegrationTests")]
[SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)]
[InlineData("value1")]
public void CanEnrichActivityFromCommand(string value)
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = true,
};
connectionOptions.EndPoints.Add(RedisEndPoint);
using var connection = ConnectionMultiplexer.Connect(connectionOptions);
var activityProcessor = new Mock<BaseProcessor<Activity>>();
var sampler = new TestSampler();
using (Sdk.CreateTracerProviderBuilder()
.AddProcessor(activityProcessor.Object)
.SetSampler(sampler)
.AddRedisInstrumentation(connection, c => c.Enrich = (activity, command) =>
{
if (command.ElapsedTime < TimeSpan.FromMilliseconds(100))
{
activity.AddTag("is_fast", true);
}
})
.Build())
{
var db = connection.GetDatabase();
bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60));
Assert.True(set);
var redisValue = db.StringGet("key1");
Assert.True(redisValue.HasValue);
Assert.Equal(value, redisValue.ToString());
}
// Disposing SDK should flush the Redis profiling session immediately.
Assert.Equal(7, activityProcessor.Invocations.Count);
var setActivity = (Activity)activityProcessor.Invocations[1].Arguments[0];
Assert.Equal(true, setActivity.GetTagValue("is_fast"));
var getActivity = (Activity)activityProcessor.Invocations[3].Arguments[0];
Assert.Equal(true, getActivity.GetTagValue("is_fast"));
}
[Fact]
public void CheckCacheIsFlushedProperly()
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = false,
};
connectionOptions.EndPoints.Add("localhost:6379");
var connection = ConnectionMultiplexer.Connect(connectionOptions);
using var instrumentation = new StackExchangeRedisCallsInstrumentation(connection, new StackExchangeRedisCallsInstrumentationOptions());
var profilerFactory = instrumentation.GetProfilerSessionsFactory();
// start a root level activity
using Activity rootActivity = new Activity("Parent")
.SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded)
.Start();
Assert.NotNull(rootActivity.Id);
// get an initial profiler from root activity
Activity.Current = rootActivity;
ProfilingSession profiler0 = profilerFactory();
// expect different result from synchronous child activity
ProfilingSession profiler1;
using (Activity.Current = new Activity("Child-Span-1").SetParentId(rootActivity.Id).Start())
{
profiler1 = profilerFactory();
Assert.NotSame(profiler0, profiler1);
}
rootActivity.Stop();
rootActivity.Dispose();
instrumentation.Flush();
Assert.Empty(instrumentation.Cache);
}
[Fact]
public async Task ProfilerSessionsHandleMultipleSpans()
{
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = false,
};
connectionOptions.EndPoints.Add("localhost:6379");
var connection = ConnectionMultiplexer.Connect(connectionOptions);
using var instrumentation = new StackExchangeRedisCallsInstrumentation(connection, new StackExchangeRedisCallsInstrumentationOptions());
var profilerFactory = instrumentation.GetProfilerSessionsFactory();
// start a root level activity
using Activity rootActivity = new Activity("Parent")
.SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded)
.Start();
Assert.NotNull(rootActivity.Id);
// get an initial profiler from root activity
Activity.Current = rootActivity;
ProfilingSession profiler0 = profilerFactory();
// expect different result from synchronous child activity
ProfilingSession profiler1;
using (Activity.Current = new Activity("Child-Span-1").SetParentId(rootActivity.Id).Start())
{
profiler1 = profilerFactory();
Assert.NotSame(profiler0, profiler1);
}
Activity.Current = rootActivity;
// expect different result from asynchronous child activity
using (Activity.Current = new Activity("Child-Span-2").SetParentId(rootActivity.Id).Start())
{
// lose async context on purpose
await Task.Delay(100).ConfigureAwait(false);
ProfilingSession profiler2 = profilerFactory();
Assert.NotSame(profiler0, profiler2);
Assert.NotSame(profiler1, profiler2);
}
Activity.Current = rootActivity;
// ensure same result back in root activity
ProfilingSession profiles3 = profilerFactory();
Assert.Same(profiler0, profiles3);
}
[Fact]
public void StackExchangeRedis_BadArgs()
{
TracerProviderBuilder builder = null;
Assert.Throws<ArgumentNullException>(() => builder.AddRedisInstrumentation(null));
var activityProcessor = new Mock<BaseProcessor<Activity>>();
Assert.Throws<NotSupportedException>(() =>
Sdk.CreateTracerProviderBuilder()
.AddProcessor(activityProcessor.Object)
.AddRedisInstrumentation(null)
.Build());
}
[Fact]
public void StackExchangeRedis_DependencyInjection_Success()
{
bool connectionMultiplexerPickedFromDI = false;
bool optionsPickedFromDI = false;
var connectionOptions = new ConfigurationOptions
{
AbortOnConnectFail = false,
};
connectionOptions.EndPoints.Add("localhost");
var services = new ServiceCollection();
services.AddSingleton<IConnectionMultiplexer>((sp) =>
{
connectionMultiplexerPickedFromDI = true;
return ConnectionMultiplexer.Connect(connectionOptions);
});
services.Configure<StackExchangeRedisCallsInstrumentationOptions>(options =>
{
optionsPickedFromDI = true;
});
services.AddOpenTelemetryTracing(builder => builder.AddRedisInstrumentation());
using var serviceProvider = services.BuildServiceProvider();
var tracerProvider = serviceProvider.GetRequiredService<TracerProvider>();
Assert.True(connectionMultiplexerPickedFromDI);
Assert.True(optionsPickedFromDI);
}
[Fact]
public void StackExchangeRedis_DependencyInjection_Failure()
{
var services = new ServiceCollection();
services.AddOpenTelemetryTracing(builder => builder.AddRedisInstrumentation());
using var serviceProvider = services.BuildServiceProvider();
Assert.Throws<InvalidOperationException>(() => serviceProvider.GetRequiredService<TracerProvider>());
}
private static void VerifyActivityData(Activity activity, bool isSet, EndPoint endPoint, bool setCommandKey = false)
{
if (isSet)
{
Assert.Equal("SETEX", activity.DisplayName);
if (setCommandKey)
{
Assert.Equal("SETEX key1", activity.GetTagValue(SemanticConventions.AttributeDbStatement));
}
else
{
Assert.Equal("SETEX", activity.GetTagValue(SemanticConventions.AttributeDbStatement));
}
}
else
{
Assert.Equal("GET", activity.DisplayName);
if (setCommandKey)
{
Assert.Equal("GET key1", activity.GetTagValue(SemanticConventions.AttributeDbStatement));
}
else
{
Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeDbStatement));
}
}
Assert.Equal(Status.Unset, activity.GetStatus());
Assert.Equal("redis", activity.GetTagValue(SemanticConventions.AttributeDbSystem));
Assert.Equal(0, activity.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisDatabaseIndexKeyName));
if (endPoint is IPEndPoint ipEndPoint)
{
Assert.Equal(ipEndPoint.Address.ToString(), activity.GetTagValue(SemanticConventions.AttributeNetPeerIp));
Assert.Equal(ipEndPoint.Port, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
}
else if (endPoint is DnsEndPoint dnsEndPoint)
{
Assert.Equal(dnsEndPoint.Host, activity.GetTagValue(SemanticConventions.AttributeNetPeerName));
Assert.Equal(dnsEndPoint.Port, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort));
}
else
{
Assert.Equal(endPoint.ToString(), activity.GetTagValue(SemanticConventions.AttributePeerService));
}
}
private static void VerifySamplingParameters(SamplingParameters samplingParameters)
{
Assert.NotNull(samplingParameters.Tags);
Assert.Contains(
samplingParameters.Tags,
kvp => kvp.Key == SemanticConventions.AttributeDbSystem
&& (string)kvp.Value == "redis");
}
}
}

View File

@ -1,20 +0,0 @@
# Start a redis container and then run OpenTelemetry redis integration tests.
# This should be run from the root of the repo:
# opentelemetry>docker-compose --file=test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/docker-compose.yml --project-directory=. up --exit-code-from=tests --build
version: '3.7'
services:
redis:
image: redis
ports:
- "6379:6379"
tests:
build:
context: .
dockerfile: ./test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Dockerfile
command: --TestCaseFilter:CategoryName=RedisIntegrationTests
environment:
- OTEL_REDISENDPOINT=redis:6379
depends_on:
- redis