在 Startup
类的 ConfigureServices
方法中,我注册 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
时,我添加了 OpenTelemetry 跟踪来检测 Redis 调用。
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 请求创建 Span。但没有检测 Redis 调用。但是,如果我在注册 AddAspNetCoreInstrumentation
的地方在第一次调用中添加 Redis 检测,那么它就可以正常工作。
有没有一种方法可以在启动期间添加一些检测,并在运行时通过添加更多检测来构建这些检测?
最佳答案
据我所知,目前没有这样的解决方案,有一个开放的issue在 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
中调用 AddRedisRuntimeInstrumentation()
和 AddRedisRuntimeInstrumentationSource()
通过 DI 使用 IRedisRuntimeInstrumentation
并在需要的地方调用 AddInstrumentation
。
关于asp.net-core - 在启动期间注册 OpenTelemetry 并在运行时动态添加更多功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70150973/