asp.net-core - 在启动期间注册 OpenTelemetry 并在运行时动态添加更多功能

标签 asp.net-core redis open-telemetry

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/

相关文章:

c# - Visual Studio在azure中的wwwroot内部署文件夹在哪里

open-telemetry - 从隐式上下文中获取当前事件的跨度

go - 如何区分不同服务的指标

asp.net-mvc - ASP.Net Core MVC 中的 data-ajax-begin 脚本阻止重定向

asp.net-core - 使用应用程序洞察跟踪 hangfire 后台作业

c# - 如何检查输入的密码是否等于存储的密码?

redis - 使用 Redis 命令 incr 和 expire 时的竞争条件

redis - 数以千计的REDIS排序集VS数百万个简单集

javascript - 使用 Redis 更新排序集

open-telemetry - 错误 io.opentelemetry.exporter.internal.grpc.OkHttpGrpcExporter