在使用链路追踪控制台追踪应用的链路数据之前,需要通过客户端将应用数据上报至链路追踪。本文介绍如何通过 Jaeger 客户端上报 .NET 应用数据。

前提条件

  1. 登录链路追踪控制台,在概览页面的 Region对应信息区域打开查看Token 开关。
  2. 客户端采集工具区域单击需要使用的链路数据采集客户端。
  3. 在下方表格中相应地域的相关信息列中,单击接入点信息末尾的复制按钮。

Tracing Analysis Endpoint Section

提示:如果应用部署于阿里云生产环境,则选择内网接入点,否则选择公网接入点。对于 Zipkin,一般情况下请使用 v2 版接入点,v1 版接入点仅限对 Zipkin 十分了解的高阶用户使用。

背景信息

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

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

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

Report Tracing Data Directly

通过 Agent 上报数据的原理如下图所示。

Report Tracing Data By Agent

通过 netcore 组件自动埋点

请按照以下步骤通过 netcore 组件埋点。

说明 下载 Demo 源码,并进入 webapi.dotnetcore 目录,按照 Readme 的说明运行程序。
  1. 安装 NuGet 包。
    // 添加以下组件
    // OpenTracing.Contrib.NetCore(aspnetcore 中间件)
    // Jaeger(OpenTracing 的实现组件)
    // Microsoft.Extensions.Logging.Console(日志组件)
    
    dotnet add  package OpenTracing.Contrib.NetCore
    dotnet add  package Jaeger
    dotnet add  package Microsoft.Extensions.Logging.Console
  2. 用项目中 Startup.cs 里面的 Configure 方法注册中间件。
    // 注册 OpenTracing 组件埋点
    services.AddOpenTracing();
    // 过滤 httpclient 采集中的 Jaeger 数据上报请求
    var httpOption = new HttpHandlerDiagnosticOptions();
    httpOption.IgnorePatterns.Add(req => req.RequestUri.AbsolutePath.Contains("/api/traces"));
    services.AddSingleton(Options.Create(httpOption));
  3. 初始化和注册 Jaeger。
    // 添加 Jaeger Tracer
    services.AddSingleton<ITracer>(serviceProvider =>
    {
        string serviceName = serviceProvider.GetRequiredService<IHostingEnvironment>().ApplicationName;
        ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
         Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory)
         // 在链路追踪控制台获取 Jaeger Endpoint
              .WithEndpoint("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");
    
          // This will log to a default localhost installation of Jaeger.
          var tracer = new Tracer.Builder(serviceName)
              .WithSampler(new ConstSampler(true))
              .WithReporter(new RemoteReporter.Builder().WithSender(senderConfiguration.GetSender()).Build())
               .Build();
    
          // Allows code that can't use DI to also access the tracer.
          GlobalTracer.Register(tracer);
    
          return tracer;
    });

通过 gRPC 组件自动埋点

请按照以下步骤通过 gRPC 组件埋点。

说明 下载 Demo 源码,并按照 Readme 的说明运行程序。
  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));

手动埋点

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

说明 下载 Demo 源码,并进入 ManualDemo 目录,按照 Readme 的说明运行程序。
  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 上透传数据。总体流程如下:
       Client Span                                                Server Span
    ┌──────────────────┐                                       ┌──────────────────┐
    │                  │                                       │                  │
    │   TraceContext   │           Http Request Headers        │   TraceContext   │
    │ ┌──────────────┐ │          ┌───────────────────┐        │ ┌──────────────┐ │
    │ │ TraceId      │ │          │ X-B3-TraceId      │        │ │ TraceId      │ │
    │ │              │ │          │                   │        │ │              │ │
    │ │ ParentSpanId │ │ Inject   │ X-B3-ParentSpanId │Extract │ │ ParentSpanId │ │
    │ │              ├─┼─────────>│                   ├────────┼>│              │ │
    │ │ SpanId       │ │          │ X-B3-SpanId       │        │ │ SpanId       │ │
    │ │              │ │          │                   │        │ │              │ │
    │ │ Sampled      │ │          │ X-B3-Sampled      │        │ │ Sampled      │ │
    │ └──────────────┘ │          └───────────────────┘        │ └──────────────┘ │
    │                  │                                       │                  │
    └──────────────────┘                                       └──────────────────┘
    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);

常见问题

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

答:请检查 senderConfiguration 配置中的 Endpoint 是否填写正确。

Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory)
           // 在链路追踪控制台获取 Jaeger Endpoint
          .WithEndpoint("http://tracing-analysis-dc-sz.aliyuncs.com/adapt_your_token/api/traces");