hyperf 3.0 应使用 opentelemetry-php sdk + otlp 协议替代 jaeger-client-php,因其支持 w3c trace context、兼容协程、可切换多种后端;初始化须在主协程启动前完成,传播器需用 compositetextmappropagator,http 中间件须正确提取 traceparent 并注入上下文,db/redis 调用需显式继承 span 上下文,otlp 导出器须配置 tls 或 insecure 模式。

Hyperf 3.0 默认不内置 OpenTelemetry 支持,但可通过官方 opentelemetry/sdk 和 opentelemetry/exporter-otlp 替换旧版 Jaeger 客户端,实现标准 OTLP 协议上报——这是目前生产环境最稳、兼容性最好、且可随时切换后端(如 Jaeger、Zipkin、Datadog、腾讯云 APM)的路径。
为什么不能直接用 jaeger-client-php
Hyperf 2.x 社区曾有基于 jaeger-client-php 的插件,但它依赖已归档的 openzipkin/zipkin 生态,不支持 W3C Trace Context 标准,跨语言链路会断;Hyperf 3.0 的协程调度模型与该 SDK 的同步 I/O 不兼容,容易导致 Span 泄漏或上下文错乱。官方明确建议:新项目一律使用 opentelemetry-php SDK + OTLP 协议。
opentelemetry-php 初始化必须在协程启动前完成
Hyperf 启动流程中,Di 容器和 Server 配置在主协程初始化,而 OpenTelemetry 的全局 TracerProvider 必须在此阶段就位,否则子协程无法继承默认传播器。常见错误是把 TracerProvider::getInstance() 放在中间件或控制器里调用,结果每次请求都新建 Provider,Span 无法关联。
- 在
config/autoload/tracing.php中定义配置项,如otel.exporter.otlp.endpoint(例如http://otel-collector:4317) - 在
bin/hyperf.php入口文件顶部(Hyperf\Contract\ApplicationInterface::class实例化之前)调用初始化函数 - 确保
propagation使用TraceContext+Baggage复合传播器:new CompositeTextMapPropagator([new TraceContext(), new Baggage()]) - Resource 属性必须包含
service.name、service.version、telemetry.sdk.language,否则 Jaeger UI 无法分组展示
HTTP 中间件中正确提取并延续 traceparent
Hyperf 的 HttpServer 请求进入时,traceparent 头可能存在于 $request->getHeader('traceparent'),但不能直接用原生值构造 Context —— 必须交由 OpenTelemetry Propagator 解析。手动解析或拼接会导致 trace_id/mask 不匹配,Jaeger 显示“broken link”。
- 在自定义中间件
TracingMiddleware的process()方法开头,调用$propagator->extract($carrier),其中$carrier是一个实现了TextMapCarrierInterface的数组封装器(可用ArrayCarrier) - 将返回的
Context注入到当前协程上下文:Coroutine::getContext()->with($context)(Hyperf 3.0+ 推荐方式) - 创建 Span 时务必传入该 Context:
$tracer->startSpan('http.request', [], $context),否则新 Span 会生成独立 trace_id - 不要在
onResponse钩子中手动结束 Span —— 应在中间件process()尾部统一$span->end(),避免因异常跳过
数据库/Redis 调用需显式注入 Span 上下文
Hyperf 的 Db 和 Redis 组件默认不感知 OpenTelemetry 上下文,即使你启用了自动插件(如 opentelemetry/instrumentation-pdo),也仅对原生 PDO 生效,不覆盖 Hyperf 封装层。若不做处理,SQL 查询会脱离父 Span,变成孤立节点。
- 在执行查询前,从当前协程 Context 获取活跃 Span:
$span = Span::fromContext(Context::getCurrent()) - 为 PDOStatement 或 Redis 连接设置自定义属性,如
$span->setAttributes(['db.statement' => $sql]) - 若使用
opentelemetry/instrumentation-pdo,需确认其版本 ≥ v1.12.0,并在composer.json中强制替换hyperf/database的底层驱动为pdo(非hyperf/pool封装) - Redis 操作建议用
opentelemetry/instrumentation-redis,它能自动捕获get/set等命令,但要求客户端是predis/predis或phpredis原生扩展,不兼容 Hyperf 自研的Hyperf\Redis\Redis类
最关键的细节:OTLP 导出器必须启用 TLS 或配置 insecure 标志(开发环境),否则 Collector 拒收连接;而 Jaeger Agent 的 UDP 端口(6831/6832)在 Hyperf 3.0 协程下极易丢包,这也是官方弃用它的根本原因。别省那几行代码——直接走 OTLP,链路才真正可靠。











