hyperf中jaeger链路追踪需三步修复:一、启动前用swoole\runtime::enablecoroutine(swoole_hook_all | swoole_hook_curl)打钩子;二、显式调用$span->finish()而非依赖__destruct;三、jaeger agent必须部署于127.0.0.1:6831并禁用localhost解析。

Hyperf 默认的协程上下文透传机制在 Jaeger 中无法自动携带 trace_id 和 span_id,直接接入会丢失链路,导致所有请求变成独立单点——这不是配置错,是协程切换时 PHP 层的上下文未被 Jaeger SDK 感知。
Hyperf 启动前必须 patch Swoole 协程钩子
Jaeger PHP SDK(包括 OpenTracing 兼容版)默认只拦截常规函数调用,对 Swoole 协程 I/O 钩子(如 co::sleep、co::readFile、co::gethostbyname)无感知。不 patch,协程挂起/恢复时 span 就断了。
- 必须在
bin/hyperf.php的Di::set(...)之后、Application::run()之前插入: Swoole\Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL);- 特别注意:
SWOOLE_HOOK_CURL要显式加上,否则co::curl_exec不触发 span 续接 - 若使用
hyperf/tracer1.2+,还需调用TracerManager::init()手动初始化,避免因 DI 延迟导致 tracer 实例为空
协程内 Span 生命周期管理不能依赖 __destruct
PHP 对象析构函数在协程退出时不一定立即执行,尤其当协程被 co::sleep(0) 或等待 channel 时,__destruct 可能延迟到整个进程 shutdown 阶段——这时 Jaeger 已上报完毕,span 直接丢弃。
- 必须显式调用
$span->finish(),且要在协程函数 return 前完成 - 推荐封装成 defer 模式:
go(function () use ($span) { defer(fn () => $span->finish()); /* 业务逻辑 */ }); - 切忌在
onReceive或onMessage回调里 new Span 后不 finish,这类 span 会堆积在内存中,表现为 tracer 内存缓慢上涨 - Hyperf 的
@Span注解在协程方法上有效,但仅限同步方法;若方法内启动新协程(如go(...)),注解不会跨协程生效
Jaeger Agent 地址必须用 UDP + 本地回环
Hyperf 进程常驻,若 Jaeger Agent 部署在远程 Docker 容器或另一台机器,UDP 包易被丢弃或延迟,导致 span 上报失败、链路断裂。这不是网络问题,是 Jaeger client 的 UDP 缓冲区溢出行为在高并发协程下被放大。
- Agent 必须部署在同一宿主机,且监听
127.0.0.1:6831(Jaeger 默认 UDP 端口) - Hyperf 配置中
agent_host设为127.0.0.1,禁止用localhost(DNS 解析可能引入毫秒级延迟) - 检查
netstat -unlp | grep :6831确认 Agent 正在监听,而非仅绑定::1(IPv6) - Docker 场景下,Agent 容器需加
--network host或显式映射-p 127.0.0.1:6831:6831/udp,否则容器网络隔离会截断 UDP
真正难调试的从来不是 trace_id 传不下去,而是 span 在协程 yield 时静默中断——它不报错、不抛异常,只是链路上突然少了一段。务必用 Swoole\Coroutine::listCoroutines() 配合 TracerManager::getTracer()->getActiveSpan() 对比验证每个活跃协程是否都持有非空 span。











