1

Hii In My Project we are using

services.AddStackExchangeRedisCache(options =>  
{  
    options.Configuration = "<Azure Redis Cache Conn String>";  
});

For Redis cache calls.

Now to trace all Calls while Requesting Api Endpoint I'm Using OpenTelemetry for Tracing, I'm able to get Http Traces but Not able to Find Redis Cache Call Traces .

var connection = ConnectionMultiplexer.Connect("<Azure Redis Conn Str>"); 

            services.AddOpenTelemetryTracing(builder => builder
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddRedisInstrumentation(connection)
                .AddConsoleExporter());

Currently I tried OpenTelemetry.StackExchange.redis.Instrumentation configured as Above but Not getting Redis call Traces.

Is there Any Way to Trace all those Redis Request Calls in Traces ?

Edit : Currently Getting Traces in Console Log using ConsoleExporter of OpenTelemetry.

Vivek
  • 11
  • 3

1 Answers1

1

It is a tricky thing, I faced the same issue.

So the solution is to create it explicitly and pass and register in DI (if needed).

So the code will be next:

IConnectionMultiplexer redisConnectionMultiplexer = await ConnectionMultiplexer.ConnectAsync("<Connection String>");
services.AddSingleton(redisConnectionMultiplexer);
services.AddStackExchangeRedisCache(options => options.ConnectionMultiplexerFactory = () => Task.FromResult(redisConnectionMultiplexer));

And in instrumentation part

services.AddOpenTelemetryTracing(providerBuilder =>
{
    providerBuilder
        .AddSource(serviceName)
        .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName, serviceVersion: serviceVersion))
        .AddRedisInstrumentation()
    ...;

});

Or you could pass a multiplexer here explicitly.

Because extension, which adds Redis instrumentations is next:

public static TracerProviderBuilder AddRedisInstrumentation(
            this TracerProviderBuilder builder,
            IConnectionMultiplexer connection = null,
            Action<StackExchangeRedisCallsInstrumentationOptions> configure = null)
        {
            Guard.ThrowIfNull(builder);

            if (builder is not IDeferredTracerProviderBuilder deferredTracerProviderBuilder)
            {
                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);
            }

            return deferredTracerProviderBuilder.Configure((sp, builder) =>
            {
                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)}");
                    }
                }

                AddRedisInstrumentation(
                    builder,
                    connection,
                    sp.GetOptions<StackExchangeRedisCallsInstrumentationOptions>(),
                    configure);
            });
        }

As you can see, it uses param one or resolves from DI.

And RedisCache (added by AddStackExchangeRedisCache) under hood do a connect in the next way:

        private void Connect()
        {
            CheckDisposed();
            if (_cache != null)
            {
                return;
            }

            _connectionLock.Wait();
            try
            {
                if (_cache == null)
                {
                    if(_options.ConnectionMultiplexerFactory == null)
                    {
                        if (_options.ConfigurationOptions is not null)
                        {
                            _connection = ConnectionMultiplexer.Connect(_options.ConfigurationOptions);
                        }
                        else
                        {
                            _connection = ConnectionMultiplexer.Connect(_options.Configuration);
                        }
                    }
                    else
                    {
                        _connection = _options.ConnectionMultiplexerFactory().GetAwaiter().GetResult();
                    }

                    PrepareConnection();
                    _cache = _connection.GetDatabase();
                }
            }
            finally
            {
                _connectionLock.Release();
            }
        }

So, as you can see if there are no connection, it uses provided factory with a highest priority.

So this approach will help you to share the same multiplexer for cache and instrumentation

Myte
  • 41
  • 5