在ConfigureServices类中的Startup方法中,我注册OpenTelemetry如下:
services.AddOpenTelemetryTracing((builder) =>
builder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService"))
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri("http://localhost:4317");
}));我也想添加Redis工具,但只有在为请求提供服务时,我才能访问Redis连接字符串,在请求中提取ClientId并从相应的客户端配置中提取该客户端的Redis连接字符串。再次在Startup中,在读取ClientInfo时,我添加了用于检测Redis调用的OpenTelemetry跟踪。
services.AddScoped<ClientInfo>(sp =>
{
var context = sp.GetService<IHttpContextAccessor>().HttpContext;
var clientId = context.Request.Headers["ClientId"].ToString();
var clientInfo = await GetClientInfo(clientId).Result;
// ClientInfo will contain Redis connection string. I cache this to avoid fetching repeatedly for same client
// I cache this ClientId in a dictionary and make sure the below registration happens
// only once per client Id.
// RedisConnection is of type IConnectionMultiplexer
var redisConnection = RedisHelper.GetConnection(clientInfo.RedisConnectionString);
services.AddOpenTelemetryTracing((builder) =>
builder
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyService"))
.AddRedisInstrumentation(redisConnection)
.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri("http://localhost:4317");
}));
return clientInfo;
});当我执行代码时,它只为传入的HTTP请求和传出的HTTP请求创建范围。但并不是在利用Redis的电话。但是,如果我在注册AddAspNetCoreInstrumentation的第一个调用中添加了Redis工具,那么它可以正常工作。
是否有一种方法可以在启动期间添加一些工具,并在运行时通过添加更多的工具来构建这些工具?
发布于 2022-10-05 14:18:27
据我所知,目前还没有这样的解决方案,在github上有一个开放的问题。但是,如果使用反射,则可以添加这样的功能。
public interface IRedisRuntimeInstrumentation
{
void AddInstrumentation(IConnectionMultiplexer multiplexer);
}
internal class RedisRuntimeInstrumentation : IRedisRuntimeInstrumentation
{
private static readonly BindingFlags BindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
private readonly PropertyInfo _instrumentationsProperty;
private readonly FieldInfo _disposedField;
private readonly TracerProvider _tracerProvider;
private readonly IOptions<StackExchangeRedisCallsInstrumentationOptions> _options;
public RedisRuntimeInstrumentation(TracerProvider tracerProvider,
IOptions<StackExchangeRedisCallsInstrumentationOptions> options)
{
var tracerProviderType = tracerProvider?.GetType() ?? throw new ArgumentNullException(nameof(tracerProvider));
_instrumentationsProperty = tracerProviderType.GetProperty("Instrumentations", BindingFlags)
?? throw new InvalidOperationException($"Failed to get property 'Instrumentations' from type - {tracerProviderType.FullName}");
_disposedField = tracerProviderType.GetField("disposed", BindingFlags)
?? throw new InvalidOperationException($"Failed to get field 'disposed' from type - {tracerProviderType.FullName}");
_tracerProvider = tracerProvider;
_options = options;
}
public void AddInstrumentation(IConnectionMultiplexer multiplexer)
{
if (multiplexer == null)
throw new ArgumentNullException(nameof(multiplexer));
if (_disposedField.GetValue(_tracerProvider) is bool disposed && disposed)
throw new InvalidOperationException("Unable to add instrumentation to disposed trace provider");
var instrumentationsPropertyValue = _instrumentationsProperty.GetValue(_tracerProvider);
if (instrumentationsPropertyValue is List<object> instrumentations)
instrumentations.Add(StackExchangeRedisCallsInstrumentationHelper.CreateInstance(multiplexer, _options.Value));
else
throw new InvalidOperationException("Failed to add instrumentation");
}
}
internal static class StackExchangeRedisCallsInstrumentationHelper
{
internal const string TypeFullName = "OpenTelemetry.Instrumentation.StackExchangeRedis.StackExchangeRedisCallsInstrumentation";
internal static readonly Type Type = typeof(StackExchangeRedisCallsInstrumentationOptions).Assembly.GetType(TypeFullName)
?? throw new InvalidOperationException($"Failed to get type - {TypeFullName}");
private static readonly ConstructorInfo TypeConstructor = Type
.GetConstructor(new[] { typeof(IConnectionMultiplexer), typeof(StackExchangeRedisCallsInstrumentationOptions) })
?? throw new InvalidOperationException($"Failed to get constructor from type - {TypeFullName}");
internal static object CreateInstance(IConnectionMultiplexer multiplexer,
StackExchangeRedisCallsInstrumentationOptions options) =>
TypeConstructor.Invoke(new object[] { multiplexer, options });
}
public static class OpenTelemetryExtensions
{
public static IServiceCollection AddRedisRuntimeInstrumentation(this IServiceCollection services)
{
services.AddSingleton<IRedisRuntimeInstrumentation, RedisRuntimeInstrumentation>();
return services;
}
public static TracerProviderBuilder AddRedisRuntimeInstrumentationSource(this TracerProviderBuilder builder)
{
var activitySourceNameField = StackExchangeRedisCallsInstrumentationHelper.Type
.GetField("ActivitySourceName", BindingFlags.Static | BindingFlags.NonPublic);
if (activitySourceNameField == null)
{
throw new InvalidOperationException(
$"Failed to get field 'ActivitySourceName' from type - {StackExchangeRedisCallsInstrumentationHelper.TypeFullName}");
}
builder.AddSource((string)activitySourceNameField.GetValue(null));
return builder;
}
}然后在Startup.cs中,通过DI调用AddRedisRuntimeInstrumentation()和AddRedisRuntimeInstrumentationSource()来使用IRedisRuntimeInstrumentation,并在需要的地方调用AddInstrumentation。
https://stackoverflow.com/questions/70150973
复制相似问题