Skip to content

Commit e534a75

Browse files
committed
Add SetCommandKey and Enrich options to the Redis instrumentation
1 parent 56d945e commit e534a75

8 files changed

Lines changed: 151 additions & 24 deletions

File tree

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions
2+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity, StackExchange.Redis.Profiling.IProfiledCommand>
3+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.set -> void
24
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.get -> System.TimeSpan
35
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.set -> void
6+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetCommandKey.get -> bool
7+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetCommandKey.set -> void
48
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.StackExchangeRedisCallsInstrumentationOptions() -> void
59
OpenTelemetry.Trace.TracerProviderBuilderExtensions
6-
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
10+
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
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions
2+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.get -> System.Action<System.Diagnostics.Activity, StackExchange.Redis.Profiling.IProfiledCommand>
3+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.Enrich.set -> void
24
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.get -> System.TimeSpan
35
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.FlushInterval.set -> void
6+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetCommandKey.get -> bool
7+
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.SetCommandKey.set -> void
48
OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentationOptions.StackExchangeRedisCallsInstrumentationOptions() -> void
59
OpenTelemetry.Trace.TracerProviderBuilderExtensions
6-
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
10+
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

src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,50 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515
// </copyright>
16+
using System;
1617
using System.Collections.Generic;
1718
using System.Diagnostics;
1819
using System.Net;
20+
using Fasterflect;
1921
using OpenTelemetry.Trace;
2022
using StackExchange.Redis.Profiling;
2123

2224
namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation
2325
{
2426
internal static class RedisProfilerEntryToActivityConverter
2527
{
26-
public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command)
28+
private static readonly Lazy<Func<object, string>> CommandAndKeyGetter = new Lazy<Func<object, string>>(() =>
29+
{
30+
var redisAssembly = typeof(IProfiledCommand).Assembly;
31+
Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand");
32+
Type messageType = redisAssembly.GetType("StackExchange.Redis.Message");
33+
34+
var messageDelegate = profiledCommandType.DelegateForGetFieldValue("Message", Flags.NonPublic | Flags.Instance);
35+
var commandAndKeyDelegate = messageType.DelegateForGetPropertyValue("CommandAndKey");
36+
37+
if (messageDelegate == null || commandAndKeyDelegate == null)
38+
{
39+
return new Func<object, string>(source => null);
40+
}
41+
42+
return new Func<object, string>(source =>
43+
{
44+
if (source == null)
45+
{
46+
return null;
47+
}
48+
49+
var message = messageDelegate(source);
50+
if (message == null)
51+
{
52+
return null;
53+
}
54+
55+
return commandAndKeyDelegate(message) as string;
56+
});
57+
});
58+
59+
public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command, StackExchangeRedisCallsInstrumentationOptions options)
2760
{
2861
var name = command.Command; // Example: SET;
2962
if (string.IsNullOrEmpty(name))
@@ -43,6 +76,8 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi
4376
return null;
4477
}
4578

79+
activity.SetEndTime(command.CommandCreated + command.ElapsedTime);
80+
4681
if (activity.IsAllDataRequested == true)
4782
{
4883
// see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md
@@ -62,7 +97,21 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi
6297

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

65-
if (command.Command != null)
100+
if (options.SetCommandKey)
101+
{
102+
var commandAndKey = CommandAndKeyGetter.Value.Invoke(command);
103+
104+
if (!string.IsNullOrEmpty(commandAndKey))
105+
{
106+
activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey);
107+
}
108+
else if (command.Command != null)
109+
{
110+
// Example: "db.statement": SET;
111+
activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command);
112+
}
113+
}
114+
else if (command.Command != null)
66115
{
67116
// Example: "db.statement": SET;
68117
activity.SetTag(SemanticConventions.AttributeDbStatement, command.Command);
@@ -100,19 +149,19 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi
100149
activity.AddEvent(new ActivityEvent("Sent", send));
101150
activity.AddEvent(new ActivityEvent("ResponseReceived", response));
102151

103-
activity.SetEndTime(command.CommandCreated + command.ElapsedTime);
152+
options.Enrich?.Invoke(activity, command);
104153
}
105154

106155
activity.Stop();
107156

108157
return activity;
109158
}
110159

111-
public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCommand> sessionCommands)
160+
public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCommand> sessionCommands, StackExchangeRedisCallsInstrumentationOptions options)
112161
{
113162
foreach (var command in sessionCommands)
114163
{
115-
ProfilerCommandToActivity(parentActivity, command);
164+
ProfilerCommandToActivity(parentActivity, command, options);
116165
}
117166
}
118167
}

src/OpenTelemetry.Instrumentation.StackExchangeRedis/OpenTelemetry.Instrumentation.StackExchangeRedis.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
44
<Description>StackExchange.Redis instrumentation for OpenTelemetry .NET</Description>
@@ -12,6 +12,7 @@
1212

1313
<ItemGroup>
1414
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api\OpenTelemetry.Api.csproj" />
15+
<PackageReference Include="fasterflect" Version="3.0.0" />
1516
<PackageReference Include="StackExchange.Redis" Version="$(StackExchangeRedisPkgVer)" />
1617
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPkgVer)" />
1718
</ItemGroup>

src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public void Dispose()
120120

121121
internal void Flush()
122122
{
123-
RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling());
123+
RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.options);
124124

125125
foreach (var entry in this.Cache)
126126
{
@@ -132,7 +132,7 @@ internal void Flush()
132132
}
133133

134134
ProfilingSession session = entry.Value.Session;
135-
RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling());
135+
RedisProfilerEntryToActivityConverter.DrainSession(parent, session.FinishProfiling(), this.options);
136136
this.Cache.TryRemove((entry.Key.TraceId, entry.Key.SpanId), out _);
137137
}
138138
}

src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
// </copyright>
1616

1717
using System;
18+
using System.Data;
1819
using System.Diagnostics;
20+
using OpenTelemetry.Trace;
21+
using StackExchange.Redis.Profiling;
1922

2023
namespace OpenTelemetry.Instrumentation.StackExchangeRedis
2124
{
@@ -28,5 +31,15 @@ public class StackExchangeRedisCallsInstrumentationOptions
2831
/// 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.
2932
/// </summary>
3033
public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(10);
34+
35+
/// <summary>
36+
/// 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.
37+
/// </summary>
38+
public bool SetCommandKey { get; set; }
39+
40+
/// <summary>
41+
/// Gets or sets.
42+
/// </summary>
43+
public Action<Activity, IProfiledCommand> Enrich { get; set; }
3144
}
3245
}

test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void ProfilerCommandToActivity_UsesCommandAsName()
6161
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
6262
profiledCommand.Setup(m => m.Command).Returns("SET");
6363

64-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
64+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
6565

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

77-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
77+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
7878

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

89-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
89+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
9090

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

103-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
103+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
104104

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

119-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
119+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
120120

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

137-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
137+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
138138

139139
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeNetPeerIp));
140140
Assert.Equal($"{address}.0.0.0", result.GetTagValue(SemanticConventions.AttributeNetPeerIp));
@@ -152,7 +152,7 @@ public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint()
152152
profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow);
153153
profiledCommand.Setup(m => m.EndPoint).Returns(dnsEndPoint);
154154

155-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
155+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
156156

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

173-
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object);
173+
var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions());
174174

175175
Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService));
176176
Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService));

0 commit comments

Comments
 (0)