22
33namespace DDTrace \Integrations \Guzzle ;
44
5+ use DDTrace \HookData ;
56use DDTrace \Http \Urls ;
67use DDTrace \Integrations \HttpClientIntegrationHelper ;
78use 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