Remove StackExchangeRedis Instrumenation (#3346)
Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
parent
8df82a2d7f
commit
c90ab4a2f2
|
|
@ -14,14 +14,6 @@ on:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
redis-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
version: [netcoreapp3.1,net6.0]
|
|
||||||
steps:
|
|
||||||
- run: 'echo "No build required"'
|
|
||||||
|
|
||||||
sql-test:
|
sql-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
|
|
||||||
|
|
@ -11,18 +11,6 @@ on:
|
||||||
- '**.md'
|
- '**.md'
|
||||||
|
|
||||||
jobs:
|
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:
|
sql-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testdata", "testdata", "{77
|
||||||
EndProject
|
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}"
|
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
|
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}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{E359BB2B-9AEC-497D-B321-7DF2450C3B8E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Exporter.Jaeger", "src\OpenTelemetry.Exporter.Jaeger\OpenTelemetry.Exporter.Jaeger.csproj", "{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}"
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{F2F81E76-6A0E-466B-B673-EBBF1A9ED075}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Debug|Any CPU.Build.0 = 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
|
{8D47E3CF-9AE3-42FE-9084-FEB72D9AD769}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,6 @@ libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/ma
|
||||||
* [ASP.NET Core](./src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
|
* [ASP.NET Core](./src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
|
||||||
* [Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
|
* [Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
|
||||||
* [HTTP clients](./src/OpenTelemetry.Instrumentation.Http/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)
|
* [SQL client](./src/OpenTelemetry.Instrumentation.SqlClient/README.md)
|
||||||
|
|
||||||
Here are the [exporter
|
Here are the [exporter
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@
|
||||||
<NewtonsoftJsonPkgVer>[12.0.2,13.0)</NewtonsoftJsonPkgVer>
|
<NewtonsoftJsonPkgVer>[12.0.2,13.0)</NewtonsoftJsonPkgVer>
|
||||||
<MoqPkgVer>[4.14.5,5.0)</MoqPkgVer>
|
<MoqPkgVer>[4.14.5,5.0)</MoqPkgVer>
|
||||||
<RabbitMQClientPkgVer>[6.1.0,7.0)</RabbitMQClientPkgVer>
|
<RabbitMQClientPkgVer>[6.1.0,7.0)</RabbitMQClientPkgVer>
|
||||||
<StackExchangeRedisPkgVer>[2.1.58,3.0)</StackExchangeRedisPkgVer>
|
|
||||||
<SwashbuckleAspNetCorePkgVer>[6.2.3]</SwashbuckleAspNetCorePkgVer>
|
<SwashbuckleAspNetCorePkgVer>[6.2.3]</SwashbuckleAspNetCorePkgVer>
|
||||||
<XUnitRunnerVisualStudioPkgVer>[2.4.3,3.0)</XUnitRunnerVisualStudioPkgVer>
|
<XUnitRunnerVisualStudioPkgVer>[2.4.3,3.0)</XUnitRunnerVisualStudioPkgVer>
|
||||||
<XUnitPkgVer>[2.4.1,3.0)</XUnitPkgVer>
|
<XUnitPkgVer>[2.4.1,3.0)</XUnitPkgVer>
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@
|
||||||
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
|
<MicrosoftSourceLinkGitHubPkgVer>[1.0.0,2.0)</MicrosoftSourceLinkGitHubPkgVer>
|
||||||
<OpenTracingPkgVer>[0.12.1,0.13)</OpenTracingPkgVer>
|
<OpenTracingPkgVer>[0.12.1,0.13)</OpenTracingPkgVer>
|
||||||
<OTelPreviousStableVer>1.3.0</OTelPreviousStableVer>
|
<OTelPreviousStableVer>1.3.0</OTelPreviousStableVer>
|
||||||
<StackExchangeRedisPkgVer>[2.1.58,3.0)</StackExchangeRedisPkgVer>
|
|
||||||
<StyleCopAnalyzersPkgVer>[1.2.0-beta.354,2.0)</StyleCopAnalyzersPkgVer>
|
<StyleCopAnalyzersPkgVer>[1.2.0-beta.354,2.0)</StyleCopAnalyzersPkgVer>
|
||||||
<SystemCollectionsImmutablePkgVer>1.4.0</SystemCollectionsImmutablePkgVer>
|
<SystemCollectionsImmutablePkgVer>1.4.0</SystemCollectionsImmutablePkgVer>
|
||||||
<SystemDiagnosticSourcePkgVer>6.0.0</SystemDiagnosticSourcePkgVer>
|
<SystemDiagnosticSourcePkgVer>6.0.0</SystemDiagnosticSourcePkgVer>
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ may be used as a reference.
|
||||||
|
|
||||||
The [inspiration of the OpenTelemetry
|
The [inspiration of the OpenTelemetry
|
||||||
project](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#instrumentation-libraries)
|
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
|
is to make every library observable out of the box by having them call
|
||||||
them call OpenTelemetry API directly. However, many libraries will not have such
|
OpenTelemetry API directly. However, many libraries will not have such
|
||||||
integration, and as such there is a need for a separate library which would
|
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
|
inject such calls, using mechanisms such as wrapping interfaces, subscribing to
|
||||||
library-specific callbacks, or translating existing telemetry into OpenTelemetry
|
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.
|
the library they instrument, and steps for enabling them.
|
||||||
|
|
||||||
* [ASP.NET](../../../src/OpenTelemetry.Instrumentation.AspNet/README.md)
|
* [ASP.NET](../../../src/OpenTelemetry.Instrumentation.AspNet/README.md)
|
||||||
* [ASP.NET Core](../../../src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
|
* [ASP.NET
|
||||||
* [gRPC client](../../../src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
|
Core](../../../src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
|
||||||
|
* [gRPC
|
||||||
|
client](../../../src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
|
||||||
* [HTTP clients](../../../src/OpenTelemetry.Instrumentation.Http/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)
|
* [SQL client](../../../src/OpenTelemetry.Instrumentation.SqlClient/README.md)
|
||||||
|
|
||||||
More community contributed instrumentations are available in [OpenTelemetry .NET
|
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.
|
Writing an instrumentation library typically involves 3 steps.
|
||||||
|
|
||||||
1. First step involves "hijacking" into the target library. The exact mechanism
|
1. First step involves "hijacking" into the target library. The exact mechanism
|
||||||
of this depends on the target library itself. For example, StackExchangeRedis
|
of this depends on the target library itself. For example,
|
||||||
library allows hooks into the library, and the [StackExchangeRedis
|
System.Data.SqlClient for .NET Framework, which publishes events using
|
||||||
instrumentation
|
`EventSource`. The [SqlClient 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
|
|
||||||
library](../../../src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs),
|
library](../../../src/OpenTelemetry.Instrumentation.SqlClient/Implementation/SqlEventSourceListener.netfx.cs),
|
||||||
in this case subscribes to the `EventSource` callbacks.
|
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
|
target instrumented library. Irrespective of the actual mechanism used in
|
||||||
first step, this should be uniform across all instrumentation libraries. The
|
first step, this should be uniform across all instrumentation libraries. The
|
||||||
`ActivitySource` must be created using the name and version of the
|
`ActivitySource` must be created using the name and version of the
|
||||||
instrumentation library (eg:
|
instrumentation library (eg: "OpenTelemetry.Instrumentation.Http") and *not*
|
||||||
"OpenTelemetry.Instrumentation.StackExchangeRedis") and *not* the
|
the instrumented library (eg: "System.Net.Http")
|
||||||
instrumented library (eg: "StackExchange.Redis")
|
1. [Context
|
||||||
1. [Context Propagation](../../../src/OpenTelemetry.Api/README.md#context-propagation):
|
Propagation](../../../src/OpenTelemetry.Api/README.md#context-propagation):
|
||||||
If your library initiates out of process requests or
|
If your library initiates out of process requests or accepts them, the
|
||||||
accepts them, the library needs to
|
library needs to [inject the
|
||||||
[inject the `PropagationContext`](../../../examples/MicroserviceExample/Utils/Messaging/MessageSender.cs)
|
`PropagationContext`](../../../examples/MicroserviceExample/Utils/Messaging/MessageSender.cs)
|
||||||
to outgoing requests and
|
to outgoing requests and [extract the
|
||||||
[extract the context](../../../examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs)
|
context](../../../examples/MicroserviceExample/Utils/Messaging/MessageReceiver.cs)
|
||||||
and hydrate the Activity/Baggage upon receiving incoming requests.
|
and hydrate the Activity/Baggage upon receiving incoming requests. This is
|
||||||
This is only required if you're using your own protocol to
|
only required if you're using your own protocol to communicate over the
|
||||||
communicate over the wire.
|
wire. (i.e. If you're using an already instrumented HttpClient or
|
||||||
(i.e. If you're using an already instrumented HttpClient or GrpcClient,
|
GrpcClient, this is already provided to you and **do not require**
|
||||||
this is already provided to you and **do not require**
|
|
||||||
injecting/extracting `PropagationContext` explicitly again.)
|
injecting/extracting `PropagationContext` explicitly again.)
|
||||||
|
|
||||||
3. Third step is an optional step, and involves providing extension methods on
|
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
|
extension method on `TracerProviderBuilder`. Inside this extension
|
||||||
method, it should call the `AddInstrumentation` method, and `AddSource`
|
method, it should call the `AddInstrumentation` method, and `AddSource`
|
||||||
method to enable its ActivitySource for the provider. An example
|
method to enable its ActivitySource for the provider. An example
|
||||||
instrumentation using this approach is [StackExchangeRedis
|
instrumentation using this approach is [SqlClient
|
||||||
instrumentation](../../../src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs)
|
instrumentation](../../../src/OpenTelemetry.Instrumentation.SqlClient/TracerProviderBuilderExtensions.cs)
|
||||||
|
|
||||||
2. If the instrumentation library does not requires any state management
|
2. If the instrumentation library does not requires any state management
|
||||||
tied to that of `TracerProvider`, then providing `TracerProviderBuilder`
|
tied to that of `TracerProvider`, then providing `TracerProviderBuilder`
|
||||||
|
|
@ -326,16 +321,16 @@ A demo sampler is shown [here](./MySampler.cs).
|
||||||
|
|
||||||
## Resource Detector
|
## Resource Detector
|
||||||
|
|
||||||
OpenTelemetry .NET SDK provides a resource detector for detecting
|
OpenTelemetry .NET SDK provides a resource detector for detecting resource
|
||||||
resource information from the `OTEL_RESOURCE_ATTRIBUTES` and
|
information from the `OTEL_RESOURCE_ATTRIBUTES` and `OTEL_SERVICE_NAME`
|
||||||
`OTEL_SERVICE_NAME` environment variables.
|
environment variables.
|
||||||
|
|
||||||
Custom resource detectors can be implemented:
|
Custom resource detectors can be implemented:
|
||||||
|
|
||||||
* ResourceDetectors should inherit from
|
* ResourceDetectors should inherit from
|
||||||
`OpenTelemetry.Resources.IResourceDetector`, (which belongs
|
`OpenTelemetry.Resources.IResourceDetector`, (which belongs to the
|
||||||
to the [OpenTelemetry](../../../src/OpenTelemetry/README.md)
|
[OpenTelemetry](../../../src/OpenTelemetry/README.md) package), and implement
|
||||||
package), and implement the `Detect` method.
|
the `Detect` method.
|
||||||
|
|
||||||
A demo ResourceDetector is shown [here](./MyResourceDetector.cs).
|
A demo ResourceDetector is shown [here](./MyResourceDetector.cs).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="StackExchange.Redis" Version="$(StackExchangeRedisPkgVer)" />
|
|
||||||
|
|
||||||
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\SpanAttributeConstants.cs" Link="Includes\SpanAttributeConstants.cs" />
|
<Compile Include="$(RepoRoot)\src\OpenTelemetry.Api\Internal\SpanAttributeConstants.cs" Link="Includes\SpanAttributeConstants.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
@ -27,7 +26,6 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Instrumentation.GrpcNetClient\OpenTelemetry.Instrumentation.GrpcNetClient.csproj" />
|
<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.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.Console\OpenTelemetry.Exporter.Console.csproj" />
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.OpenTelemetryProtocol\OpenTelemetry.Exporter.OpenTelemetryProtocol.csproj" />
|
||||||
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.ZPages\OpenTelemetry.Exporter.ZPages.csproj" />
|
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.ZPages\OpenTelemetry.Exporter.ZPages.csproj" />
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Examples.Console
|
||||||
/// <param name="args">Arguments from command line.</param>
|
/// <param name="args">Arguments from command line.</param>
|
||||||
public static void Main(string[] args)
|
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(
|
.MapResult(
|
||||||
(JaegerOptions options) => TestJaegerExporter.Run(options.Host, options.Port),
|
(JaegerOptions options) => TestJaegerExporter.Run(options.Host, options.Port),
|
||||||
(ZipkinOptions options) => TestZipkinExporter.Run(options.Uri),
|
(ZipkinOptions options) => TestZipkinExporter.Run(options.Uri),
|
||||||
|
|
@ -55,7 +55,6 @@ namespace Examples.Console
|
||||||
(LogsOptions options) => TestLogs.Run(options),
|
(LogsOptions options) => TestLogs.Run(options),
|
||||||
(GrpcNetClientOptions options) => TestGrpcNetClient.Run(),
|
(GrpcNetClientOptions options) => TestGrpcNetClient.Run(),
|
||||||
(HttpClientOptions options) => TestHttpClient.Run(),
|
(HttpClientOptions options) => TestHttpClient.Run(),
|
||||||
(RedisOptions options) => TestRedis.Run(options.Uri),
|
|
||||||
(ZPagesOptions options) => TestZPagesExporter.Run(),
|
(ZPagesOptions options) => TestZPagesExporter.Run(),
|
||||||
(ConsoleOptions options) => TestConsoleExporter.Run(options),
|
(ConsoleOptions options) => TestConsoleExporter.Run(options),
|
||||||
(OpenTelemetryShimOptions options) => TestOTelShimWithConsoleExporter.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")]
|
[Verb("zpages", HelpText = "Specify the options required to test ZPages")]
|
||||||
internal class ZPagesOptions
|
internal class ZPagesOptions
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
# StackExchange.Redis Instrumentation for OpenTelemetry
|
|
||||||
|
|
||||||
[](https://www.nuget.org/packages/OpenTelemetry.Instrumentation.StackExchangeRedis)
|
|
||||||
[](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)
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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"]
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
Loading…
Reference in New Issue