1. 简介

Spring Cloud Sleuth 为 Spring Cloud 实现了分布式跟踪解决方案。spring-doc.cadn.net.cn

1.1. 术语

Spring Cloud Sleuth 借用了 Dapper 的术语。spring-doc.cadn.net.cn

跨度:基本工作单元。例如,发送 RPC 是一个新的跨度,向 RPC 发送响应也是如此。 跨度由跨度的唯一 64 位 ID 和跨度所属跟踪的另一个 64 位 ID 标识。 跨度还具有其他数据,例如描述、带时间戳的事件、键值注释(标签)、导致它们的跨度的 ID 以及进程 ID(通常是 IP 地址)。spring-doc.cadn.net.cn

跨度可以启动和停止,并跟踪其计时信息。 创建跨度后,必须在将来的某个时间点停止它。spring-doc.cadn.net.cn

启动跟踪的初始跨度称为root span.ID 的值 的 等于跟踪 ID。

跟踪:一组跨度形成树状结构。 例如,如果运行分布式大数据存储,则跟踪可能由PUT请求。spring-doc.cadn.net.cn

注解:用于及时记录事件的存在。有了 Brave 仪器,我们不再需要设置特殊事件 让 Zipkin 了解客户端和服务器是谁,在哪里 请求的开始和结束位置。出于学习目的, 但是,我们标记这些事件是为了突出什么类型 发生的行动。spring-doc.cadn.net.cn

  • cs:客户端发送。客户已提出请求。此注释指示跨度的开始。spring-doc.cadn.net.cn

  • sr:服务器接收:服务器端收到请求并开始处理。 减去cstimestamp 显示网络延迟。spring-doc.cadn.net.cn

  • ss:服务器发送。在请求处理完成时(当响应发送回客户端时)进行注释。 减去srtimestamp 显示服务器端处理请求所需的时间。spring-doc.cadn.net.cn

  • cr:客户已收到。表示跨度的结束。 客户端已成功收到来自服务器端的响应。 减去cstimestamp 显示客户端接收来自服务器的响应所需的全部时间。spring-doc.cadn.net.cn

下图显示了 SpanTrace 在系统中的外观,以及 Zipkin 注释:spring-doc.cadn.net.cn

跟踪信息传播

音符的每种颜色都表示一个跨度(有七个跨度 - 从 AG)。 请考虑以下注释:spring-doc.cadn.net.cn

Trace Id = X
Span Id = D
Client Sent

此注释表示当前范围的“跟踪 ID”设置为 X,将“范围 ID”设置为 D。 此外,Client Sent事件发生了。spring-doc.cadn.net.cn

下图显示了跨度的父子关系的外观:spring-doc.cadn.net.cn

父子关系

1.2. 目的

以下部分引用上图中所示的示例。spring-doc.cadn.net.cn

1.2.1. 使用 Zipkin 进行分布式跟踪

此示例有七个跨度。 如果转到 Zipkin 中的跟踪,可以在第二个跟踪中看到这个数字,如下图所示:spring-doc.cadn.net.cn

痕迹

但是,如果选择特定跟踪,则可以看到四个跨度,如下图所示:spring-doc.cadn.net.cn

跟踪信息传播
当您选择特定跟踪时,您会看到合并的跨度。 这意味着,如果有两个跨度发送到 Zipkin,并带有 Server Received 和 Server Sent 或 Client Received 和 Client Sent 注释,则它们将显示为单个 span。

为什么在这种情况下七跨和四跨之间有区别?spring-doc.cadn.net.cn

  • 一个跨度来自http:/start跨度。它有服务器接收 (sr) 和服务器已发送 (ss) 注释。spring-doc.cadn.net.cn

  • 两个跨度来自 RPC 调用service1service2http:/foo端点。 发送的客户端 (cs) 和收到的客户 (cr) 事件发生在service1边。 服务器已接收 (sr) 和服务器已发送 (ss) 事件发生在service2边。 这两个跨度形成一个与 RPC 调用相关的逻辑跨度。spring-doc.cadn.net.cn

  • 两个跨度来自 RPC 调用service2service3http:/bar端点。 发送的客户端 (cs) 和收到的客户 (cr) 事件发生在service2边。 收到的服务器 (sr) 和服务器已发送 (ss) 事件发生在service3边。 这两个跨度形成一个与 RPC 调用相关的逻辑跨度。spring-doc.cadn.net.cn

  • 两个跨度来自 RPC 调用service2service4http:/baz端点。 发送的客户端 (cs) 和收到的客户 (cr) 事件发生在service2边。 服务器已接收 (sr) 和服务器已发送 (ss) 事件发生在service4边。 这两个跨度形成一个与 RPC 调用相关的逻辑跨度。spring-doc.cadn.net.cn

因此,如果我们计算物理跨度,我们会得到一个来自http:/start,两个来自service1service2,两个来自service2service3,两个来自service2service4.总之,我们总共有七个跨度。spring-doc.cadn.net.cn

从逻辑上讲,我们看到总共四个 Span 的信息,因为我们有一个与传入请求相关的 Span 自service1以及与 RPC 调用相关的三个跨度。spring-doc.cadn.net.cn

1.2.2. 可视化错误

Zipkin 可让您可视化跟踪中的错误。 当抛出异常但未捕获时,我们在跨度上设置了适当的标签,然后 Zipkin 可以正确地对其进行着色。 您可以在跟踪列表中看到一条红色跟踪。出现这是因为引发了异常。spring-doc.cadn.net.cn

如果单击该跟踪,您会看到类似的图片,如下所示:spring-doc.cadn.net.cn

错误跟踪

如果单击其中一个跨度,则会看到以下内容spring-doc.cadn.net.cn

错误跟踪信息传播

span 显示错误的原因以及与之相关的整个堆栈跟踪。spring-doc.cadn.net.cn

1.2.3. 使用 Brave 进行分布式跟踪

从版本开始2.0.0,Spring Cloud Sleuth 使用 Brave 作为跟踪库。 因此,Sleuth 不再负责存储上下文,而是将工作委托给 Brave。spring-doc.cadn.net.cn

由于《侦探》的命名和标记惯例与《勇敢者》不同,我们决定从现在开始遵循《勇敢者》的惯例。 但是,如果你想使用旧版侦探方法,你可以将spring.sleuth.http.legacy.enabled属性设置为true.spring-doc.cadn.net.cn

1.2.4. 现场示例

Zipkin 部署在 Pivotal Web 服务上
单击 Pivotal Web Services 图标即可实时查看!单击 Pivotal Web Services 图标即可实时查看!

Zipkin 中的依赖关系图应类似于下图:spring-doc.cadn.net.cn

依赖
Zipkin 部署在 Pivotal Web 服务上
单击 Pivotal Web Services 图标即可实时查看!单击 Pivotal Web Services 图标即可实时查看!

1.2.5. 对数相关性

当使用 grep 通过扫描等于(例如) 的跟踪 ID 来读取这四个应用程序的日志时2485ec27856c56f4,您将获得类似于以下内容的输出:spring-doc.cadn.net.cn

service1.log:2016-02-26 11:15:47.561  INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application   : Hello from service1. Calling service2
service2.log:2016-02-26 11:15:47.710  INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application   : Hello from service2. Calling service3 and then service4
service3.log:2016-02-26 11:15:47.895  INFO [service3,2485ec27856c56f4,1210be13194bfe5,true] 68060 --- [nio-8083-exec-1] i.s.c.sleuth.docs.service3.Application   : Hello from service3
service2.log:2016-02-26 11:15:47.924  INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application   : Got response from service3 [Hello from service3]
service4.log:2016-02-26 11:15:48.134  INFO [service4,2485ec27856c56f4,1b1845262ffba49d,true] 68061 --- [nio-8084-exec-1] i.s.c.sleuth.docs.service4.Application   : Hello from service4
service2.log:2016-02-26 11:15:48.156  INFO [service2,2485ec27856c56f4,9aa10ee6fbde75fa,true] 68059 --- [nio-8082-exec-1] i.s.c.sleuth.docs.service2.Application   : Got response from service4 [Hello from service4]
service1.log:2016-02-26 11:15:48.182  INFO [service1,2485ec27856c56f4,2485ec27856c56f4,true] 68058 --- [nio-8081-exec-1] i.s.c.sleuth.docs.service1.Application   : Got response from service2 [Hello from service2, response from service3 [Hello from service3] and from service4 [Hello from service4]]

如果您使用日志聚合工具(例如 KibanaSplunk 等),则可以对发生的事件进行排序。 Kibana 中的示例类似于下图:spring-doc.cadn.net.cn

与 Kibana 的日志关联

如果您想使用 Logstash,以下列表显示了 Logstash 的 Grok 模式:spring-doc.cadn.net.cn

filter {
  # pattern matching logback pattern
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
  }
  date {
    match => ["timestamp", "ISO8601"]
  }
  mutate {
    remove_field => ["timestamp"]
  }
}
如果要将 Grok 与 Cloud Foundry 中的日志一起使用,则必须使用以下模式:
filter {
  # pattern matching logback pattern
  grok {
    match => { "message" => "(?m)OUT\s+%{TIMESTAMP_ISO8601:timestamp}\s+%{LOGLEVEL:severity}\s+\[%{DATA:service},%{DATA:trace},%{DATA:span},%{DATA:exportable}\]\s+%{DATA:pid}\s+---\s+\[%{DATA:thread}\]\s+%{DATA:class}\s+:\s+%{GREEDYDATA:rest}" }
  }
  date {
    match => ["timestamp", "ISO8601"]
  }
  mutate {
    remove_field => ["timestamp"]
  }
}
使用 Logstash 的 JSON 日志返回

通常,您不想将日志存储在文本文件中,而是存储在 Logstash 可以立即选择的 JSON 文件中。为此,您必须执行以下作(为了便于阅读,我们将groupId:artifactId:version符号)。spring-doc.cadn.net.cn

依赖项设置spring-doc.cadn.net.cn

  1. 确保 Logback 位于类路径 (ch.qos.logback:logback-core).spring-doc.cadn.net.cn

  2. 添加 Logstash Logback 编码。例如,要使用 version4.6net.logstash.logback:logstash-logback-encoder:4.6.spring-doc.cadn.net.cn

日志备份设置spring-doc.cadn.net.cn

请考虑以下 Logback 配置文件示例(名为 logback-spring.xml)。spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

    <springProperty scope="context" name="springAppName" source="spring.application.name"/>
    <!-- Example for logging into the build folder of your project -->
    <property name="LOG_FILE" value="${BUILD_FOLDER:-build}/${springAppName}"/>

    <!-- You can override this to have a custom pattern -->
    <property name="CONSOLE_LOG_PATTERN"
              value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>

    <!-- Appender to log to console -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <!-- Minimum logging level to be presented in the console logs-->
            <level>DEBUG</level>
        </filter>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- Appender to log to file -->
    <appender name="flatfile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

    <!-- Appender to log to file in a JSON format -->
    <appender name="logstash" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE}.json</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_FILE}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>
            <maxHistory>7</maxHistory>
        </rollingPolicy>
        <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                <timestamp>
                    <timeZone>UTC</timeZone>
                </timestamp>
                <pattern>
                    <pattern>
                        {
                        "timestamp": "@timestamp",
                        "severity": "%level",
                        "service": "${springAppName:-}",
                        "trace": "%X{traceId:-}",
                        "span": "%X{spanId:-}",
                        "baggage": "%X{key:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "rest": "%message"
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>

    <springProfile name="logzio">
        <!-- Use shutdownHook so that we can close gracefully and finish the log drain -->
        <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
        <appender name="LogzioLogbackAppender" class="io.logz.logback.LogzioLogbackAppender">
            <token>${LOGZ_IO_API_TOKEN}</token>
            <logzioUrl>https://listener.logz.io:8071</logzioUrl>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>INFO</level>
            </filter>
            <debug>true</debug>
            <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
                <providers>
                    <timestamp>
                        <timeZone>UTC</timeZone>
                    </timestamp>
                    <pattern>
                        <pattern>
                            {
                            "timestamp": "@timestamp",
                            "severity": "%level",
                            "service": "${springAppName:-}",
                            "trace": "%X{traceId:-}",
                            "span": "%X{spanId:-}",
                            "baggage": "%X{key:-}",
                            "pid": "${PID:-}",
                            "thread": "%thread",
                            "class": "%logger{40}",
                            "rest": "%message"
                            }
                        </pattern>
                    </pattern>
                </providers>
            </encoder>
        </appender>

        <root level="info">
            <!-- IMPORTANT: make sure to include this line, otherwise the appender won't be used -->
            <appender-ref ref="LogzioLogbackAppender"/>
        </root>
    </springProfile>

    <root level="INFO">
        <appender-ref ref="console"/>
        <!-- uncomment this to have also JSON logs -->
        <!--<appender-ref ref="logstash"/>-->
        <!--<appender-ref ref="flatfile"/>-->
    </root>
</configuration>

该 Logback 配置文件:spring-doc.cadn.net.cn

如果您使用自定义logback-spring.xml,则必须通过spring.application.namebootstrap而不是application属性文件。 否则,您的自定义日志备份文件无法正确读取该属性。

1.2.6. 传播跨度上下文

跨度上下文是必须跨进程边界传播到任何子跨度的状态。 跨度上下文的一部分是行李。跟踪和跨度 ID 是跨度上下文的必需部分。 行李是可选部件。spring-doc.cadn.net.cn

Baggage 是存储在 span 上下文中的一组键:值对。 行李与痕迹一起旅行,并附着在每个跨度上。 Spring Cloud Sleuth 知道,如果 HTTP 标头前缀为baggage-对于消息传递,它以baggage_.spring-doc.cadn.net.cn

目前对行李物品的数量或尺寸没有限制。 但是,请记住,太多可能会降低系统吞吐量或增加 RPC 延迟。 在极端情况下,由于超过传输级消息或标头容量,过多的行李可能会使应用程序崩溃。

以下示例显示了在跨度上设置行李:spring-doc.cadn.net.cn

Span initialSpan = this.tracer.nextSpan().name("span").start();
ExtraFieldPropagation.set(initialSpan.context(), "foo", "bar");
ExtraFieldPropagation.set(initialSpan.context(), "UPPER_CASE", "someValue");
行李与跨度标签

行李随跟踪一起移动(每个子跨度都包含其父级的行李)。 Zipkin 对行李一无所知,也不会收到该信息。spring-doc.cadn.net.cn

从 Sleuth 2.0.0 开始,您必须显式传递行李密钥名称 在您的项目配置中。在此处阅读有关该设置的更多信息

标记附加到特定跨度。换句话说,它们仅针对该特定跨度显示。 但是,您可以按标记搜索以查找跟踪,假设存在具有搜索标记值的跨度。spring-doc.cadn.net.cn

如果您希望能够根据行李查找跨度,则应在根跨度中添加相应的条目作为标签。spring-doc.cadn.net.cn

跨度必须在范围内。

以下列表显示了使用行李的集成测试:spring-doc.cadn.net.cn

设置
spring.sleuth:
  baggage-keys:
    - baz
    - bizarrecase
  propagation-keys:
    - foo
    - upper_case
代码
initialSpan.tag("foo",
        ExtraFieldPropagation.get(initialSpan.context(), "foo"));
initialSpan.tag("UPPER_CASE",
        ExtraFieldPropagation.get(initialSpan.context(), "UPPER_CASE"));

1.3. 将侦探添加到项目中

本节介绍如何使用 Maven 或 Gradle 将 Sleuth 添加到您的项目中。spring-doc.cadn.net.cn

要确保您的应用程序名称在 Zipkin 中正确显示,请将spring.application.name属性bootstrap.yml.

1.3.1. Only Sleuth(对数相关)

如果只想使用 Spring Cloud Sleuth 而不使用 Zipkin 集成,请将spring-cloud-starter-sleuth模块添加到您的项目中。spring-doc.cadn.net.cn

以下示例显示了如何使用 Maven 添加 Sleuth:spring-doc.cadn.net.cn

专家
<dependencyManagement> (1)
      <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-dependencies</artifactId>
              <version>${release.train.version}</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencies>
</dependencyManagement>

<dependency> (2)
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
1 我们建议您通过 Spring BOM 添加依赖项管理,这样您就不需要自己管理版本。
2 将依赖项添加到spring-cloud-starter-sleuth.

以下示例展示了如何使用 Gradle 添加 Sleuth:spring-doc.cadn.net.cn

Gradle
dependencyManagement { (1)
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
    }
}

dependencies { (2)
    compile "org.springframework.cloud:spring-cloud-starter-sleuth"
}
1 我们建议您通过 Spring BOM 添加依赖项管理,这样您就不需要自己管理版本。
2 将依赖项添加到spring-cloud-starter-sleuth.

1.3.2. 通过 HTTP 与 Zipkin 进行侦探

如果您同时想要 Sleuth 和 Zipkin,请添加spring-cloud-starter-zipkinDependency。spring-doc.cadn.net.cn

以下示例显示了如何为 Maven 执行此作:spring-doc.cadn.net.cn

专家
<dependencyManagement> (1)
      <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-dependencies</artifactId>
              <version>${release.train.version}</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencies>
</dependencyManagement>

<dependency> (2)
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
1 我们建议您通过 Spring BOM 添加依赖项管理,这样您就不需要自己管理版本。
2 将依赖项添加到spring-cloud-starter-zipkin.

以下示例展示了如何为 Gradle 执行此作:spring-doc.cadn.net.cn

Gradle
dependencyManagement { (1)
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
    }
}

dependencies { (2)
    compile "org.springframework.cloud:spring-cloud-starter-zipkin"
}
1 我们建议您通过 Spring BOM 添加依赖项管理,这样您就不需要自己管理版本。
2 将依赖项添加到spring-cloud-starter-zipkin.

1.3.3. 使用 Zipkin over RabbitMQ 或 Kafka 侦探

如果您想使用 RabbitMQ 或 Kafka 而不是 HTTP,请添加spring-rabbitspring-kafkaDependency。 默认目标名称为zipkin.spring-doc.cadn.net.cn

如果使用 Kafka,则必须将属性spring.zipkin.sender.type属性相应地:spring-doc.cadn.net.cn

spring.zipkin.sender.type: kafka
spring-cloud-sleuth-stream已弃用,并且与这些目标不兼容。

如果你想要 Sleuth 而不是 RabbitMQ,请添加spring-cloud-starter-zipkinspring-rabbit依赖。spring-doc.cadn.net.cn

以下示例展示了如何为 Gradle 执行此作:spring-doc.cadn.net.cn

专家
<dependencyManagement> (1)
      <dependencies>
          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-dependencies</artifactId>
              <version>${release.train.version}</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>
      </dependencies>
</dependencyManagement>

<dependency> (2)
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency> (3)
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
</dependency>
1 我们建议您通过 Spring BOM 添加依赖项管理,这样您就不需要自己管理版本。
2 将依赖项添加到spring-cloud-starter-zipkin.这样,所有嵌套的依赖项都会被下载。
3 要自动配置 RabbitMQ,请添加spring-rabbitDependency。
Gradle
dependencyManagement { (1)
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${releaseTrainVersion}"
    }
}

dependencies {
    compile "org.springframework.cloud:spring-cloud-starter-zipkin" (2)
    compile "org.springframework.amqp:spring-rabbit" (3)
}
1 我们建议您通过 Spring BOM 添加依赖项管理,这样您就不需要自己管理版本。
2 将依赖项添加到spring-cloud-starter-zipkin.这样,所有嵌套的依赖项都会被下载。
3 要自动配置 RabbitMQ,请添加spring-rabbitDependency。

1.4. 覆盖 Zipkin 的自动配置

Spring Cloud Sleuth 从版本 2.1.0 开始支持向多个跟踪系统发送跟踪。 为了使其正常工作,每个跟踪系统都需要有一个Reporter<Span>Sender. 如果要覆盖提供的 bean,则需要为它们指定一个特定的名称。 为此,您可以分别使用ZipkinAutoConfiguration.REPORTER_BEAN_NAMEZipkinAutoConfiguration.SENDER_BEAN_NAME.spring-doc.cadn.net.cn

@Configuration
protected static class MyConfig {

    @Bean(ZipkinAutoConfiguration.REPORTER_BEAN_NAME)
    Reporter<zipkin2.Span> myReporter() {
        return AsyncReporter.create(mySender());
    }

    @Bean(ZipkinAutoConfiguration.SENDER_BEAN_NAME)
    MySender mySender() {
        return new MySender();
    }

    static class MySender extends Sender {

        private boolean spanSent = false;

        boolean isSpanSent() {
            return this.spanSent;
        }

        @Override
        public Encoding encoding() {
            return Encoding.JSON;
        }

        @Override
        public int messageMaxBytes() {
            return Integer.MAX_VALUE;
        }

        @Override
        public int messageSizeInBytes(List<byte[]> encodedSpans) {
            return encoding().listSizeInBytes(encodedSpans);
        }

        @Override
        public Call<Void> sendSpans(List<byte[]> encodedSpans) {
            this.spanSent = true;
            return Call.create(null);
        }

    }

}