1. 概述
1.1 插件名称
- 分布式追踪插件
1.2 插件作用
Tracing 插件基于 OpenTelemetry 把一次请求的调用链贯穿客户端、网关与上游服务:
- 入站上下文:读取客户端携带的 W3C
traceparent、tracestate以及 W3Cbaggage;没有合法上下文的请求会开启一条新的 trace。 - 每个请求一个 server span:网关为请求创建 OpenTelemetry span,名称由请求方法与命中的路由组成,并记录路由信息与请求结果。
- 出站上下文:把网关 span 的上下文注入到发往上游的 HTTP 请求,使上游延续同一条 trace,其 span 成为网关 span 的子 span。
- 导出:配置了端点后,span 通过 OTLP over HTTP 导出到任意兼容的后端(Jaeger、Tempo、OpenTelemetry Collector 等)。
- 日志关联:请求的 trace id 通过 exchange 属性
shenyu-agent-trace-id暴露,logging 插件会把它写进访问日志记录。
插件的 order 为 0,在插件链里先于其它业务插件执行(只有 global handler 排在它之前),因此它的 span 覆盖该请求的其余全部插件。
1.3 默认关闭
只有 shenyu.tracing.enabled=true 时才会生效:starter 只在此时创建 OpenTelemetry SDK 与 tracing 插件。开关保持默认值时,网关行为与未引入插件时完全一致。
2. 接入方式
2.1 引入依赖
在网关中引入 tracing starter。走 HTTP 的路 由继续使用网关已有的 http client 插件,由它完成 trace 上下文的注入。
<!-- apache shenyu tracing plugin start-->
<dependency>
<groupId>org.apache.shenyu</groupId>
<artifactId>shenyu-spring-boot-starter-plugin-tracing</artifactId>
<version>${project.version}</version>
</dependency>
<!-- apache shenyu tracing plugin end-->
starter 已内置 OpenTelemetry SDK 与 OTLP over HTTP exporter,不需要额外依赖。
2.2 开启插件
shenyu:
tracing:
enabled: true
otlp:
endpoint: http://otel-collector:4318
启动时网关会打印真正生效的配置。span 没有到达后端时,这是第一个要看的日志:
shenyu tracing: service name shenyu-gateway, sampler ParentBased{...}, span creation enabled, spans exported http://otel-collector:4318/v1/traces
3. 配置项
所有配置都在 shenyu.tracing 下。
| 配置项 | 默认值 | 说明 |
|---|---|---|
enabled | false | 是否开启插件。为 false 时不创建 span,也不做任何注入。 |
serviceName | shenyu-gateway | span 的 service.name 资源属性,后端里网关的服务名。 |
createSpan | true | 网关是否创建自己的 span。已经由 Java agent 等方案埋点的部署设为 false,见第 10 节。 |
sampler | parent_based | parent_based、always_on、always_off 或 ratio,见第 4 节。 |
sampleRatio | 0.1 | ratio 采样器的比例,取值 0 到 1。只被 ratio 使用,且只作用于没有父上下文的请求。 |
resourceAttributes | 无 | 追加到每个 span 的资源属性,例 如 deployment.environment。 |
otlp.endpoint | 无 | collector 的 traces 端点。留空时 span 完全不导出。 |
otlp.timeout | 10s | 单次导出请求的超时时间,使用 duration 写法,例如 10s、500ms。 |
otlp.headers | 无 | 导出请求的 header,例如 collector 要求的 authorization。 |
一份完整的配置:
shenyu:
tracing:
enabled: true
serviceName: shenyu-gateway
createSpan: true
sampler: parent_based
sampleRatio: 0.1
resourceAttributes:
deployment.environment: prod
service.version: 2.7.2
otlp:
endpoint: http://otel-collector:4318
timeout: 10s
headers:
authorization: Bearer <token>
3.1 端点写法
otlp.endpoint 是 collector 的 traces 端点:
- 不带 path 的端点,例如
http://otel-collector:4318,会自动补上/v1/traces,避免把 collector 的根路径当作 traces 端点而静默丢数据。 - 已经带 path 的端点,例如
http://otel-collector:4318/v1/traces,按原样使用;端点上的 query 与 fragment 也会保留。
4. 采样
采样器只决定请求的 span 是否被记录与导出,不会改变请求是否被转发。
| 采样器 | 对应的 OpenTelemetry 采样器 | 行为 |
|---|---|---|
parent_based | parentBased(alwaysOn) | 沿用客户端的采样决策;客户端没有决策时采样。这是默认值。 |
always_on | alwaysOn() | 所有请求都采样,包括客户端已经决定不采样的请求。 |
always_off | alwaysOff() | 所有请求都不采样,包括客户端已经决定采样的请求。 |
ratio | parentBased(traceIdRatioBased(sampleRatio)) | 与 parent_based 一样沿用客户端决策;对没有父上下文的请求按 sampleRatio 采样。带合法 traceparent 的请求沿用其客户端的决策,因此不会被比例重新采样。 |
说明:
sampleRatio会被收敛到0到1,越界时会打印日志;无法解析的值等同于不采样。- 采样标志会传给上游:未采样的请求依然会被传播,只是网关 span 的 sampled 标志为 0,上游不会因此开始收集客户端并不需要的 trace。
- 生产环境建议使用
parent_based,这样每一跳都尊重调用方的决策;排障时可以临时用always_on;always_off可以在不摘依赖、不改路由的情况下关闭追踪;ratio只对完全没有 trace 上下文的入口流量(例如移动端直连、压测流量)有意义。 always_on与always_off都不是按路由生效的开关,它们作用于网关的所有请求。
5. 上下文传播
5.1 入站
网关读取客户端的 W3C trace 上下文与 W3C baggage:
traceparent、tracestate用于延续客户端的 trace,baggage随之一并携带。- 请求没有合法上下文,或者上下文格式非法时,会开启新的 trace。非法的 header 不会报错,请求照常转发。
- 请求中出现重复的
traceparent时,按“没有上下文”处理:重复的 trace 上下文 header 是已知的注入手段。请求照常转发,但会开启新 trace。 - 只支持 W3C 格式,不读取 B3(见第 11 节)。
5.2 出站
网关 span 的上下文由 http client 插件注入到发往上游的 HTTP 请求,WebClient 与 Netty 两条路径都覆盖:
- 写入网关 span 的上下文之前,会先删掉出站请求中来自客户端的
traceparent与tracestate,因此离开网关的 trace 上下文有且只有一份,且属于网关 span。经由网关调用的上游,其 parent 永远是网关 span,而不是客户端的 span。 baggage不属于 trace 本身,按客户端发送的原样透传。- 未开启追踪,或
createSpan=false且没有任何埋点设置活跃上下文时,不做任何注入:客户端的 header 原样转发。 baggage会被上游接收并可能被其记录,不要在其中放置密钥等敏感信息。
注入是一个扩展点:TraceContextPropagator 是 ShenYu 的 SPI,tracing 插件注册了 http 协议的实现。某个协议插件如果要传播自己的协议,可以增加该协议的 propagator 实现并把自己的 carrier 交给它。目前只提供了 http 的实现,因此不通过 HTTP 请求上游的协议还没有覆盖(见第 11 节)。
6. 网关 span
6.1 名称
span 名称为 {method} {route},例如 GET /http/order/findById。其中 route 取上游注册的 api 路径,因此名称是有界值,不会随流量增长:
| 场景 | span 名称 |
|---|---|
| 命中路由 | GET /http/order/findById |
| 未命中路由(例如 404) | GET |
| 既没有方法也没有路由 | shenyu-gateway |
6.2 属性
| 属性 | 记录范围 |
|---|---|
http.request.method | 所有请求 |
http.route | 命中已注册 api 的请求 |
shenyu.rpc.type | 所有请求,例如 http |
shenyu.route.module | 所有请求,命中路由所属模块(通常是应用名) |
shenyu.route.method | 非 http 类协议的请求,命中的 api 方法 |
shenyu.context.path | 经过 context path 插件的请求 |
shenyu.upstream.host | 已路由到上游的请求 |
shenyu.selector.id | HTTP 路由的 divide 插件;其它协议不暴露该值 |
http.response.status_code | span 结束时已知状态码的请求 |
shenyu.error.type | 仅 error span:timeout 或异常类型 |
shenyu.cancelled | 被客户端取消的请求 |
只记录有界值:原始请求路径与 query string 都不会作为属性,路由取自命中 api 的元数据。这样属性与 span 名称的基数不会随流量增长,也避免把 query string 里常见的个人数据写进后端。
6.3 状态与错误
状态遵循 OpenTelemetry 的 http 约定:
| 结果 | span 状态 |
|---|---|
响应 2xx / 3xx | UNSET |
响应 4xx(客户端错误) | UNSET,客户端错误不算网关的失败 |
响应 5xx(服务端错误) | ERROR,描述为 HTTP 500 |
超时,异常或 408 / 504 | ERROR,并记录 shenyu.error.type=timeout |
| 其它异常 | ERROR,描述为异常类型 |
| 客户端取消请求 | 状态不变,记录 shenyu.cancelled=true |
异常会以 OpenTelemetry 的 exception event 记录,其中带有异常类型、message 与堆栈。span 的状态描述本身只写异常类型,不会把异常 message 变成状态描述。
被客户端错误“答复”的失败,例如限流拒绝请求并返回 429,其 span 状态与直接响应同样状态时一致,保持 UNSET。这样按 ERROR span 统计的看板统计的是网关与上游的失败,而不是被拒绝的客户端请求。
7. 日志与 trace 关联
请求的 trace id 会被写入 exchange 属性 shenyu-agent-trace-id:
- logging 插件读取该属性并写进访问日志记录的
traceId字段,因此可以用后端的 trace id 找到对应的访问日志,反之亦然。 - 网关的请求级日志也会在请求结束后输出该 trace id:
shenyu chain handle uri:/http/order/findById, traceId:4bf92f3577b34da6a3ce929d0e0e4736, cost:12
该日志为 debug 级别。需要在普通部署的日志里看到 trace id 时开启:
logging:
level:
org.apache.shenyu.web.handler: debug
8. 导出到后端
exporter 使用 OTLP over HTTP,当前主流后端都支持,不需要为每家后端更换专用 exporter。下面的例子只有端点不同。
8.1 OpenTelemetry Collector
# otel-collector.yml
receivers:
otlp:
protocols:
http:
endpoint: 0.0.0.0:4318
exporters:
otlphttp:
endpoint: http://tempo:4318
service:
pipelines:
traces:
receivers: [ otlp ]
exporters: [ otlphttp ]
shenyu:
tracing:
enabled: true
otlp:
endpoint: http://otel-collector:4318
8.2 Jaeger
较新的 Jaeger 直接接受 OTLP,网关可以直接导出,不需要中间的 collector;对于自身不接受 OTLP 的版本,在它前面放一个 OpenTelemetry Collector,并让网关指向该 collector。
shenyu:
tracing:
enabled: true
otlp:
endpoint: http://jaeger:4318