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