Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
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.SetCommandKey.get -> bool
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetCommandKey.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
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
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.SetCommandKey.get -> bool
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetCommandKey.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
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,50 @@
// 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 Fasterflect;
using OpenTelemetry.Trace;
using StackExchange.Redis.Profiling;

namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation
{
internal static class RedisProfilerEntryToActivityConverter
{
public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command)
private static readonly Lazy<Func<object, string>> CommandAndKeyGetter = new Lazy<Func<object, string>>(() =>
{
var redisAssembly = typeof(IProfiledCommand).Assembly;
Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand");
Type messageType = redisAssembly.GetType("StackExchange.Redis.Message");

var messageDelegate = profiledCommandType.DelegateForGetFieldValue("Message", Flags.NonPublic | Flags.Instance);
var commandAndKeyDelegate = messageType.DelegateForGetPropertyValue("CommandAndKey");

if (messageDelegate == null || commandAndKeyDelegate == null)
{
return new Func<object, string>(source => null);
}

return new Func<object, string>(source =>
{
if (source == null)
{
return null;
}

var message = messageDelegate(source);
if (message == null)
{
return null;
}

return commandAndKeyDelegate(message) as string;
});
});

public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command, StackExchangeRedisCallsInstrumentationOptions options)
{
var name = command.Command; // Example: SET;
if (string.IsNullOrEmpty(name))
Expand All @@ -43,6 +76,8 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi
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
Expand All @@ -62,7 +97,21 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi

activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName, command.Flags.ToString());

if (command.Command != null)
if (options.SetCommandKey)
{
var commandAndKey = CommandAndKeyGetter.Value.Invoke(command);

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);
Expand Down Expand Up @@ -100,19 +149,19 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi
activity.AddEvent(new ActivityEvent("Sent", send));
activity.AddEvent(new ActivityEvent("ResponseReceived", response));

activity.SetEndTime(command.CommandCreated + command.ElapsedTime);
options.Enrich?.Invoke(activity, command);
}

activity.Stop();

return activity;
}

public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCommand> sessionCommands)
public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCommand> sessionCommands, StackExchangeRedisCallsInstrumentationOptions options)
{
foreach (var command in sessionCommands)
{
ProfilerCommandToActivity(parentActivity, command);
ProfilerCommandToActivity(parentActivity, command, options);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
<Description>StackExchange.Redis instrumentation for OpenTelemetry .NET</Description>
Expand All @@ -12,6 +12,7 @@

<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api\OpenTelemetry.Api.csproj" />
<PackageReference Include="fasterflect" Version="3.0.0" />
<PackageReference Include="StackExchange.Redis" Version="$(StackExchangeRedisPkgVer)" />
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPkgVer)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void Dispose()

internal void Flush()
{
RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling());
RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.options);

foreach (var entry in this.Cache)
{
Expand All @@ -132,7 +132,7 @@ internal void Flush()
}

ProfilingSession session = entry.Value.Session;
RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling());
RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling(), this.options);
this.Cache.TryRemove((entry.Key.TraceId, entry.Key.SpanId), out _);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
// </copyright>

using System;
using System.Data;
using System.Diagnostics;
using OpenTelemetry.Trace;
using StackExchange.Redis.Profiling;

namespace OpenTelemetry.Instrumentation.StackExchangeRedis
{
Expand All @@ -28,5 +31,15 @@ public class StackExchangeRedisCallsInstrumentationOptions
/// 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 add the command key to the <see cref="SemanticConventions.AttributeDbStatement"/> tag. Default value: False.
/// </summary>
public bool SetCommandKey { get; set; }

/// <summary>
/// Gets or sets.
/// </summary>
public Action<Activity, IProfiledCommand> Enrich { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void ProfilerCommandToActivity_UsesCommandAsName()
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.Command).Returns("SET");

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.Equal("SET", result.DisplayName);
}
Expand All @@ -74,7 +74,7 @@ public void ProfilerCommandToActivity_UsesTimestampAsStartTime()
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(now.DateTime);

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.Equal(now, result.StartTimeUtc);
}
Expand All @@ -86,7 +86,7 @@ public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis()
var profiledCommand = new Mock<IProfiledCommand>();
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbSystem));
Assert.Equal("redis", result.GetTagValue(SemanticConventions.AttributeDbSystem));
Expand All @@ -100,7 +100,7 @@ public void ProfilerCommandToActivity_UsesCommandAsDbStatementAttribute()
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.Command).Returns("SET");

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbStatement));
Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbStatement));
Expand All @@ -116,7 +116,7 @@ public void ProfilerCommandToActivity_UsesFlagsForFlagsAttribute()
CommandFlags.NoRedirect;
profiledCommand.Setup(m => m.Flags).Returns(expectedFlags);

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.NotNull(result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName));
Assert.Equal("PreferMaster, FireAndForget, NoRedirect", result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName));
Expand All @@ -134,7 +134,7 @@ public void ProfilerCommandToActivity_UsesIpEndPointAsEndPoint()
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.EndPoint).Returns(ipLocalEndPoint);

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
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));
Expand All @@ -152,7 +152,7 @@ public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint()
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.EndPoint).Returns(dnsEndPoint);

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerName));
Assert.Equal(dnsEndPoint.Host, result.GetTagValue(SemanticConventions.AttributeNetPeerName));
Expand All @@ -170,7 +170,7 @@ public void ProfilerCommandToActivity_UsesOtherEndPointAsEndPoint()
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
profiledCommand.Setup(m => m.EndPoint).Returns(unixEndPoint);

var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());

Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService));
Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService));
Expand Down
Loading