> For the complete documentation index, see [llms.txt](https://meetbill.gitbook.io/butterfly-project-doc/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://meetbill.gitbook.io/butterfly-project-doc/project-handlers/cloud_native/log_trace/tracing.md).

# Tracing

## OpenTracing 数据模型

* 1 分布式追踪的概念
* 2 整体概念
  * 2.1 链路
* 3 业界
* 4 例子
  * 4.1 opentracing
    * 4.1.1 Scope & ScopeManager
    * 4.1.2 使用

## 1 分布式追踪的概念

谷歌在 2010 年 4 月发表了一篇论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》(<http://1t.click/6EB)，介绍了分布式追踪的概念。>

对于分布式追踪，主要有以下的几个概念：

> * 追踪 Trace：就是由分布的微服务协作所支撑的一个事务。一个追踪，包含为该事务提供服务的各个服务请求。
> * 跨度 Span：Span 是事务中的一个工作流，一个 Span 包含了时间戳，日志和标签信息。Span 之间包含父子关系，或者主从(Followup)关系。
> * 跨度上下文 Span Context：跨度上下文是支撑分布式追踪的关键，它可以在调用的服务之间传递，上下文的内容包括诸如：从一个服务传递到另一个服务的时间，追踪的 ID，Span 的 ID 还有其它需要从上游服务传递到下游服务的信息。

## 2 整体概念

OpenTracing 中的调用链（Trace）通过归属于此调用链的 Span 来隐性地定义。一条调用链可以视为一个由多个 Span 组成的有向无环图（DAG 图）。Span 之间的关系被命名为 References。例如下面的示例调用链就是由 8 个 Span 组成的。

单个 Trace 中 Span 间的因果关系

```
        [Span A]  ←←←(The root span)
            |
     +------+------+
     |             |
[Span B]      [Span C] ←←←(Span C 是 Span A 的子节点，ChildOf)
     |             |
[Span D]      +---+-------+
               |           |
           [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                       ↑
                                       ↑
                                       ↑
                         （Span G 在 Span F 后被调用，FollowsFrom）

Span，可以被翻译为跨度，可以被理解为一次方法调用，一个程序块的调用，或者一次 RPC/数据库访问。
只要是一个具有完整时间周期的程序访问，都可以被认为是一个 span.
```

有些情况下，使用下面这种基于时间轴的时序图可以更好地展现调用链。

单个 Trace 中 Span 间的时间关系

```
––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

[Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··] [Span G··] [Span H··]
```

### 2.1 链路

Tracer 接口用于创建 Span（startSpan 函数）、解析上下文（Extract 函数）和透传上下文（Inject 函数）。它具有以下能力：

创建一个新 Span 或者设置 Span 属性

```
/** 创建和开始一个 span，返回一个 span，span 中包括操作名称和设置的选项。
** 例如：
**    创建一个无 parentSpan 的 Span：
**    sp := tracer.StartSpan("GetFeed")
**   创建一个有 parentSpan 的 Span
**    sp := tracer.StartSpan("GetFeed",opentracing.ChildOf(parentSpan.Context()))
**/
StartSpan(operationName string, opts ...StartSpanOption) Span
```

每个 Span 包含以下对象：

> * Operation name：操作名称 （也可以称作 Span name）。
> * Start timestamp：起始时间。
> * Finish timestamp：结束时间。
> * Span tag：一组键值对构成的 Span 标签集合。键值对中，键必须为 String，值可以是字符串、布尔或者数字类型。
> * Span log：一组 Span 的日志集合。每次 Log 操作包含一个键值对和一个时间戳。键值对中，键必须为 String，值可以是任意类型。
> * SpanContext: Span 上下文对象。每个 SpanContext 包含以下状态：

要实现任何一个 OpenTracing，都需要依赖一个独特的 Span 去跨进程边界传输当前调用链的状态（例如：Trace 和 Span 的 ID）。

Baggage Items 是 Trace 的随行数据，是一个键值对集合，存在于 Trace 中，也需要跨进程边界传输。

References（Span 间关系）：相关的零个或者多个 Span（Span 间通过 SpanContext 建立这种关系）。

## 3 业界

> * Dapper(Google) : 各 tracer 的基础
> * StackDriver Trace (Google)
> * Zipkin(twitter)
> * Appdash(golang)
> * 鹰眼(taobao)
> * 谛听(盘古，阿里云云产品使用的Trace系统)
> * 云图(蚂蚁Trace系统)
> * sTrace(神马)
> * X-ray(aws)

## 4 例子

### 4.1 opentracing

> * SDK: <https://github.com/opentracing/opentracing-python>
> * eg: <https://github.com/uber-common/opentracing-python-instrumentation>
> * <https://github.com/opentracing-contrib/python-flask>
> * <https://github.com/opentracing-contrib/python-django>

#### 4.1.1 Scope & ScopeManager

> * Scope 对象是 Active Span 的容器；
>   * 通过 Scope 能拿到当前上下文内的 Active Span; `_ThreadLocalScope` 是 Scope 的一个实现，通过 ThreadLocal 来存储
> * ScopeManager 用来管理 Scope, ScopeManager 抽象了激活当前 Span 实例方法（via activate(Span)） 和 访问当前活跃 Span 实例的方法 (via activeSpan() )；
>   * ThreadLocalScopeManager 是 ScopeManager 的一个实现

Q: 为什么要抽象出 Scope 的概念？直接使用 ThreadLocal 存储 Span 不就可以了吗？

```
首先理解 Scope 是什么？
Scope 是 Active Span 的一个容器，Scope 代表着当前活跃的 Span; 是对当前活跃 Span 的一个抽象，代表了当前上下文所处于的一个过程；

另外， ThreadLocalScope 还记录了 _to_restore Span, 这样结束时，可以恢复到上一个 Span 的状态；

我理解如果只是 get_current_span() 逻辑的话，直接把 span 塞到 ThreadLocal 里就可以在线程内传递了；
但是 ScopeManager 看代码是这样实现的，ScopeManager 包含一个 Scope, Scope 又包含了当前 Span 和 recover Scope；
好处是：这样就保证了，如果开启一个子 Span（子 span 会产生孙子 span），子 span 结束后，还可以回到 父span （这样可以继续产生以父 span 为基础的兄弟 span）
        如果只是 ThreadLocal 里塞一个当前 span的话，是解决不了这种情况的。
```

#### 4.1.2 使用

```
# Manual activation of the Span.
span = tracer.start_span(operation_name='someWork')
with tracer.scope_manager.activate(span, True) as scope:
   # Do things.

# Automatic activation of the Span.
# finish_on_close is a required parameter.
with tracer.start_active_span('someWork', finish_on_close=True) as scope:
   # Do things.

# Handling done through a try construct:
span = tracer.start_span(operation_name='someWork')
scope = tracer.scope_manager.activate(span, True)
try:
   # Do things.
except Exception as e:
   scope.set_tag('error', '...')
finally:
   scope.finish()
```

> Accessing the Active Span

```
# Access to the active span is straightforward.
scope = tracer.scope_manager.active()
if scope is not None:
    scope.span.set_tag('...', '...')
```
