diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs index efb2f7db29..0cd0bdc319 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/Implementation/RedisProfilerEntryToActivityConverter.cs @@ -23,190 +23,189 @@ using OpenTelemetry.Trace; using StackExchange.Redis.Profiling; -namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation +namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; + +internal static class RedisProfilerEntryToActivityConverter { - internal static class RedisProfilerEntryToActivityConverter + private static readonly Lazy> MessageDataGetter = new(() => { - private static readonly Lazy> MessageDataGetter = new(() => - { - var redisAssembly = typeof(IProfiledCommand).Assembly; - Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand"); - Type scriptMessageType = redisAssembly.GetType("StackExchange.Redis.RedisDatabase+ScriptEvalMessage"); + var redisAssembly = typeof(IProfiledCommand).Assembly; + Type profiledCommandType = redisAssembly.GetType("StackExchange.Redis.Profiling.ProfiledCommand"); + Type scriptMessageType = redisAssembly.GetType("StackExchange.Redis.RedisDatabase+ScriptEvalMessage"); - var messageDelegate = CreateFieldGetter(profiledCommandType, "Message", BindingFlags.NonPublic | BindingFlags.Instance); - var scriptDelegate = CreateFieldGetter(scriptMessageType, "script", BindingFlags.NonPublic | BindingFlags.Instance); - var commandAndKeyFetcher = new PropertyFetcher("CommandAndKey"); + var messageDelegate = CreateFieldGetter(profiledCommandType, "Message", BindingFlags.NonPublic | BindingFlags.Instance); + var scriptDelegate = CreateFieldGetter(scriptMessageType, "script", BindingFlags.NonPublic | BindingFlags.Instance); + var commandAndKeyFetcher = new PropertyFetcher("CommandAndKey"); + + if (messageDelegate == null) + { + return new Func(source => (null, null)); + } - if (messageDelegate == null) + return new Func(source => + { + if (source == null) { - return new Func(source => (null, null)); + return (null, null); } - return new Func(source => + var message = messageDelegate(source); + if (message == null) { - if (source == null) - { - return (null, null); - } - - var message = messageDelegate(source); - if (message == null) - { - return (null, null); - } + return (null, null); + } - string script = null; - if (message.GetType() == scriptMessageType) - { - script = scriptDelegate.Invoke(message); - } + string script = null; + if (message.GetType() == scriptMessageType) + { + script = scriptDelegate.Invoke(message); + } - if (commandAndKeyFetcher.TryFetch(message, out var value)) - { - return (value, script); - } + if (commandAndKeyFetcher.TryFetch(message, out var value)) + { + return (value, script); + } - return (null, script); - }); + return (null, script); }); + }); - public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command, StackExchangeRedisCallsInstrumentationOptions options) + public static Activity ProfilerCommandToActivity(Activity parentActivity, IProfiledCommand command, StackExchangeRedisCallsInstrumentationOptions options) + { + var name = command.Command; // Example: SET; + if (string.IsNullOrEmpty(name)) { - var name = command.Command; // Example: SET; - if (string.IsNullOrEmpty(name)) - { - name = StackExchangeRedisCallsInstrumentation.ActivityName; - } + name = StackExchangeRedisCallsInstrumentation.ActivityName; + } - var activity = StackExchangeRedisCallsInstrumentation.ActivitySource.StartActivity( - name, - ActivityKind.Client, - parentActivity?.Context ?? default, - StackExchangeRedisCallsInstrumentation.CreationTags, - startTime: command.CommandCreated); + var activity = StackExchangeRedisCallsInstrumentation.ActivitySource.StartActivity( + name, + ActivityKind.Client, + parentActivity?.Context ?? default, + StackExchangeRedisCallsInstrumentation.CreationTags, + startTime: command.CommandCreated); - if (activity == null) - { - return null; - } + if (activity == null) + { + return null; + } - activity.SetEndTime(command.CommandCreated + command.ElapsedTime); + activity.SetEndTime(command.CommandCreated + command.ElapsedTime); - if (activity.IsAllDataRequested) - { - // see https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/database.md + if (activity.IsAllDataRequested) + { + // 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 + // 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 + // 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 + // Total: + // command.ElapsedTime; // 00:00:32.4988020 - activity.SetStatus(Status.Unset); + activity.SetStatus(Status.Unset); - activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName, command.Flags.ToString()); + activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName, command.Flags.ToString()); - if (options.SetVerboseDatabaseStatements) + 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)) { - 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); - } + 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 != null) + { + if (command.EndPoint is IPEndPoint ipEndPoint) { - 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(SemanticConventions.AttributeNetPeerIp, ipEndPoint.Address.ToString()); + activity.SetTag(SemanticConventions.AttributeNetPeerPort, ipEndPoint.Port); } - - 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); - - if (options.EnrichActivityWithTimingEvents) + else if (command.EndPoint is DnsEndPoint dnsEndPoint) { - activity.AddEvent(new ActivityEvent("Enqueued", enqueued)); - activity.AddEvent(new ActivityEvent("Sent", send)); - activity.AddEvent(new ActivityEvent("ResponseReceived", response)); + activity.SetTag(SemanticConventions.AttributeNetPeerName, dnsEndPoint.Host); + activity.SetTag(SemanticConventions.AttributeNetPeerPort, dnsEndPoint.Port); + } + else + { + activity.SetTag(SemanticConventions.AttributePeerService, command.EndPoint.ToString()); } - - options.Enrich?.Invoke(activity, command); } - activity.Stop(); + activity.SetTag(StackExchangeRedisCallsInstrumentation.RedisDatabaseIndexKeyName, command.Db); - return activity; - } + // TODO: deal with the re-transmission + // command.RetransmissionOf; + // command.RetransmissionReason; - public static void DrainSession(Activity parentActivity, IEnumerable sessionCommands, StackExchangeRedisCallsInstrumentationOptions options) - { - foreach (var command in sessionCommands) + var enqueued = command.CommandCreated.Add(command.CreationToEnqueued); + var send = enqueued.Add(command.EnqueuedToSending); + var response = send.Add(command.SentToResponse); + + if (options.EnrichActivityWithTimingEvents) { - ProfilerCommandToActivity(parentActivity, command, options); + activity.AddEvent(new ActivityEvent("Enqueued", enqueued)); + activity.AddEvent(new ActivityEvent("Sent", send)); + activity.AddEvent(new ActivityEvent("ResponseReceived", response)); } + + options.Enrich?.Invoke(activity, command); } - /// - /// Creates getter for a field defined in private or internal type - /// represented with classType variable. - /// - private static Func CreateFieldGetter(Type classType, string fieldName, BindingFlags flags) + activity.Stop(); + + return activity; + } + + public static void DrainSession(Activity parentActivity, IEnumerable sessionCommands, StackExchangeRedisCallsInstrumentationOptions options) + { + foreach (var command in sessionCommands) { - 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)getterMethod.CreateDelegate(typeof(Func)); - } + ProfilerCommandToActivity(parentActivity, command, options); + } + } - return null; + /// + /// Creates getter for a field defined in private or internal type + /// represented with classType variable. + /// + private static Func CreateFieldGetter(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)getterMethod.CreateDelegate(typeof(Func)); } + + return null; } } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs index 92eb51ec61..8d451142cd 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentation.cs @@ -25,128 +25,127 @@ using StackExchange.Redis; using StackExchange.Redis.Profiling; -namespace OpenTelemetry.Instrumentation.StackExchangeRedis +namespace OpenTelemetry.Instrumentation.StackExchangeRedis; + +/// +/// Redis calls instrumentation. +/// +internal class StackExchangeRedisCallsInstrumentation : IDisposable { + internal const string RedisDatabaseIndexKeyName = "db.redis.database_index"; + internal const string RedisFlagsKeyName = "db.redis.flags"; + internal static readonly string ActivitySourceName = typeof(StackExchangeRedisCallsInstrumentation).Assembly.GetName().Name; + internal static readonly 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> CreationTags = new[] + { + new KeyValuePair(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(); + /// - /// Redis calls instrumentation. + /// Initializes a new instance of the class. /// - internal class StackExchangeRedisCallsInstrumentation : IDisposable + /// to instrument. + /// Configuration options for redis instrumentation. + public StackExchangeRedisCallsInstrumentation(IConnectionMultiplexer connection, StackExchangeRedisCallsInstrumentationOptions options) { - internal const string RedisDatabaseIndexKeyName = "db.redis.database_index"; - internal const string RedisFlagsKeyName = "db.redis.flags"; - internal static readonly string ActivitySourceName = typeof(StackExchangeRedisCallsInstrumentation).Assembly.GetName().Name; - internal static readonly 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> CreationTags = new[] - { - new KeyValuePair(SemanticConventions.AttributeDbSystem, "redis"), - }; + Guard.ThrowIfNull(connection); - internal readonly ConcurrentDictionary<(ActivityTraceId TraceId, ActivitySpanId SpanId), (Activity Activity, ProfilingSession Session)> Cache - = new(); + this.options = options ?? new StackExchangeRedisCallsInstrumentationOptions(); - private readonly StackExchangeRedisCallsInstrumentationOptions options; - private readonly EventWaitHandle stopHandle = new(false, EventResetMode.ManualReset); - private readonly Thread drainThread; + this.drainThread = new Thread(this.DrainEntries) + { + Name = "OpenTelemetry.Redis", + IsBackground = true, + }; + this.drainThread.Start(); - private readonly ProfilingSession defaultSession = new(); + connection.RegisterProfiler(this.GetProfilerSessionsFactory()); + } - /// - /// Initializes a new instance of the class. - /// - /// to instrument. - /// Configuration options for redis instrumentation. - public StackExchangeRedisCallsInstrumentation(IConnectionMultiplexer connection, StackExchangeRedisCallsInstrumentationOptions options) + /// + /// Returns session for the Redis calls recording. + /// + /// Session associated with the current span context to record Redis calls. + public Func GetProfilerSessionsFactory() + { + return () => { - Guard.ThrowIfNull(connection); + if (this.stopHandle.WaitOne(0)) + { + return null; + } - this.options = options ?? new StackExchangeRedisCallsInstrumentationOptions(); + Activity parent = Activity.Current; - this.drainThread = new Thread(this.DrainEntries) + // If no parent use the default session. + if (parent == null || parent.IdFormat != ActivityIdFormat.W3C) { - Name = "OpenTelemetry.Redis", - IsBackground = true, - }; - this.drainThread.Start(); - - connection.RegisterProfiler(this.GetProfilerSessionsFactory()); - } + return this.defaultSession; + } - /// - /// Returns session for the Redis calls recording. - /// - /// Session associated with the current span context to record Redis calls. - public Func GetProfilerSessionsFactory() - { - return () => + // 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)) { - 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; - }; - } + session = (parent, new ProfilingSession()); + this.Cache.TryAdd(cacheKey, session); + } - /// - public void Dispose() - { - this.stopHandle.Set(); - this.drainThread.Join(); + return session.Session; + }; + } - this.Flush(); + /// + public void Dispose() + { + this.stopHandle.Set(); + this.drainThread.Join(); - this.stopHandle.Dispose(); - } + this.Flush(); - internal void Flush() - { - RedisProfilerEntryToActivityConverter.DrainSession(null, this.defaultSession.FinishProfiling(), this.options); + this.stopHandle.Dispose(); + } - foreach (var entry in this.Cache) + 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) { - 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 _); + // 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) + private void DrainEntries(object state) + { + while (true) { - while (true) + if (this.stopHandle.WaitOne(this.options.FlushInterval)) { - if (this.stopHandle.WaitOne(this.options.FlushInterval)) - { - break; - } - - this.Flush(); + break; } + + this.Flush(); } } } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs index 81b37b485d..4b9e7c99f3 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/StackExchangeRedisCallsInstrumentationOptions.cs @@ -19,35 +19,34 @@ using OpenTelemetry.Trace; using StackExchange.Redis.Profiling; -namespace OpenTelemetry.Instrumentation.StackExchangeRedis +namespace OpenTelemetry.Instrumentation.StackExchangeRedis; + +/// +/// Options for StackExchange.Redis instrumentation. +/// +public class StackExchangeRedisCallsInstrumentationOptions { /// - /// Options for StackExchange.Redis instrumentation. + /// Gets or sets the maximum time that should elapse between flushing the internal buffer of Redis profiling sessions and creating objects. Default value: 00:00:10. /// - public class StackExchangeRedisCallsInstrumentationOptions - { - /// - /// Gets or sets the maximum time that should elapse between flushing the internal buffer of Redis profiling sessions and creating objects. Default value: 00:00:10. - /// - public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(10); + public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(10); - /// - /// Gets or sets a value indicating whether or not the should use reflection to get more detailed tag values. Default value: False. - /// - public bool SetVerboseDatabaseStatements { get; set; } + /// + /// Gets or sets a value indicating whether or not the should use reflection to get more detailed tag values. Default value: False. + /// + public bool SetVerboseDatabaseStatements { get; set; } - /// - /// Gets or sets an action to enrich an Activity. - /// - /// - /// : the activity being enriched. - /// : the profiled redis command from which additional information can be extracted to enrich the activity. - /// - public Action Enrich { get; set; } + /// + /// Gets or sets an action to enrich an Activity. + /// + /// + /// : the activity being enriched. + /// : the profiled redis command from which additional information can be extracted to enrich the activity. + /// + public Action Enrich { get; set; } - /// - /// Gets or sets a value indicating whether or not the should enrich Activity with entries about the Redis command processing/lifetime. Defaults to true. - /// - public bool EnrichActivityWithTimingEvents { get; set; } = true; - } + /// + /// Gets or sets a value indicating whether or not the should enrich Activity with entries about the Redis command processing/lifetime. Defaults to true. + /// + public bool EnrichActivityWithTimingEvents { get; set; } = true; } diff --git a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs index 2d99482051..22c7e56494 100644 --- a/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.StackExchangeRedis/TracerProviderBuilderExtensions.cs @@ -19,72 +19,71 @@ using OpenTelemetry.Internal; using StackExchange.Redis; -namespace OpenTelemetry.Trace +namespace OpenTelemetry.Trace; + +/// +/// Extension methods to simplify registering of dependency instrumentation. +/// +public static class TracerProviderBuilderExtensions { /// - /// Extension methods to simplify registering of dependency instrumentation. + /// Enables automatic data collection of outgoing requests to Redis. /// - public static class TracerProviderBuilderExtensions + /// + /// Note: If an is not supplied + /// using the parameter it will be + /// resolved using the application . + /// + /// being configured. + /// Optional to instrument. + /// Optional callback to configure options. + /// The instance of to chain the calls. + public static TracerProviderBuilder AddRedisInstrumentation( + this TracerProviderBuilder builder, + IConnectionMultiplexer connection = null, + Action configure = null) { - /// - /// Enables automatic data collection of outgoing requests to Redis. - /// - /// - /// Note: If an is not supplied - /// using the parameter it will be - /// resolved using the application . - /// - /// being configured. - /// Optional to instrument. - /// Optional callback to configure options. - /// The instance of to chain the calls. - public static TracerProviderBuilder AddRedisInstrumentation( - this TracerProviderBuilder builder, - IConnectionMultiplexer connection = null, - Action configure = null) - { - Guard.ThrowIfNull(builder); + Guard.ThrowIfNull(builder); - if (builder is not IDeferredTracerProviderBuilder deferredTracerProviderBuilder) + if (builder is not IDeferredTracerProviderBuilder deferredTracerProviderBuilder) + { + if (connection == null) { - 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); + 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 deferredTracerProviderBuilder.Configure((sp, builder) => + return AddRedisInstrumentation(builder, connection, new StackExchangeRedisCallsInstrumentationOptions(), configure); + } + + return deferredTracerProviderBuilder.Configure((sp, builder) => + { + if (connection == null) { + connection = (IConnectionMultiplexer)sp.GetService(typeof(IConnectionMultiplexer)); 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)}"); - } + throw new InvalidOperationException($"StackExchange.Redis {nameof(IConnectionMultiplexer)} could not be resolved through application {nameof(IServiceProvider)}"); } + } - AddRedisInstrumentation( - builder, - connection, - sp.GetOptions(), - configure); - }); - } + AddRedisInstrumentation( + builder, + connection, + sp.GetOptions(), + configure); + }); + } - private static TracerProviderBuilder AddRedisInstrumentation( - TracerProviderBuilder builder, - IConnectionMultiplexer connection, - StackExchangeRedisCallsInstrumentationOptions options, - Action configure) - { - configure?.Invoke(options); + private static TracerProviderBuilder AddRedisInstrumentation( + TracerProviderBuilder builder, + IConnectionMultiplexer connection, + StackExchangeRedisCallsInstrumentationOptions options, + Action configure) + { + configure?.Invoke(options); - return builder - .AddInstrumentation(() => new StackExchangeRedisCallsInstrumentation(connection, options)) - .AddSource(StackExchangeRedisCallsInstrumentation.ActivitySourceName); - } + return builder + .AddInstrumentation(() => new StackExchangeRedisCallsInstrumentation(connection, options)) + .AddSource(StackExchangeRedisCallsInstrumentation.ActivitySourceName); } } diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs index 578e3f613c..f718265990 100644 --- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/Implementation/RedisProfilerEntryToActivityConverterTests.cs @@ -24,158 +24,157 @@ using StackExchange.Redis.Profiling; using Xunit; -namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation +namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Implementation; + +[Collection("Redis")] +public class RedisProfilerEntryToActivityConverterTests : IDisposable { - [Collection("Redis")] - public class RedisProfilerEntryToActivityConverterTests : IDisposable - { - private readonly ConnectionMultiplexer connection; - private readonly IDisposable tracerProvider; + private readonly ConnectionMultiplexer connection; + private readonly IDisposable tracerProvider; - public RedisProfilerEntryToActivityConverterTests() + public RedisProfilerEntryToActivityConverterTests() + { + var connectionOptions = new ConfigurationOptions { - var connectionOptions = new ConfigurationOptions - { - AbortOnConnectFail = false, - }; - connectionOptions.EndPoints.Add("localhost:6379"); + AbortOnConnectFail = false, + }; + connectionOptions.EndPoints.Add("localhost:6379"); - this.connection = ConnectionMultiplexer.Connect(connectionOptions); + this.connection = ConnectionMultiplexer.Connect(connectionOptions); - this.tracerProvider = Sdk.CreateTracerProviderBuilder() - .AddRedisInstrumentation(this.connection) - .Build(); - } + this.tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddRedisInstrumentation(this.connection) + .Build(); + } - public void Dispose() - { - this.tracerProvider.Dispose(); - this.connection.Dispose(); - GC.SuppressFinalize(this); - } + 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(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - profiledCommand.Setup(m => m.Command).Returns("SET"); + [Fact] + public void ProfilerCommandToActivity_UsesCommandAsName() + { + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); + profiledCommand.Setup(m => m.Command).Returns("SET"); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); - Assert.Equal("SET", result.DisplayName); - } + Assert.Equal("SET", result.DisplayName); + } - [Fact] - public void ProfilerCommandToActivity_UsesTimestampAsStartTime() - { - var now = DateTimeOffset.Now; - var activity = new Activity("redis-profiler"); - var profiledCommand = new Mock(); - profiledCommand.Setup(m => m.CommandCreated).Returns(now.DateTime); + [Fact] + public void ProfilerCommandToActivity_UsesTimestampAsStartTime() + { + var now = DateTimeOffset.Now; + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(now.DateTime); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); - Assert.Equal(now, result.StartTimeUtc); - } + Assert.Equal(now, result.StartTimeUtc); + } - [Fact] - public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis() - { - var activity = new Activity("redis-profiler"); - var profiledCommand = new Mock(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); + [Fact] + public void ProfilerCommandToActivity_SetsDbTypeAttributeAsRedis() + { + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); - Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbSystem)); - Assert.Equal("redis", result.GetTagValue(SemanticConventions.AttributeDbSystem)); - } + 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(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - profiledCommand.Setup(m => m.Command).Returns("SET"); + [Fact] + public void ProfilerCommandToActivity_UsesCommandAsDbStatementAttribute() + { + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); + profiledCommand.Setup(m => m.Command).Returns("SET"); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); - Assert.NotNull(result.GetTagValue(SemanticConventions.AttributeDbStatement)); - Assert.Equal("SET", result.GetTagValue(SemanticConventions.AttributeDbStatement)); - } + 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(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - var expectedFlags = CommandFlags.FireAndForget | - CommandFlags.NoRedirect; - profiledCommand.Setup(m => m.Flags).Returns(expectedFlags); + [Fact] + public void ProfilerCommandToActivity_UsesFlagsForFlagsAttribute() + { + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + 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()); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); - Assert.NotNull(result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName)); - Assert.Equal("PreferMaster, FireAndForget, NoRedirect", result.GetTagValue(StackExchangeRedisCallsInstrumentation.RedisFlagsKeyName)); - } + 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; + [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(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - profiledCommand.Setup(m => m.EndPoint).Returns(ipLocalEndPoint); + var activity = new Activity("redis-profiler"); + IPEndPoint ipLocalEndPoint = new IPEndPoint(address, port); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); + profiledCommand.Setup(m => m.EndPoint).Returns(ipLocalEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + 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)); - } + 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); + [Fact] + public void ProfilerCommandToActivity_UsesDnsEndPointAsEndPoint() + { + var dnsEndPoint = new DnsEndPoint("https://opentelemetry.io/", 443); - var activity = new Activity("redis-profiler"); - var profiledCommand = new Mock(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - profiledCommand.Setup(m => m.EndPoint).Returns(dnsEndPoint); + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); + profiledCommand.Setup(m => m.EndPoint).Returns(dnsEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + 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)); - } + 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(); - profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); - profiledCommand.Setup(m => m.EndPoint).Returns(unixEndPoint); + [Fact] + public void ProfilerCommandToActivity_UsesOtherEndPointAsEndPoint() + { + var unixEndPoint = new UnixDomainSocketEndPoint("https://opentelemetry.io/"); + var activity = new Activity("redis-profiler"); + var profiledCommand = new Mock(); + profiledCommand.Setup(m => m.CommandCreated).Returns(DateTime.UtcNow); + profiledCommand.Setup(m => m.EndPoint).Returns(unixEndPoint); - var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); + var result = RedisProfilerEntryToActivityConverter.ProfilerCommandToActivity(activity, profiledCommand.Object, new StackExchangeRedisCallsInstrumentationOptions()); - Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService)); - Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService)); - } -#endif + Assert.NotNull(result.GetTagValue(SemanticConventions.AttributePeerService)); + Assert.Equal(unixEndPoint.ToString(), result.GetTagValue(SemanticConventions.AttributePeerService)); } +#endif } diff --git a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs index 46a29aa7b6..1066317736 100644 --- a/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs +++ b/test/OpenTelemetry.Instrumentation.StackExchangeRedis.Tests/StackExchangeRedisCallsInstrumentationTests.cs @@ -25,393 +25,392 @@ using StackExchange.Redis.Profiling; using Xunit; -namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Tests +namespace OpenTelemetry.Instrumentation.StackExchangeRedis.Tests; + +[Collection("Redis")] +public class StackExchangeRedisCallsInstrumentationTests { - [Collection("Redis")] - public class StackExchangeRedisCallsInstrumentationTests - { - /* - To run the integration tests, set the OTEL_REDISENDPOINT machine-level environment variable to a valid Redis endpoint. + /* + 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 - */ + 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); + 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) + [Trait("CategoryName", "RedisIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)] + [InlineData("value1")] + public void SuccessfulCommandTestWithKey(string value) + { + var connectionOptions = new ConfigurationOptions { - 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>(); - 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 }); + AbortOnConnectFail = true, + }; + connectionOptions.EndPoints.Add(RedisEndPoint); + + using var connection = ConnectionMultiplexer.Connect(connectionOptions); + var db = connection.GetDatabase(); + db.KeyDelete("key1"); + + var activityProcessor = new Mock>(); + 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"); + var redisValue = db.StringGet("key1"); - Assert.False(redisValue.HasValue); + Assert.False(redisValue.HasValue); - bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60)); + bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60)); - Assert.True(set); + Assert.True(set); - redisValue = db.StringGet("key1"); + redisValue = db.StringGet("key1"); - Assert.True(redisValue.HasValue); - Assert.Equal(value, redisValue.ToString()); - } + Assert.True(redisValue.HasValue); + Assert.Equal(value, redisValue.ToString()); + } - // Disposing SDK should flush the Redis profiling session immediately. + // Disposing SDK should flush the Redis profiling session immediately. - Assert.Equal(11, activityProcessor.Invocations.Count); + 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)); + 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); - } + 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) + [Trait("CategoryName", "RedisIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)] + [InlineData("value1")] + public void SuccessfulCommandTest(string value) + { + var connectionOptions = new ConfigurationOptions { - var connectionOptions = new ConfigurationOptions - { - AbortOnConnectFail = true, - }; - connectionOptions.EndPoints.Add(RedisEndPoint); - - using var connection = ConnectionMultiplexer.Connect(connectionOptions); - - var activityProcessor = new Mock>(); - var sampler = new TestSampler(); - using (Sdk.CreateTracerProviderBuilder() - .AddProcessor(activityProcessor.Object) - .SetSampler(sampler) - .AddRedisInstrumentation(connection, c => c.SetVerboseDatabaseStatements = false) - .Build()) - { - var db = connection.GetDatabase(); + AbortOnConnectFail = true, + }; + connectionOptions.EndPoints.Add(RedisEndPoint); + + using var connection = ConnectionMultiplexer.Connect(connectionOptions); + + var activityProcessor = new Mock>(); + 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)); + bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60)); - Assert.True(set); + Assert.True(set); - var redisValue = db.StringGet("key1"); + var redisValue = db.StringGet("key1"); - Assert.True(redisValue.HasValue); - Assert.Equal(value, redisValue.ToString()); - } + Assert.True(redisValue.HasValue); + Assert.Equal(value, redisValue.ToString()); + } - // Disposing SDK should flush the Redis profiling session immediately. + // Disposing SDK should flush the Redis profiling session immediately. - Assert.Equal(7, activityProcessor.Invocations.Count); + 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); - } + 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() + [Fact] + public async void ProfilerSessionUsesTheSameDefault() + { + var connectionOptions = new ConfigurationOptions { - 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); - } + 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) + [Trait("CategoryName", "RedisIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(RedisEndPointEnvVarName)] + [InlineData("value1")] + public void CanEnrichActivityFromCommand(string value) + { + var connectionOptions = new ConfigurationOptions { - var connectionOptions = new ConfigurationOptions - { - AbortOnConnectFail = true, - }; - connectionOptions.EndPoints.Add(RedisEndPoint); - - using var connection = ConnectionMultiplexer.Connect(connectionOptions); - - var activityProcessor = new Mock>(); - 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(); + AbortOnConnectFail = true, + }; + connectionOptions.EndPoints.Add(RedisEndPoint); + + using var connection = ConnectionMultiplexer.Connect(connectionOptions); + + var activityProcessor = new Mock>(); + 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)); + bool set = db.StringSet("key1", value, TimeSpan.FromSeconds(60)); - Assert.True(set); + Assert.True(set); - var redisValue = db.StringGet("key1"); + var redisValue = db.StringGet("key1"); - Assert.True(redisValue.HasValue); - Assert.Equal(value, redisValue.ToString()); - } + Assert.True(redisValue.HasValue); + Assert.Equal(value, redisValue.ToString()); + } - // Disposing SDK should flush the Redis profiling session immediately. + // Disposing SDK should flush the Redis profiling session immediately. - Assert.Equal(7, activityProcessor.Invocations.Count); + 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")); - } + 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() + [Fact] + public void CheckCacheIsFlushedProperly() + { + var connectionOptions = new ConfigurationOptions { - var connectionOptions = new ConfigurationOptions - { - AbortOnConnectFail = false, - }; - connectionOptions.EndPoints.Add("localhost:6379"); + AbortOnConnectFail = false, + }; + connectionOptions.EndPoints.Add("localhost:6379"); - var connection = ConnectionMultiplexer.Connect(connectionOptions); + var connection = ConnectionMultiplexer.Connect(connectionOptions); - using var instrumentation = new StackExchangeRedisCallsInstrumentation(connection, new StackExchangeRedisCallsInstrumentationOptions()); - var profilerFactory = instrumentation.GetProfilerSessionsFactory(); + 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(); + // start a root level activity + using Activity rootActivity = new Activity("Parent") + .SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded) + .Start(); - Assert.NotNull(rootActivity.Id); + Assert.NotNull(rootActivity.Id); - // get an initial profiler from root activity - Activity.Current = rootActivity; - ProfilingSession profiler0 = profilerFactory(); + // 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); - } + // 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(); + rootActivity.Stop(); + rootActivity.Dispose(); - instrumentation.Flush(); - Assert.Empty(instrumentation.Cache); - } + instrumentation.Flush(); + Assert.Empty(instrumentation.Cache); + } - [Fact] - public async Task ProfilerSessionsHandleMultipleSpans() + [Fact] + public async Task ProfilerSessionsHandleMultipleSpans() + { + var connectionOptions = new ConfigurationOptions { - var connectionOptions = new ConfigurationOptions - { - AbortOnConnectFail = false, - }; - connectionOptions.EndPoints.Add("localhost:6379"); + AbortOnConnectFail = false, + }; + connectionOptions.EndPoints.Add("localhost:6379"); - var connection = ConnectionMultiplexer.Connect(connectionOptions); + var connection = ConnectionMultiplexer.Connect(connectionOptions); - using var instrumentation = new StackExchangeRedisCallsInstrumentation(connection, new StackExchangeRedisCallsInstrumentationOptions()); - var profilerFactory = instrumentation.GetProfilerSessionsFactory(); + 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(); + // start a root level activity + using Activity rootActivity = new Activity("Parent") + .SetParentId(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.Recorded) + .Start(); - Assert.NotNull(rootActivity.Id); + Assert.NotNull(rootActivity.Id); - // get an initial profiler from root activity - Activity.Current = rootActivity; - ProfilingSession profiler0 = profilerFactory(); + // 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); - } + // 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; + 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); + // 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); - } + ProfilingSession profiler2 = profilerFactory(); + Assert.NotSame(profiler0, profiler2); + Assert.NotSame(profiler1, profiler2); + } - Activity.Current = rootActivity; + Activity.Current = rootActivity; - // ensure same result back in root activity - ProfilingSession profiles3 = profilerFactory(); - Assert.Same(profiler0, profiles3); - } + // ensure same result back in root activity + ProfilingSession profiles3 = profilerFactory(); + Assert.Same(profiler0, profiles3); + } - [Fact] - public void StackExchangeRedis_BadArgs() - { - TracerProviderBuilder builder = null; - Assert.Throws(() => builder.AddRedisInstrumentation(null)); + [Fact] + public void StackExchangeRedis_BadArgs() + { + TracerProviderBuilder builder = null; + Assert.Throws(() => builder.AddRedisInstrumentation(null)); - var activityProcessor = new Mock>(); - Assert.Throws(() => + var activityProcessor = new Mock>(); + Assert.Throws(() => Sdk.CreateTracerProviderBuilder() .AddProcessor(activityProcessor.Object) .AddRedisInstrumentation(null) .Build()); - } + } - [Fact] - public void StackExchangeRedis_DependencyInjection_Success() - { - bool connectionMultiplexerPickedFromDI = false; - bool optionsPickedFromDI = false; + [Fact] + public void StackExchangeRedis_DependencyInjection_Success() + { + bool connectionMultiplexerPickedFromDI = false; + bool optionsPickedFromDI = false; - var connectionOptions = new ConfigurationOptions - { - AbortOnConnectFail = false, - }; - connectionOptions.EndPoints.Add("localhost"); + var connectionOptions = new ConfigurationOptions + { + AbortOnConnectFail = false, + }; + connectionOptions.EndPoints.Add("localhost"); - var services = new ServiceCollection(); - services.AddSingleton((sp) => - { - connectionMultiplexerPickedFromDI = true; - return ConnectionMultiplexer.Connect(connectionOptions); - }); - services.Configure(options => - { - optionsPickedFromDI = true; - }); - services.AddOpenTelemetryTracing(builder => builder.AddRedisInstrumentation()); + var services = new ServiceCollection(); + services.AddSingleton((sp) => + { + connectionMultiplexerPickedFromDI = true; + return ConnectionMultiplexer.Connect(connectionOptions); + }); + services.Configure(options => + { + optionsPickedFromDI = true; + }); + services.AddOpenTelemetryTracing(builder => builder.AddRedisInstrumentation()); - using var serviceProvider = services.BuildServiceProvider(); + using var serviceProvider = services.BuildServiceProvider(); - var tracerProvider = serviceProvider.GetRequiredService(); + var tracerProvider = serviceProvider.GetRequiredService(); - Assert.True(connectionMultiplexerPickedFromDI); - Assert.True(optionsPickedFromDI); - } + Assert.True(connectionMultiplexerPickedFromDI); + Assert.True(optionsPickedFromDI); + } - [Fact] - public void StackExchangeRedis_DependencyInjection_Failure() - { - var services = new ServiceCollection(); + [Fact] + public void StackExchangeRedis_DependencyInjection_Failure() + { + var services = new ServiceCollection(); - services.AddOpenTelemetryTracing(builder => builder.AddRedisInstrumentation()); + services.AddOpenTelemetryTracing(builder => builder.AddRedisInstrumentation()); - using var serviceProvider = services.BuildServiceProvider(); + using var serviceProvider = services.BuildServiceProvider(); - Assert.Throws(() => serviceProvider.GetRequiredService()); - } + Assert.Throws(() => serviceProvider.GetRequiredService()); + } - private static void VerifyActivityData(Activity activity, bool isSet, EndPoint endPoint, bool setCommandKey = false) + private static void VerifyActivityData(Activity activity, bool isSet, EndPoint endPoint, bool setCommandKey = false) + { + if (isSet) { - if (isSet) + Assert.Equal("SETEX", activity.DisplayName); + if (setCommandKey) { - Assert.Equal("SETEX", activity.DisplayName); - if (setCommandKey) - { - Assert.Equal("SETEX key1", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); - } - else - { - Assert.Equal("SETEX", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); - } + Assert.Equal("SETEX key1", 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("SETEX", 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) + } + else + { + Assert.Equal("GET", activity.DisplayName); + if (setCommandKey) { - Assert.Equal(dnsEndPoint.Host, activity.GetTagValue(SemanticConventions.AttributeNetPeerName)); - Assert.Equal(dnsEndPoint.Port, activity.GetTagValue(SemanticConventions.AttributeNetPeerPort)); + Assert.Equal("GET key1", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); } else { - Assert.Equal(endPoint.ToString(), activity.GetTagValue(SemanticConventions.AttributePeerService)); + Assert.Equal("GET", activity.GetTagValue(SemanticConventions.AttributeDbStatement)); } } - private static void VerifySamplingParameters(SamplingParameters samplingParameters) + 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.NotNull(samplingParameters.Tags); - Assert.Contains( - samplingParameters.Tags, - kvp => kvp.Key == SemanticConventions.AttributeDbSystem - && (string)kvp.Value == "redis"); + 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"); } }