Hyperf 3.0 必须弃用 jaeger-client-php,改用 opentelemetry-php + OTLP 协议;因其依赖归档生态、不支持 W3C Trace Context,且同步 I/O 与协程冲突导致 Span 泄漏、上下文错乱、链路断裂。

Hyperf 3.0 中直接集成 jaeger-client-php 已不可靠,必须改用 opentelemetry-php + OTLP 协议对接 Jaeger(或任何兼容 OTLP 的后端),否则 trace_id 会断、协程上下文错乱、跨语言链路无法串联。
为什么 jaeger-client-php 在 Hyperf 3.0 里跑不通
它依赖已归档的 openzipkin/zipkin 生态,不支持 W3C Trace Context 标准;更关键的是其内部使用同步 I/O,在 Hyperf 的协程调度下极易造成 Span 泄漏——比如一个请求里多个 Redis 调用,Span 可能被错误地挂到不同协程上,最终在 Jaeger UI 里看到大量孤立、无父子关系的 Span。
常见现象包括:
-
traceparent头存在但 Jaeger 显示 “broken link” - 同一请求的 DB 和 HTTP Span 不在同一 Trace 下
- 重启服务后部分 Span 丢失,或出现重复 trace_id
官方明确建议:新项目一律弃用 jaeger-client-php,改用 opentelemetry/sdk 和 opentelemetry/exporter-otlp。
OTLP 导出器必须配置 insecure 或 TLS,否则上报失败
Jaeger Collector 默认监听 http://localhost:4317(gRPC)或 http://localhost:4318(HTTP/JSON),但 opentelemetry/exporter-otlp 默认启用 TLS 验证。若本地用 Docker 启动 all-in-one,没配证书就会卡住,日志里看不到错误,Span 就静默丢弃。
正确做法是显式禁用 TLS 验证(开发/测试环境):
use OpenTelemetry\Export\Proto\OtlpHttpExporter;<br>new OtlpHttpExporter([<br> 'endpoint' => 'http://otel-collector:4318/v1/traces',<br> 'insecure' => true,<br>]);
生产环境则需配置有效证书路径和 CA:
-
insecure => false(默认值) certificate => '/path/to/ca.pem'- 确保 Collector 开启 TLS 并暴露对应端口
HTTP 中间件里必须用 Propagator 解析 traceparent,不能手动拼接
Hyperf 的 $request->getHeader('traceparent') 返回的是原始字符串,比如 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01。如果直接 new SpanContext() 或硬塞进 Context,W3C 校验会失败,导致后续 Span 的 trace_id/mask 不匹配。
正确流程是:
- 在中间件开头获取 header 值
- 传给
$propagator->extract()(必须是CompositeTextMapPropagator,含TraceContext+Baggage) - 用返回的
Context作为后续 Span 的 parent
示例关键片段:
$headers = $request->getHeaders();<br>$context = $this->propagator->extract($headers);<br>$span = $this->tracer->spanBuilder($name)->setParent($context)->startSpan();
Resource 属性缺失会导致 Jaeger UI 分组失效
Jaeger UI 按 service.name 和 service.version 对服务分组展示。如果初始化 TracerProvider 时没设置 Resource,所有 Span 都会归到 “unknown_service:php”,无法区分 gateway、user、order 等服务。
必须显式传入:
-
service.name:取自env('APP_NAME', 'hyperf-service') -
service.version:建议用 git commit hash 或composer show my/app | grep version -
telemetry.sdk.language:固定为php
这个 Resource 必须在主协程启动前注册,一旦 Provider 初始化完成就不可更改——所以别在中间件里调 TracerProvider::getInstance(),那会新建一个没 Resource 的实例。
最易被忽略的一点:DB/Redis 调用不会自动继承 HTTP 请求的 Span 上下文,必须在执行前显式绑定当前 Context,否则它们会生成独立 Trace。Hyperf 的 hyperf/db 和 hyperf/redis 组件虽有切面支持,但前提是 Tracing 初始化正确且全局 Provider 已就位。


















