Skip to content

Commit 2f9bb74

Browse files
committed
Change option to SetVerboseDatabaseStatements add script to DbStatement tag value
1 parent 5e2c8aa commit 2f9bb74

4 files changed

Lines changed: 76 additions & 23 deletions

File tree

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

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,42 +17,56 @@
1717
using System.Collections.Generic;
1818
using System.Diagnostics;
1919
using System.Net;
20-
using Fasterflect;
20+
using System.Reflection;
21+
using System.Reflection.Emit;
2122
using OpenTelemetry.Trace;
2223
using StackExchange.Redis.Profiling;
2324

2425
namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation
2526
{
2627
internal static class RedisProfilerEntryToActivityConverter
2728
{
28-
private static readonly Lazy<Func<object, string>> CommandAndKeyGetter = new Lazy<Func<object, string>>(() =>
29+
private static readonly Lazy<Func<object, (string, string)>> MessageDataGetter = new Lazy<Func<object, (string, string)>>(() =>
2930
{
3031
var redisAssembly = typeof(IProfiledCommand).Assembly;
3132
Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand");
3233
Type messageType = redisAssembly.GetType("StackExchange.Redis.Message");
34+
Type scriptMessageType = redisAssembly.GetType("StackExchange.Redis.RedisDatabase+ScriptEvalMessage");
3335

34-
var messageDelegate = profiledCommandType.DelegateForGetFieldValue("Message", Flags.NonPublic | Flags.Instance);
35-
var commandAndKeyDelegate = messageType.DelegateForGetPropertyValue("CommandAndKey");
36+
var messageDelegate = CreateFieldGetter<object>(profiledCommandType, "Message", BindingFlags.NonPublic | BindingFlags.Instance);
37+
var scriptDelegate = CreateFieldGetter<string>(scriptMessageType, "script", BindingFlags.NonPublic | BindingFlags.Instance);
38+
var commandAndKeyFetcher = new PropertyFetcher<string>("CommandAndKey");
3639

37-
if (messageDelegate == null || commandAndKeyDelegate == null)
40+
if (messageDelegate == null)
3841
{
39-
return new Func<object, string>(source => null);
42+
return new Func<object, (string, string)>(source => (null, null));
4043
}
4144

42-
return new Func<object, string>(source =>
45+
return new Func<object, (string, string)>(source =>
4346
{
4447
if (source == null)
4548
{
46-
return null;
49+
return (null, null);
4750
}
4851

4952
var message = messageDelegate(source);
5053
if (message == null)
5154
{
52-
return null;
55+
return (null, null);
5356
}
5457

55-
return commandAndKeyDelegate(message) as string;
58+
string script = null;
59+
if (message.GetType() == scriptMessageType)
60+
{
61+
script = scriptDelegate.Invoke(message);
62+
}
63+
64+
if (commandAndKeyFetcher.TryFetch(message, out var value))
65+
{
66+
return (value, script);
67+
}
68+
69+
return (null, script);
5670
});
5771
});
5872

@@ -97,11 +111,15 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi
97111

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

100-
if (options.SetCommandKey)
114+
if (options.SetVerboseDatabaseStatements)
101115
{
102-
var commandAndKey = CommandAndKeyGetter.Value.Invoke(command);
116+
var (commandAndKey, script) = MessageDataGetter.Value.Invoke(command);
103117

104-
if (!string.IsNullOrEmpty(commandAndKey))
118+
if (!string.IsNullOrEmpty(commandAndKey) && !string.IsNullOrEmpty(script))
119+
{
120+
activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey + " " + script);
121+
}
122+
else if (!string.IsNullOrEmpty(commandAndKey))
105123
{
106124
activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey);
107125
}
@@ -164,5 +182,28 @@ public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCo
164182
ProfilerCommandToActivity(parentActivity, command, options);
165183
}
166184
}
185+
186+
/// <summary>
187+
/// Creates getter for a field defined in private or internal type
188+
/// repesented with classType variable.
189+
/// </summary>
190+
private static Func<object, TField> CreateFieldGetter<TField>(Type classType, string fieldName, BindingFlags flags)
191+
{
192+
FieldInfo field = classType.GetField(fieldName, flags);
193+
if (field != null)
194+
{
195+
string methodName = classType.FullName + ".get_" + field.Name;
196+
DynamicMethod getterMethod = new DynamicMethod(methodName, typeof(TField), new[] { typeof(object) }, true);
197+
ILGenerator generator = getterMethod.GetILGenerator();
198+
generator.Emit(OpCodes.Ldarg_0);
199+
generator.Emit(OpCodes.Castclass, classType);
200+
generator.Emit(OpCodes.Ldfld, field);
201+
generator.Emit(OpCodes.Ret);
202+
203+
return (Func<object, TField>)getterMethod.CreateDelegate(typeof(Func<object, TField>));
204+
}
205+
206+
return null;
207+
}
167208
}
168209
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
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>
55
<PackageTags>$(PackageTags);distributed-tracing;Redis;StackExchange.Redis</PackageTags>
6+
<IncludeDiagnosticSourceInstrumentationHelpers>true</IncludeDiagnosticSourceInstrumentationHelpers>
67
<IncludeInstrumentationHelpers>true</IncludeInstrumentationHelpers>
78
</PropertyGroup>
89

@@ -12,7 +13,6 @@
1213

1314
<ItemGroup>
1415
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Api\OpenTelemetry.Api.csproj" />
15-
<PackageReference Include="fasterflect" Version="3.0.0" />
1616
<PackageReference Include="StackExchange.Redis" Version="$(StackExchangeRedisPkgVer)" />
1717
<PackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPkgVer)" />
1818
</ItemGroup>

src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ public class StackExchangeRedisCallsInstrumentationOptions
3333
public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(10);
3434

3535
/// <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.
36+
/// 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.
3737
/// </summary>
38-
public bool SetCommandKey { get; set; }
38+
public bool SetVerboseDatabaseStatements { get; set; }
3939

4040
/// <summary>
4141
/// Gets or sets.

test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,33 +53,45 @@ public void SuccessfulCommandTestWithKey(string value)
5353
connectionOptions.EndPoints.Add(RedisEndPoint);
5454

5555
using var connection = ConnectionMultiplexer.Connect(connectionOptions);
56+
var db = connection.GetDatabase();
57+
db.KeyDelete("key1");
5658

5759
var activityProcessor = new Mock<BaseProcessor<Activity>>();
5860
var sampler = new TestSampler();
5961
using (Sdk.CreateTracerProviderBuilder()
6062
.AddProcessor(activityProcessor.Object)
6163
.SetSampler(sampler)
62-
.AddRedisInstrumentation(connection, c => c.SetCommandKey = true)
64+
.AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = true)
6365
.Build())
6466
{
65-
var db = connection.GetDatabase();
67+
var prepared = LuaScript.Prepare("redis.call('set', @key, @value)");
68+
db.ScriptEvaluate(prepared, new { key = (RedisKey)"mykey", value = 123 });
69+
70+
var redisValue = db.StringGet("key1");
71+
72+
Assert.False(redisValue.HasValue);
6673

6774
bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60));
6875

6976
Assert.True(set);
7077

71-
var redisValue = db.StringGet("key1");
78+
redisValue = db.StringGet("key1");
7279

7380
Assert.True(redisValue.HasValue);
7481
Assert.Equal(value, redisValue.ToString());
7582
}
7683

7784
// Disposing SDK should flush the Redis profiling session immediately.
7885

79-
Assert.Equal(7, activityProcessor.Invocations.Count);
86+
Assert.Equal(11, activityProcessor.Invocations.Count);
87+
88+
var scriptActivity = (Activity)activityProcessor.Invocations[1].Arguments[0];
89+
Assert.Equal("EVAL", scriptActivity.DisplayName);
90+
Assert.Equal("EVAL redis.call('set', ARGV[1], ARGV[2])", scriptActivity.GetTagValue(SemanticConventions.AttributeDbStatement));
8091

81-
VerifyActivityData((Activity)activityProcessor.Invocations[1].Arguments[0], true, connection.GetEndPoints()[0], true);
8292
VerifyActivityData((Activity)activityProcessor.Invocations[3].Arguments[0], false, connection.GetEndPoints()[0], true);
93+
VerifyActivityData((Activity)activityProcessor.Invocations[5].Arguments[0], true, connection.GetEndPoints()[0], true);
94+
VerifyActivityData((Activity)activityProcessor.Invocations[7].Arguments[0], false, connection.GetEndPoints()[0], true);
8395
VerifySamplingParameters(sampler.LatestSamplingParameters);
8496
}
8597

@@ -101,7 +113,7 @@ public void SuccessfulCommandTest(string value)
101113
using (Sdk.CreateTracerProviderBuilder()
102114
.AddProcessor(activityProcessor.Object)
103115
.SetSampler(sampler)
104-
.AddRedisInstrumentation(connection, c => c.SetCommandKey = false)
116+
.AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = false)
105117
.Build())
106118
{
107119
var db = connection.GetDatabase();

0 commit comments

Comments
 (0)