Skip to content

Commit b74a962

Browse files
authored
fix(guzzle): distributed tracing header injection in HyperF/Swoole environments (#3544)
* fix(guzzle): inject distributed tracing headers before request execution Use install_hook with pre-hook to inject headers into PSR-7 request before it reaches any handler (curl or CoroutineHandler). This ensures headers are propagated in HyperF/Swoole environments where CoroutineHandler reads headers directly from the request object. Fixes distributed tracing continuity for Guzzle in coroutine contexts. * ALWAYS call overrideArguments() to prevent JIT compilation issues
1 parent 9db6c6e commit b74a962

File tree

1 file changed

+60
-30
lines changed

1 file changed

+60
-30
lines changed

src/DDTrace/Integrations/Guzzle/GuzzleIntegration.php

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace DDTrace\Integrations\Guzzle;
44

5+
use DDTrace\HookData;
56
use DDTrace\Http\Urls;
67
use DDTrace\Integrations\HttpClientIntegrationHelper;
78
use DDTrace\Integrations\Integration;
@@ -33,10 +34,65 @@ public static function handlePromiseResponse($response, SpanData $span)
3334

3435
public static function init(): int
3536
{
36-
/* Until we support both pre- and post- hooks on the same function, do
37-
* not send distributed tracing headers; curl will almost guaranteed do
38-
* it for us anyway. Just do a post-hook to get the response.
39-
*/
37+
\DDTrace\install_hook(
38+
'GuzzleHttp\Client::transfer',
39+
static function (HookData $hook) {
40+
// Note: We must ALWAYS call overrideArguments() to prevent JIT compilation issues.
41+
// See ext/hook/uhook.c: "hooks wishing to override args must do so unconditionally"
42+
43+
$modified = false;
44+
45+
if (isset($hook->args[0])) {
46+
$request = $hook->args[0];
47+
48+
if ($request instanceof \Psr\Http\Message\RequestInterface) {
49+
$dtHeaders = \DDTrace\generate_distributed_tracing_headers();
50+
51+
if (!empty($dtHeaders)) {
52+
foreach ($dtHeaders as $name => $value) {
53+
if (!$request->hasHeader($name)) {
54+
$request = $request->withHeader($name, $value);
55+
$modified = true;
56+
}
57+
}
58+
59+
if ($modified) {
60+
$hook->args[0] = $request;
61+
}
62+
}
63+
}
64+
}
65+
66+
// CRITICAL: Always call overrideArguments to prevent JIT from breaking header injection
67+
$hook->overrideArguments($hook->args);
68+
},
69+
static function (HookData $hook) {
70+
$span = $hook->span();
71+
if (!$span) {
72+
return;
73+
}
74+
75+
$span->resource = 'transfer';
76+
$span->name = 'GuzzleHttp\Client.transfer';
77+
Integration::handleInternalSpanServiceName($span, self::NAME);
78+
$span->type = Type::HTTP_CLIENT;
79+
$span->meta[Tag::SPAN_KIND] = Tag::SPAN_KIND_VALUE_CLIENT;
80+
$span->meta[Tag::COMPONENT] = self::NAME;
81+
$span->peerServiceSources = HttpClientIntegrationHelper::PEER_SERVICE_SOURCES;
82+
83+
if (isset($hook->args[0])) {
84+
self::addRequestInfo($span, $hook->args[0]);
85+
}
86+
87+
if (isset($hook->returned)) {
88+
$response = $hook->returned;
89+
if (\is_a($response, 'GuzzleHttp\Promise\PromiseInterface')) {
90+
self::handlePromiseResponse($response, $span);
91+
}
92+
}
93+
}
94+
);
95+
4096
\DDTrace\trace_method(
4197
'GuzzleHttp\Client',
4298
'send',
@@ -52,8 +108,6 @@ static function (SpanData $span, $args, $retval) {
52108
\defined('GuzzleHttp\ClientInterface::VERSION')
53109
&& substr(\GuzzleHttp\ClientInterface::VERSION, 0, 2) === '5.'
54110
) {
55-
// On Guzzle 6+, we do not need to generate peer.service for the send span,
56-
// as the terminal span is 'transfer'
57111
$span->peerServiceSources = HttpClientIntegrationHelper::PEER_SERVICE_SOURCES;
58112
}
59113

@@ -80,30 +134,6 @@ static function (SpanData $span, $args, $retval) {
80134
}
81135
);
82136

83-
\DDTrace\trace_method(
84-
'GuzzleHttp\Client',
85-
'transfer',
86-
static function (SpanData $span, $args, $retval) {
87-
$span->resource = 'transfer';
88-
$span->name = 'GuzzleHttp\Client.transfer';
89-
Integration::handleInternalSpanServiceName($span, self::NAME);
90-
$span->type = Type::HTTP_CLIENT;
91-
$span->meta[Tag::SPAN_KIND] = Tag::SPAN_KIND_VALUE_CLIENT;
92-
$span->meta[Tag::COMPONENT] = self::NAME;
93-
$span->peerServiceSources = HttpClientIntegrationHelper::PEER_SERVICE_SOURCES;
94-
95-
if (isset($args[0])) {
96-
self::addRequestInfo($span, $args[0]);
97-
}
98-
if (isset($retval)) {
99-
$response = $retval;
100-
if (\is_a($response, 'GuzzleHttp\Promise\PromiseInterface')) {
101-
self::handlePromiseResponse($response, $span);
102-
}
103-
}
104-
}
105-
);
106-
107137
return Integration::LOADED;
108138
}
109139

0 commit comments

Comments
 (0)