可观测性框架-OpenTelemetry(trace-sdk篇)

2,687 阅读21分钟

OpenTelemetry

OpenTelemetry - CNCF · GitHub

目前是推荐使用Prometheus + Grafana做Metrics存储、展示,使用Jaeger做分布式追踪的存储和展示,使用Fluentd做日志存储和展示。
不过OpenTelemetry官方目前只有Trace相关的API是稳定的。Metrics相关的API当前还不稳定,所以官方文档中Metrcis相关的部分还不太完善。Logs相关部分还未开始做,优先级最低。
基于当前的这种情况,我目前只推荐用其来做trace相关的内容,等后面稳定了之后再接入此框架来做metrics和log相关的内容。本篇当前也只是介绍其trace相关内容(基于Go版本的SDK)。

后面预计还会出五篇同类型的文章:trace-collector篇、metrics-sdk使用篇、metrics-collector篇、logs-sdk使用篇、logs-collector篇。分别以sdk和collector端的角度进行讲解。

简介

OpenTelemetry(也称为 OTel)是一个开源可观测能力框架,由一系列工具、API 和 SDK 组成,使 IT 团队能够检测、生成、收集和导出远程监测数据以进行分析和了解软件性能和行为。

OpenTelemetry的核心工作目前主要集中在3个部分:

  • 规范的制定和协议的统一,规范包含数据传输、API的规范,协议的统一包含:HTTP W3C的标准支持及GRPC等框架的协议标准;
  • 多语言SDK的实现和集成,用户可以使用SDK进行代码自动注入和手动埋点,同时对其他三方库(Log4j、LogBack等)进行集成支持;
  • 数据收集系统的实现,当前是基于OpenCensus Service的收集系统,包括Agent和Collector。

由此可见,OpenTelemetry的自身定位很明确:数据采集和标准规范的统一,对于数据如何去使用、存储、展示、告警,官方是不涉及的。

为什么要学OpenTelemetry

  • 兼容性好:合并了OpenTracing和OpenCensus,可以同时兼容二者。
  • 跨平台:它提供了一个与厂商无关的实现,因此可以按照自己的需求将其发送到不同的后端进行分析,切换后端只用改很少的代码,扩展性极强。jaeger-client官方也已不再维护,并且强烈推荐使用openTelemetry。
  • 简化可观测性:正如OpenTelemetry所说的"高质量的观测下要求高质量的遥测"。希望看到更多的厂商转向OpenTelemetry,因为它更方便,且仅需测试单一标准。
  • 未来发展好:当前已经成为了CNCF的孵化项目之一

OTLP协议

opentelemetry.io/docs/refere… \

OTLP(全称 OpenTelemetry Protocol )是 OpenTelemetry 原生的遥测信号传递协议,虽然在 OpenTelemetry 的项目中组件支持了Zipkin v2或Jaeger Thrift的协议格式的实现,但是都是以第三方贡献库的形式提供的。只有 OTLP 是 OpenTelemetry 官方原生支持的格式。OTLP 的数据模型定义是基于 ProtoBuf 完成的,如果你需要实现一套可以收集 OTLP 遥测数据的后端服务,那就需要了解里面的内容,对应可以参考代码仓库:opentelemetry-proto(github.com/open-teleme…

概念

官方文档中概念颇多,很容易绕进去,我就列了几个在使用客户端SDK(openTelemetry-Go)时遇见最多的一些概念(collector相关后面有时间看了再出一篇),某些部分会说的比较详细,相关源码就不拷贝过来了,想看的可以去github看,这里限于篇幅就只用简单的文字把部分原理叙述一下。

Signals(信号)

每个信号由四个核心组件组成:APIs、SDKs、OpenTelemetry Protocol(OTLP)、Collector。下面是官方文档中的四种信号:

  • Traces(痕迹)

    • 为我们提供了用户或应用程序发出请求时会发生什么的全局图景。
    • 跟踪|开放遥测 (opentelemetry.io)
    • trace可视为span的有向无环图,span之间的边定义为父子关系。一个span表示一个事务中的操作。
  • Metrics(度量/指标)

    • 是在运行时捕获的有关服务的度量/指标。
  • Logs(日志)

    • 日志是带有时间戳的文本记录,可以是结构化(推荐)的,也可以是非结构化的,带有元数据metadata。
  • Baggage(附加信息)

    • 是指span之间传递的上下文信息
    • 是添加在 metrics、log、traces 中的注解信息,键值对需要唯一,无法更改。

Tracer Provider(追踪器提供者)

  • tracerProvider初始化还包括Resource、Exporter、Sampler和SpanLimit初始化,这通常是使用OpenTelemetry进行tracing的第一步。

  • 大多数应用程序中,tp初始化一次,其生命周期与运用程序的生命周期匹配。

  • 初始化:NewTracerProvider()方法

  • 源码探究

    •   // 
        type TracerProvider struct {
            mu             sync.Mutex // 保证map并发安全
            namedTracer    map[instrumentation.Scope]*tracer // 保存创建的tracer
            spanProcessors atomic.Value // span处理器,可以有多个
            sampler        Sampler // 采集器,
            idGenerator    IDGenerator // id生成器,用于生成traceId和spanId,目前只有一个默认的
            spanLimits     SpanLimits // 对span的限制配置
            resource       *resource.Resource // 资源,标识当前客户端
        }
        // tp有下面五个方法,基于篇幅具体源码分析就不展开讲了,想了解细节建议去翻看源码,这里就简单说一下这五个方法主要是用来干什么。// 通过此实现了tracer接口,返回一个tracer实例,如果已创建对应name的tracer会直接返回之前的实例
        func (p *TracerProvider) Tracer(name string, opts ...trace.TracerOption) trace.Tracer {}
        // 注册span处理器到tp,(目前使用场景来说一个tp一个exporter,然后一个exporter一般只会配一个spanProcessor,一个exporter配置多个spanProcessor的场景暂时还未遇到,所以用的比较少。。)
        func (p *TracerProvider) RegisterSpanProcessor(sp SpanProcessor) {}
        // 注销span处理器(同上)
        func (p *TracerProvider) UnregisterSpanProcessor(sp SpanProcessor) {}
        // 如果tp使用的是异步处理span的spanProcessor,比如使用WithBatcher()方式,就可以通过调用此方法主动通知exporter导出一批span到相关collector中
        func (p *TracerProvider) ForceFlush(ctx context.Context) error {}
        // 销毁tp
        func (p *TracerProvider) Shutdown(ctx context.Context) error {}
      

Tracer(追踪者)

  • tracer创建span,其中包含有关给定操作(如服务中的请求)所发生情况的详细信息。tracer是通过tracerProvider调用Tracer(name)创建的,如果之前创建过会直接返回之前创建的tracer实例。

  • span 一旦span完成,它就是不可变的并且不能再被修改。

    • span是通过tracer通过调用Start(ctx, name)方法来创建的,Start()方法会在内部调用newSpan()方法创建span,并且根据tp配置的采集器来判断该span是否要被采集。

    • attributes 属性:作为元数据应用于span的键值对,可用于聚合、过滤和分组跟踪。 可以在span创建时添加,也可在span完成之前的生命周期内的任何其他时间添加。

      // setting attributes at creation...
      ctx, span = tracer.Start(ctx, "attributesAtCreation", trace.WithAttributes(attribute.String("hello", "world")))
      // ... and after creation
      span.SetAttributes(attribute.Bool("isTrue", true), attribute.String("stringAttr", "hi!"))
      
    • event 事件:是span上的人类可读信息,表示在其生命周期内正在发生的事情。例如,假设一个函数需要访问临界区资源。可以在两点创建一个事件:一次是在我们尝试访问资源时,另一次是在我们获取互斥锁时。

      span.AddEvent("Acquiring lock")
      mutex.Lock()
      span.AddEvent("Got lock, doing work...")
      // do stuff
      span.AddEvent("Unlocking")
      mutex.Unlock()
      

      event 的一个有用特征是它们的时间戳显示为从跨度开始的偏移量,使您可以轻松查看它们之间经过了多少时间。 事件也可以有自己的属性。

    • state 状态:可以在 span 上设置状态,通常用于指定 span 正在跟踪的操作中存在错误。

      result, err := operationThatCouldFail()
      if err != nil {
          span.SetStatus(codes.Error, "operationThatCouldFail failed")
      }
      

      当前opentelemetry中spanState只有三种状态:Unset,Error,OK。 默认情况下,所有跨度的状态都是Unset。在极少数情况下,您可能还希望将状态设置为Ok。不过,这通常不是必需的。 如果您有一个失败的操作并且您希望捕获它产生的错误,您可以记录该错误。

      result, err := operationThatCouldFail()
      if err != nil {
          span.SetStatus(codes.Error, "operationThatCouldFail failed")
          span.RecordError(err)
      }
      

      官方强烈建议在使用RecordError时也将span的状态设为Error(因为RecordError函数在调用时不会自动设置span状态为Error),除非你不希望将追踪时出现失败操作的span当作错误span。

Exporters(导出器)

不同的导出器使用示例github上面也有:opentelemetry-go/example at main · open-telemetry/opentelemetry-go · GitHub

traceExporter

  • traceExporter将trace发送给consumer。
  • 当前官方支持的consumer有jaeger、zipkin、otlp-collector、stdout(控制台、文件等标准输出),并且提供了相关exporter
  • consumer可以是调试和开发时的标准输出、OpenTelemetry Collector或者其他任何开源或供应商后端。

metricExporter

  • metricExporter将trace发送给consumer。
  • 当前官方支持的metric exporter有Prometheus、otlp-collector、stdout(控制台、文件等),并且提供了相关exporter
  • consumer可以是调试和开发时的标准输出、OpenTelemetry Collector或者其他任何开源或供应商后端。

Resource (资源)

resource 附加于某个 process 产生的所有 trace 的键值对,在初始化阶段指定并传递到 collector 中。(tracerProvider将resource与collector关联起来)

资源是一种特殊类型的属性,适用于流程生成的所有跨度。这些元数据应用于表示有关非临时进程的基础元数据 - 例如,进程的主机名或其实例 ID。

资源应在初始化时分配给跟踪器提供程序,并且创建与属性非常相似:

resources := resource.NewWithAttributes(
    semconv.SchemaURL,
    semconv.ServiceNameKey.String("myService"),
    semconv.ServiceVersionKey.String("1.0.0"),
    semconv.ServiceInstanceIDKey.String("abcdef12345"),
)
​
provider := sdktrace.NewTracerProvider(
    ...
    sdktrace.WithResource(resources),
)

初始化Resource:

  • 直接使用默认的Default()
  • New()
  • NewWithAttributes()

Merge(resource1,resource2)可以将两个资源合并成一个然后返回。

初始化resource的时候还可以配置检测器:

  • Detector 检测器 用于收集相关信息添加到resource中。

    • WithAttributes:将传入的0个或多个键值对添加到resource中。(最常用)

    • 还可以通过实现自动检测资源。这些可能会发现有关当前正在运行的进程、运行该操作系统的操作系统、托管该操作系统实例的云提供程序或任何其他资源属性的信息。下面是官方已实现的自动检测器:

      • WithFromEnv:将环境变量中的相关属性添加到要配置的resource中。(获取环境变量中key为OTEL_RESOURCE_ATTRIBUTESOTEL_SERVICE_NAME的值) 除了WithFromEnv调用的是NewSchemaless(),不用包含semconv.SchemaURL,,其他检测器底层调用生成resource的函数都会注入semconv.SchemaURL
      • WithHost:将host的属性添加到要配置的resource中。(主机名)
      • WithTelemetrySDK:将SDK版本信息添加到resource中。(sdk的名称、使用的语言、版本)
      • WithSchemaURL:设置Schema URL到resource中。
      • WithOS:将所有操作系统属性添加到配置的resource中。相关属性请参见各个WithOS*函数以配置特定属性。(当前操作系统的类型和描述)
      • WithProcess:将所有Process属性添加到配置的资源中。(进程的pid,可执行文件的名称和路径,命令行参数,所有者,运行时的名称、版本和描述) 注意!此选项将包含进程命令行参数。如果这些包含敏感信息,则将包含在导出的资源中。 相关属性请参见各个WithProcess*函数以配置特定属性。
      • WithContainer:将所有容器属性添加到配置的资源中。(容器id) 相关属性请参见各个WithContainer*函数以配置特定属性。

      也可自己自定义自动检测器,只需实现检测器Detector接口,然后使用WithDetectors()函数注册到resource中。resource会在程序启动时调用所有被注册的Dector的Detect()方法来收集信息

SpanProcessor(span处理器)

SDK的spanProcessor决定exporter什么时候发送span到collector。而collector的spanProcessor暂时还未看(猜测是决定什么时候写入后端存储)

openTelemetry-go SDK根据处理方式分为两种处理器

  • 同步处理器

    • 用WithSyncer(exporter)声明,表示用同步的方式将span发送到后端存储。 其适用于测试、调试或显示其他功能的示例,不建议生产环境用此处理方式。
  • 异步处理器

    • 用WithBatcher(exporter)声明,表示用异步的方式将span发送到后端。 生产环境建议使用
    • 原理:采集到的span发送到本地缓冲区(一个队列),触发某些信号(达到批处理最大数量or批处理间隔时间)后通知exporter将一批span发送给collector。同时也可以配置相关参数:队列(缓冲区)大小(默认2048)、最大批处理数量(默认512)、批处理触发间隔时间(默认5s)、队列是否阻塞(默认false)、exporter导出span到collector的超时时间(默认30s)。详情参看源码。
      流程图: openTelemetry-spanProcessor.png

SpanLimit(对span进行限制)

tracerProvider初始化时配置

  • span的AttributeValue的最大长度(默认-1无限制)
  • span的Attribute最大数量(默认最多128个)
  • span的事件最大数量(默认128)
  • span的link最大数量(默认128)
  • span每个事件的Attribute最大数量(默认128)
  • span每个link的Attribute最大数量(默认128)

Sampler(采样器)

处理和导出数据|开放遥测 (opentelemetry.io)

为什么要采样?控制发送到可观测性后端的span,从而降低摄取成本。

sampling(采样)是限制系统生成的跟踪量的过程。应使用的确切采样器取决于您的特定需求,但通常应在跟踪开始时做出决定,并允许采样决策传播到其他服务。

客户端采样配置(opentelemetry-go)

配置跟踪器提供程序时,需要在跟踪器提供程序上设置采样器,如下所示:

provider := sdktrace.NewTracerProvider(
    sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
  • AlwaysSample意味着始终采样每条trace,NeverSample()则反之亦然。

  • TraceIDRatioBased,它将根据提供给采样器的分数对一小部分迹线进行采样。因此,如果将其设置为 .5,则将对一半的跟踪进行采样。
    采集原理:首先根据系数fraction乘以2^63来确定采集边界Bound,然后根据traceID(traceID是一个[16]byte)来判断该span是采集还是不采集。
    判断依据:截取traceID0个到第7个字节,然后根据大端方式生成一个64位的数字,然后再取其高63位得到x,如果 x < Bound 则采集该span,反之不采集。 部分源码:

    func TraceIDRatioBased(fraction float64) Sampler {
        ...
        return &traceIDRatioSampler{
            traceIDUpperBound: uint64(fraction * (1 << 63)),
            description:       fmt.Sprintf("TraceIDRatioBased{%g}", fraction),
        }
    }
    func (ts traceIDRatioSampler) ShouldSample(p SamplingParameters) SamplingResult {// 决定最终是否采样
        psc := trace.SpanContextFromContext(p.ParentContext)
        x := binary.BigEndian.Uint64(p.TraceID[0:8]) >> 1
        if x < ts.traceIDUpperBound {//采样
            return SamplingResult{
                Decision:   RecordAndSample,
                Tracestate: psc.TraceState(),
            }
        }
        return SamplingResult{//丢弃
            Decision:   Drop,
            Tracestate: psc.TraceState(),
        }
    }
    
  • ParentBased,其行为因传入采样决策而异。通常,这将对具有已采样父项的跨度进行采样,而不会对父项未采样的跨度进行采样。

    • 这样做有一个显着的优势:您始终可以获得完整的picture(描述)。

      • 工作原理:对于作为跟踪树的第一个跨度(根跨度),我们决定是否对其进行采样。

        该决策通过上下文传播通过此跟踪中的其余子跨度冒泡,使每个子级知道是否需要对其进行采样。

        重要的是要了解这是一个复合采样器,这意味着它不会独立存在,而是让我们定义如何为每个用例进行采样。例如 – 您可以使用根采样器定义当我们没有父级时该怎么做。您可以定义当我们有一个具有不同采样器的远程父/本地父时该怎么做。

    • 在性能方面 - 即使您决定使用 0% 采样,开销也很小,因为无论如何都会创建跨度但不会发送。这样做是为了传播上下文。

    这是最受欢迎的采样器,也是官方文档推荐的采样器。生产环境下建议TraceIDRatioBasedParentBased一起使用

  • 默认情况下(未设置时),采样器使用ParentBased(AlwaysSample())

collector采样配置

不同的collector(opentelemetry-collector、jaeger等)提供的有不同的采样方式。
jaeger:www.jaegertracing.io/docs/1.39/s… opentelemetry-collector:

采样方式(补充)

  • 基于头部的采样(jaeger-collector采用) 顾名思义,基于头部的采样意味着在跟踪开始时决定是否预先采样。

    由于简单性,这是当今最常见的采样方式,但由于我们事先不知道所有内容,我们被迫做出任意决定(例如随机百分比的所有跨度进行采样),这可能会限制我们理解一切的能力。这是在OTEL发行版级别完成的。

    基于头部的采样的一个缺点是,您无法决定只对有错误的跨度进行采样,因为您事先不知道这一点(采样或不采样的决定发生在错误发生之前)

  • 基于尾部的采样(opentelemetry-collector实现了这种:github.com/open-teleme…) 与基于头部的采样相反,在这里,当我们已经收集数据时,我们在整个流程结束时做出决定。首先,为确保捕获所有跨度,请在 SDK 中使用默认采样器或 AlwaysOn 采样器。

    这对于指标很有用,例如,当我们想要收集延迟时,我们必须知道无法提前完成的确切开始和结束时间。

    此外,基于头部的缺点是基于尾部的优势 - 只能对有错误的跨度进行采样。

    尾部采样的潜在问题

Context Propagation(上下文传播)

opentelemetry.io/docs/instru…

  • 上下文传播是启用分布式跟踪的核心概念。通过上下文传播,无论span是在何处生成的,都可以相互关联并组合成trace。

  • 我们通过两个子概念来定义上下文传播:上下文context和传播propagation。

    • context是一个对象,其中包含发送和接收服务的信息,用于将一个span与另一个span相关联,并将其与整个trace相关联。
    • propagation是在服务和进程之间移动上下文的机制。通过这样做,它会组装分布式跟踪。它序列化或反序列化跨度上下文,并提供要从一个服务传播到另一个服务的相关跟踪信息。我们现在有了所谓的 :跟踪上下文
  • 在开放遥测中还有其他形式的上下文。例如,一些上下文是关于跨度的W3C规范的实现,而在OpenTelemetry中,这称为SpanContext

要阅读有关传播的更多信息,请参阅 go.opentelemetry.io/otel/propagation 和 go.opentelemetry.io/otel/baggage。

Propagators(目前源码中看似乎跟baggage强相关)

传播器,比如在多进程的调用中,开启传播器用于跨服务传播 spanContext。 opentelemetry.io/docs/refere…

  • 类型 Propagators API 目前定义了一种Propagator类型:

    • TextMapPropagator是一种类型,它将字符串键/值对作为值注入载体并从载体中提取值。

    Propagator将来会添加二进制类型(参见#437)。

  • Carrier 载体

    • 载体carrier是Propagator 用来读取值和写入值的介质。每个特定Propagator类型都定义了其预期的载体类型,例如字符串映射或字节数组。

      Inject中使用的载体预计是可变的。

  • Operations 操作 propagator必须定义Inject和Extract操作,以便分别向载体写入值和从载体读取值。每种propagator类型都必须定义特定的载体类型,并且可以定义其他参数。

    • Inject 注入:注入值到载体中,例如,写入到HTTP请求头。 必须的参数:

      • 一个context。
      • 一个carrier。例如,一个传出的消息或HTTP请求
    • Extract 提取:从传入的请求中提取值。例如,来自HTTP请求的标头。 必须的参数:

      • 一个context。
      • 一个carrier。例如,一个传出的消息或HTTP请求

      返回一个由参数context派生出来的context,包含提取的值,它可以是spanContext,Baggage或另一个cross-cutting concern 上下文。

Collector(还未细看)

虽然 Collector 翻译过来叫接收,但它负责遥测数据源的接收、处理和导出三部分,提供了与供应商无关的实现。

collector 是个实体组件,有两个部署方案,Agent(与应用程序一起在本地运行的守护进程,各个 host 负责该 host 上的遥测数据)和collector(独立运行的服务,接收所有遥测数据)。

其中,Metrics、Logging、Trace、Baggage 叫Signal,以上的对象中除了Collector,其它几个由于没有具体部署对象,名词一多理解起来还是比较费劲,需要多翻官方文档。

终极目标

实现Metrics、Tracing、Logging的融合,作为APM的数据采集终极解决方案。

  • Tracing:提供了一个请求从接收到处理完成整个生命周期的跟踪路径,一次请求通常过经过N个系统,因此也被称为分布式链路追踪
  • Metrics:例如cpu、请求延迟、用户访问数等Counter、Gauge、Histogram指标
  • Logging:传统的日志,提供精确的系统记录

这三者的组合可以形成大一统的APM解决方案:

  • 基于Metrics告警发现异常 通过Tracing定位到具体的系统和方法
  • 根据模块的日志最终定位到错误详情和根源
  • 调整Metrics等设置,更精确的告警/发现问题

当前流程:Specification Status Summary | OpenTelemetry

兼容性

兼容性|开放遥测 (opentelemetry.io)

局限性

OpenTelemetry 的局限性

安装

OpenTelemetry 分为两部分:用于检测代码的 API 和实现 API 的 SDK。

  • API安装 要开始将 OpenTelemetry 集成到任何项目中,API 用于定义遥测数据的生成方式。要在您的应用程序中生成跟踪遥测数据,您将使用go.opentelemetry.io/otel/trace包中的 OpenTelemetry Trace API。

    •   go get go.opentelemetry.io/otel \
               go.opentelemetry.io/otel/trace
      
  • SDK安装 OpenTelemetry 在其 OpenTelemetry API 的实现中被设计为模块化。OpenTelemetry Go 项目提供了一个 SDK 包,go.opentelemetry.io/otel/sdk它实现了这个 API 并遵守 OpenTelemetry 规范。

    •   go get go.opentelemetry.io/otel/sdk \
                 go.opentelemetry.io/otel/exporters/stdout/stdouttrace
      

使用

(可能跟概念部分内容有重叠)

客户端层面

quickStart:opentelemetry.io/docs/instru…

API

trace是一种telemetry,表示服务正在完成的工作。是处理交易的参与者之间的连接记录,通常通过客户端/服务器请求处理和其他形式的通信。

导入的包

  • 创建trace

    •   newCtx, span := otel.Tracer(name).Start(ctx, 一般为被检测的函数名称)
        defer span.End()
        do something...
      

      跨度之间通过context去关联

      Tracer()返回一个新的tracer,所有的tracer全部保存在一个map中(map[tracerName]*tracer),如果tracer已存在map中会直接返回之前创建的tracer

SDK

SDK 将来自 OpenTelemetry API 的遥测数据连接到导出器。

导入包

"go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
  • 创建导出器(Exporter) 导出器是允许将遥测数据(telemetry data)发送到某处的包:发送到控制台(console)(这就是我们在这里所做的),或者发送到远程系统(remote system)或收集器(collector)以进行进一步分析和/或丰富。OpenTelemetry 通过其生态系统支持各种出口商,包括JaegerZipkinPrometheus等流行的开源工具。

    参考官方示例:github.com/open-teleme…

    • 控制台

      func newExporter(w io.Writer) (trace.SpanExporter, error) {
          return stdouttrace.New(
              stdouttrace.WithWriter(w),
              // Use human-readable output.
              stdouttrace.WithPrettyPrint(),
              // Do not print timestamps for the demo.
          stdouttrace.WithoutTimestamps(),
          )
      }
      

      这将创建一个带有基本选项的新控制台导出器。稍后您将在配置 SDK 向其发送遥测数据时使用此功能,但首先您需要确保数据是可识别的。

    • jaeger 参考:github.com/open-teleme…

      先下载依赖包:go get go.opentelemetry.io/otel/exporters/jaeger

      // url example = http://localhost:14268/api/traces
      func newExporterByJaeger(url string) (trace.SpanExporter, error) {
          return jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
      }
      
  • 创建资源(Resource) 遥测数据(telemetry data)对于解决服务问题至关重要。问题是,您需要一种方法来识别数据来自哪个服务,甚至是哪个服务实例。OpenTelemetry 使用 Resource 来表示生成遥测的实体。

    •   // newResource returns a resource describing this application.
        func newResource() *resource.Resource {
            r, _ := resource.Merge(
                resource.Default(),
                resource.NewWithAttributes(
                    semconv.SchemaURL,
                    semconv.ServiceNameKey.String("fib"),
                    semconv.ServiceVersionKey.String("v0.1.0"),
                    attribute.String("environment", "demo"),
                ),
            )
            return r
        }
      

      您希望与 SDK 处理的所有遥测数据相关联的任何信息都可以添加到返回的Resource. 这是通过向 注册 来完成ResourceTracerProvider。您现在可以创造的东西!

  • 安装跟踪器提供者(Tracer Provider)

    您已对您的应用程序进行检测以生成telemetry数据,并且您有一个导出器exporter将该数据发送到控制台collector,但它们是如何连接的?这是 TracerProvider 使用的地方。这是一个集中点,instrumentation将从中获取 Tracer,并将来自这些 Tracer 的遥测数据汇集到导出管道export pipelines

    接收数据并最终将数据传输给导出器exporter的管道称为 SpanProcessor。一个 TracerProvider 可以配置多个 span processors,但对于此示例,您只需要配置一个。使用以下内容更新您的main功能main.go

    •   func main() {
            l := log.New(os.Stdout, "", 0)
        ​
            // Write telemetry data to a file.
            f, err := os.Create("traces.txt")
            if err != nil {
                l.Fatal(err)
            }
            defer f.Close()
        ​
            exp, err := newExporter(f)
            if err != nil {
                l.Fatal(err)
            }
        ​
            tp := trace.NewTracerProvider(
                trace.WithBatcher(exp),
                trace.WithResource(newResource()),
            )
            defer func() {
                if err := tp.Shutdown(context.Background()); err != nil {
                    l.Fatal(err)
                }
            }()
            otel.SetTracerProvider(tp)
        ​
            /* … */
        }
      

      注意trace.NewTracerProvider()中可规定发送span的方式是异步还是同步,当使用trace.WithSyncer()时是同步发送;当使用trace.WithBatcher()时是通过通道(默认长度2048)异步按批发送,每隔一段时间(默认批处理5s超时触发)发送 or 达到最大批处理长度(默认512)时把要批处理的span利用导出器导出(也可手动调用ForceFlush强制导出),当通道满了之后再存入span也有两种策略:丢弃这个span(默认)或者阻塞住直到队列有空位