|
17 | 17 | using System.Collections.Generic; |
18 | 18 | using System.Diagnostics; |
19 | 19 | using System.Net; |
20 | | -using Fasterflect; |
| 20 | +using System.Reflection; |
| 21 | +using System.Reflection.Emit; |
21 | 22 | using OpenTelemetry.Trace; |
22 | 23 | using StackExchange.Redis.Profiling; |
23 | 24 |
|
24 | 25 | namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation |
25 | 26 | { |
26 | 27 | internal static class RedisProfilerEntryToActivityConverter |
27 | 28 | { |
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)>>(() => |
29 | 30 | { |
30 | 31 | var redisAssembly = typeof(IProfiledCommand).Assembly; |
31 | 32 | Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand"); |
32 | 33 | Type messageType = redisAssembly.GetType("StackExchange.Redis.Message"); |
| 34 | + Type scriptMessageType = redisAssembly.GetType("StackExchange.Redis.RedisDatabase+ScriptEvalMessage"); |
33 | 35 |
|
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"); |
36 | 39 |
|
37 | | - if (messageDelegate == null || commandAndKeyDelegate == null) |
| 40 | + if (messageDelegate == null) |
38 | 41 | { |
39 | | - return new Func<object, string>(source => null); |
| 42 | + return new Func<object, (string, string)>(source => (null, null)); |
40 | 43 | } |
41 | 44 |
|
42 | | - return new Func<object, string>(source => |
| 45 | + return new Func<object, (string, string)>(source => |
43 | 46 | { |
44 | 47 | if (source == null) |
45 | 48 | { |
46 | | - return null; |
| 49 | + return (null, null); |
47 | 50 | } |
48 | 51 |
|
49 | 52 | var message = messageDelegate(source); |
50 | 53 | if (message == null) |
51 | 54 | { |
52 | | - return null; |
| 55 | + return (null, null); |
53 | 56 | } |
54 | 57 |
|
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); |
56 | 70 | }); |
57 | 71 | }); |
58 | 72 |
|
@@ -97,11 +111,15 @@ public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfi |
97 | 111 |
|
98 | 112 | activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName, command.Flags.ToString()); |
99 | 113 |
|
100 | | - if (options.SetCommandKey) |
| 114 | + if (options.SetVerboseDatabaseStatements) |
101 | 115 | { |
102 | | - var commandAndKey = CommandAndKeyGetter.Value.Invoke(command); |
| 116 | + var (commandAndKey, script) = MessageDataGetter.Value.Invoke(command); |
103 | 117 |
|
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)) |
105 | 123 | { |
106 | 124 | activity.SetTag(SemanticConventions.AttributeDbStatement, commandAndKey); |
107 | 125 | } |
@@ -164,5 +182,28 @@ public static void DrainSession(Activity parentActivity, IEnumerable<IProfiledCo |
164 | 182 | ProfilerCommandToActivity(parentActivity, command, options); |
165 | 183 | } |
166 | 184 | } |
| 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 | + } |
167 | 208 | } |
168 | 209 | } |
0 commit comments