在使用链路追踪控制台追踪应用的链路数据之前,需要通过客户端将应用数据上报至链路追踪。本文介绍如何通过Jaeger客户端上报.NET应用数据(此方法同样适用于使用C#语言开发的应用)。

前提条件

获取接入点信息
  1. 登录链路追踪Tracing Analysis控制台,在页面顶部选择需要接入的地域。
  2. 在左侧导航栏单击集群配置,然后单击接入点信息页签。
  3. 接入点信息页签的集群信息区域打开显示Token开关。
  4. 客户端采集工具区域单击需要使用的链路数据采集客户端。

    在下方表格的相关信息列中,获取接入点信息。

    Jaeger/Zipkin接入点信息
    说明
    • 对于ACK集群应用,建议通过ARMS OpenTelemetry Collector上报应用数据。ARMS OpenTelemetry Collector支持本地集群内的链路采样与指标无损统计,在降低链路传输、存储成本的同时,不影响监控或告警指标的准确性。更多信息,请参见ARMS OpenTelemetry Collector
    • 如果应用部署于阿里云生产环境,则选择私网接入点,否则选择公网接入点。对于Zipkin,一般情况下请使用v2版接入点,v1版接入点仅限对Zipkin十分了解的高阶用户使用。

背景信息

Jaeger是Uber推出的一款开源分布式追踪系统,兼容OpenTracing API,已在Uber大规模使用,且已加入CNCF开源组织。其主要功能是聚合来自各个异构系统的实时监控数据。

目前OpenTracing社区已有许多组件可支持各种.NET框架,例如:

数据是如何上报的?
  • 不通过Jaeger Agent而直接上报数据的原理如下图所示。不通过Jaeger Agent而直接上报
  • 通过Jaeger Agent上报数据的原理如下图所示。通过Jaeger Agent上报数据

示例Demo

示例代码仓库:dotnet-demo

.NET 6.0埋点

通过OpenTracing组件自动埋点

Demo源码的运行版本要求:
  • Jaeger:1.0.2版本
  • .NET:6.0版本
  1. 在示例项目的dotnet-demo/net6.0/Shared/JaegerServiceCollectionExtensions.cs文件中填写上报数据端口并修改上报服务名,用于实现ITracer对象的初始化和注册逻辑。
    public static class JaegerServiceCollectionExtensions
    {
        // 参考前提条件获取Jaeger Endpoint并填入参数
        private static readonly Uri _jaegerUri = new Uri("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");
    
        public static IServiceCollection AddJaeger(this IServiceCollection services)
        {
            if (services == null)
                throw new ArgumentNullException(nameof(services));
    
            services.AddSingleton<ITracer>(serviceProvider =>
            {
                // 可以将其修改为自定义的服务名
                string serviceName = Assembly.GetEntryAssembly().GetName().Name;
    
                ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
    
                ISampler sampler = new ConstSampler(sample: true);
    
                IReporter reporter = new RemoteReporter.Builder()
                    .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build())
                    .Build();
    
                ITracer tracer = new Tracer.Builder(serviceName)
                    .WithLoggerFactory(loggerFactory)
                    .WithSampler(sampler)
                    .WithReporter(reporter)
                    .Build();
    
                GlobalTracer.Register(tracer);
    
                return tracer;
            });
    
            // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger.
            services.Configure<HttpHandlerDiagnosticOptions>(options =>
            {
                options.IgnorePatterns.Add(request => _jaegerUri.IsBaseOf(request.RequestUri));
            });
    
            return services;
        }
    }
  2. 进入项目目录dotnet-demo/net6.0/CustomersApi,然后运行以下命令。
    dotnet run --framework:net6.0
  3. 启动本地服务,并访问以下地址。
    http://localhost:5001/health
  4. 链路追踪Tracing Analysis控制台通过自定义的serviceName搜索应用,查看上报的数据。

NET Core 3.1埋点

Demo源码的运行版本要求:
  • Jaeger:1.0.2版本
  • .NET:3.1版本

通过NetCore组件自动埋点

  1. 在项目的dotnet-demo/netcoreapp3.1/Shared/JaegerServiceCollectionExtensions.cs中填写上报数据端口并修改上报服务名,用于实现ITracer对象的初始化和注册逻辑。
    public static class JaegerServiceCollectionExtensions
    {
        // 参考前提条件获取Jaeger Endpoint并填入参数
        private static readonly Uri _jaegerUri = new Uri("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");
    
        public static IServiceCollection AddJaeger(this IServiceCollection services)
        {
            if (services == null)
                throw new ArgumentNullException(nameof(services));
    
            services.AddSingleton<ITracer>(serviceProvider =>
            {
                // 可以将其修改为自定义的服务名
                string serviceName = Assembly.GetEntryAssembly().GetName().Name;
    
                ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
    
                ISampler sampler = new ConstSampler(sample: true);
    
                IReporter reporter = new RemoteReporter.Builder()
                    .WithSender(new HttpSender.Builder(_jaegerUri.ToString()).Build())
                    .Build();
    
                ITracer tracer = new Tracer.Builder(serviceName)
                    .WithLoggerFactory(loggerFactory)
                    .WithSampler(sampler)
                    .WithReporter(reporter)
                    .Build();
    
                GlobalTracer.Register(tracer);
    
                return tracer;
            });
    
            // Prevent endless loops when OpenTracing is tracking HTTP requests to Jaeger.
            services.Configure<HttpHandlerDiagnosticOptions>(options =>
            {
                options.IgnorePatterns.Add(request => _jaegerUri.IsBaseOf(request.RequestUri));
            });
    
            return services;
        }
    }
  2. 进入项目目录dotnet-demo/netcoreapp3.1/Shared,然后运行以下命令。
    // 添加aspnetcore中间件
    dotnet add  package OpenTracing.Contrib.NetCore
  3. 进入项目目录dotnet-demo/netcoreapp3.1/CustomersApi,然后运行以下命令。
    // 添加aspnetcore中间件
    dotnet add  package OpenTracing.Contrib.NetCore
    // 运行示例程序
    dotnet run --framework:netcoreapp3.1
  4. 启动本地服务,并访问以下地址。
    http://localhost:5001/health
  5. 链路追踪Tracing Analysis控制台通过自定义的serviceName搜索应用,查看上报的数据。

通过gRPC组件自动埋点

  1. 安装NuGet包。
    // 添加以下组件。
    // OpenTracing.Contrib.Grpc(gRPC中间件)
    // Jaeger(OpenTracing的实现组件)
    // Microsoft.Extensions.Logging.Console(日志组件)
    
    dotnet add  package OpenTracing.Contrib.grpc
    dotnet add  package Jaeger
  2. 初始化ITracer对象。
    public static Tracer InitTracer(string serviceName, ILoggerFactory loggerFactory)
            {
                Configuration.SamplerConfiguration samplerConfiguration = new Configuration.SamplerConfiguration(loggerFactory)
                    .WithType(ConstSampler.Type)
                    .WithParam(1);
                Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory)
                        // 参考前提条件获取Jaeger Endpoint并填入参数
                       .WithEndpoint("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");
                Configuration.ReporterConfiguration reporterConfiguration = new Configuration.ReporterConfiguration(loggerFactory)
                    .WithSender(senderConfiguration);
    
                return (Tracer)new Configuration(serviceName, loggerFactory)
                    .WithSampler(samplerConfiguration)
                    .WithReporter(reporterConfiguration)
                    .GetTracer();
            }
  3. 在服务端埋点。构建用于埋点的ServerTracingInterceptor对象,并将ServerTracingInterceptor绑定到服务上。
    ILoggerFactory loggerFactory = new LoggerFactory().AddConsole();
    ITracer tracer = TracingHelper.InitTracer("dotnetGrpcServer", loggerFactory);
    ServerTracingInterceptor tracingInterceptor = new ServerTracingInterceptor(tracer);
    Server server = new Server
     {
        Services = { Greeter.BindService(new GreeterImpl()).Intercept(tracingInterceptor) },
         Ports = { new ServerPort("localhost", Port, ServerCredentials.Insecure) }
     };
  4. 在客户端埋点。构建用于埋点的ClientTracingInterceptor对象,并将ClientTracingInterceptor绑定到Channel上。
    ILoggerFactory loggerFactory = new LoggerFactory().AddConsole();
    ITracer tracer = TracingHelper.InitTracer("dotnetGrpcClient", loggerFactory);
    ClientTracingInterceptor tracingInterceptor = new ClientTracingInterceptor(tracer);
    Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
    
    var client = new Greeter.GreeterClient(channel.Intercept(tracingInterceptor));
  5. 进入项目目录dotnet-demo/netcoreapp3.1/GreeterServer,然后在终端运行以下命令开启gRPC的服务端。
    dotnet run --framework:netcoreapp3.1
  6. 进入项目目录dotnet-demo/netcoreapp3.1/GreeterClient,然后在另一个终端运行以下命令开启gRPC的客户端。
    dotnet run --framework:netcoreapp3.1

    如果终端输出Greeting: Hello you,说明服务端和客户端之间通信成功,在链路追踪Tracing Analysis控制台可以看到示例应用dotnetGrpcServerdotnetGrpcClient上报的数据。

手动埋点

除了利用各种现有插件实现埋点外,还可以使用手动埋点的方法通过Jaeger将.NET应用数据上报至链路追踪控制台。

  1. 安装NuGet包。
    // Jaeger(OpenTracing的实现组件)
    // Microsoft.Extensions.Logging.Console(日志组件)
    
    dotnet add  package Microsoft.Extensions.Logging.Console
    dotnet add  package Jaeger
  2. 构建ITracer对象。ITracer是OpenTracing定义的对象,我们用Jaeger来构建该对象(配置网关、采样率等)。
    public static ITracer InitTracer(string serviceName, ILoggerFactory loggerFactory)
     {
        Configuration.SamplerConfiguration samplerConfiguration = new Configuration.SamplerConfiguration(loggerFactory)
              .WithType(ConstSampler.Type)
              .WithParam(1);
        Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory)
               // 在链路追踪控制台获取Jaeger Endpoint。
              .WithEndpoint("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");
    
       Configuration.ReporterConfiguration reporterConfiguration = new Configuration.ReporterConfiguration(loggerFactory)
              .WithSender(senderConfiguration);
    
       return (Tracer)new Configuration(serviceName, loggerFactory)
              .WithSampler(samplerConfiguration)
              .WithReporter(reporterConfiguration)
            .GetTracer();
    }
  3. 将ITreacer注册到GlobalTracer中,方便调用代码。
    GlobalTracer.Register(InitTracer("dotnetManualDemo", loggerFactory ));
  4. 记录请求数据。
    ITracer tracer = GlobalTracer.Instance;
    ISpan span = tracer.BuildSpan("parentSpan").WithTag("mytag","parentSapn").Start();
    tracer.ScopeManager.Activate(span, false);
    // ... do something
    span.Finish();
    说明 以上代码用于记录请求的根操作,如果需要记录请求的上一步和下一步操作,则需要调用AsChildOf方法。

    示例:

    ITracer tracer = GlobalTracer.Instance;
    ISpan parentSpan = tracer.ActiveSpan;
    ISpan childSpan =tracer.BuildSpan("childSpan").AsChildOf(parentSpan).WithTag("mytag", "spanSecond").Start();
    tracer.ScopeManager.Activate(childSpan, false);
    // ... do something
    childSpan.Finish();
  5. 可选:为了快速排查问题,您可以为某个记录添加一些自定义标签,例如记录是否发生错误、请求的返回值等。
    tracer.activeSpan().setTag("http.status_code", "200");
  6. 在分布式系统中发送RPC请求时会带上Tracing数据,包括TraceId、ParentSpanId、SpanId、Sampled等。您可以在HTTP请求中使用Extract/Inject方法在HTTP Request Headers上透传数据。总体流程如下:
    流程图
    1. 在客户端调用Inject方法传入Context信息。

      Tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new HttpHeadersInjectAdapter(request.Headers));
    2. 在服务端调用Extract方法解析Context信息。

      ISpanContext extractedSpanContext = _tracer.Extract(BuiltinFormats.HttpHeaders, new RequestHeadersExtractAdapter(request.Headers));
      ISpan childSpan = _tracer.BuildSpan(operationName).AsChildOf(extractedSpanContext);
  7. 进入项目目录dotnet-demo/netcoreapp3.1/ManualDemo,然后运行以下命令,示例程序将会上报数据。
    dotnet run --framework:netcoreapp3.1

    链路追踪Tracing Analysis控制台可以查看手动埋点的示例应用dotnetManualDemo上报的数据。

常见问题

Q1:Demo程序执行成功,为什么控制台上没有上报数据?

A1:请检查senderConfiguration配置中的Endpoint是否填写正确。
Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory)
           // 在链路追踪控制台获取Jaeger Endpoint。
          .WithEndpoint("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");

Q2:如何设置采样率?

A2:具体详情,请参见Jaeger采样率文档