From 1cb514c578dad5073aac876f74b553f1a4c42843 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Mon, 22 Jun 2026 13:26:15 +0200 Subject: [PATCH 1/7] Add otel endpoint in weblog --- ..._drop_in_extract_and_make_distant_call.php | 72 +++++++++++++++++++ .../docker/php/common/rewrite-rules.conf | 1 + 2 files changed, 73 insertions(+) create mode 100644 utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php diff --git a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php new file mode 100644 index 00000000000..ea6086a9457 --- /dev/null +++ b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php @@ -0,0 +1,72 @@ + 'url parameter required']); + exit; +} + +// Build carrier from $_SERVER HTTP_* keys +$carrier = []; +foreach ($_SERVER as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $carrier[strtolower(str_replace('_', '-', substr($key, 5)))] = $value; + } +} + +// Extract upstream context via OTel propagator. DDTrace bridges this call into +// consume_distributed_tracing_headers, so DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT +// applies (restart: fresh trace + span link; ignore: drop all context). +$context = OpenTelemetry\API\Globals::propagator()->extract($carrier); + +$tracer = OpenTelemetry\API\Globals::tracerProvider()->getTracer('dd-trace'); +$span = $tracer + ->spanBuilder('otel_extract_distant_call') + ->setSpanKind(OpenTelemetry\API\Trace\SpanKind::KIND_SERVER) + ->setParent($context) + ->startSpan(); +$scope = $span->activate(); + +try { + $ch = curl_init($url); + $response_headers = []; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLINFO_HEADER_OUT, true); + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$response_headers) { + $len = strlen($header); + $parts = explode(':', $header, 2); + if (count($parts) === 2) { + $response_headers[strtolower(trim($parts[0]))] = trim($parts[1]); + } + return $len; + }); + curl_exec($ch); + $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $request_headers = []; + $raw = curl_getinfo($ch, CURLINFO_HEADER_OUT); + if ($raw) { + foreach (explode("\r\n", $raw) as $line) { + if (strpos($line, ':') !== false) { + [$k, $v] = explode(':', $line, 2); + $request_headers[trim($k)] = trim($v); + } + } + } + curl_close($ch); + + header('Content-Type: application/json'); + echo json_encode([ + 'url' => $url, + 'status_code' => $status_code, + 'request_headers' => $request_headers, + 'response_headers' => $response_headers, + ]); +} finally { + $span->end(); + $scope->detach(); +} diff --git a/utils/build/docker/php/common/rewrite-rules.conf b/utils/build/docker/php/common/rewrite-rules.conf index 7f46acdd997..58fc6921735 100644 --- a/utils/build/docker/php/common/rewrite-rules.conf +++ b/utils/build/docker/php/common/rewrite-rules.conf @@ -27,6 +27,7 @@ RewriteRule "^/signup$" "/signup/" RewriteRule "^/shell_execution$" "/shell_execution/" RewriteRule "^/otel_drop_in_baggage_api_otel$" "/otel_drop_in_baggage_api_otel/" RewriteRule "^/otel_drop_in_baggage_api_datadog$" "/otel_drop_in_baggage_api_datadog/" +RewriteRule "^/otel_drop_in_extract_and_make_distant_call$" "/otel_drop_in_extract_and_make_distant_call/" [QSA] RewriteRule "^/otel_drop_in$" "/otel_drop_in/" RewriteRule "^/db$" "/db/" RewriteRule "^/trace/sql$" "/trace_sql/" From 8cba01890948263aa893e3430492cae5d96000c1 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Mon, 22 Jun 2026 14:14:19 +0200 Subject: [PATCH 2/7] Enable Test_ExtractBehavior_Restart_Otel from 1.22 --- manifests/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/php.yml b/manifests/php.yml index db6eb53666d..4a9c919d9b5 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -1044,7 +1044,7 @@ manifest: tests/test_library_conf.py::Test_ExtractBehavior_Default::test_single_tracecontext: v1.17.0 tests/test_library_conf.py::Test_ExtractBehavior_Ignore: incomplete_test_app (/make_distant_call endpoint is not correctly implemented) tests/test_library_conf.py::Test_ExtractBehavior_Restart: incomplete_test_app (/make_distant_call endpoint is not correctly implemented) - tests/test_library_conf.py::Test_ExtractBehavior_Restart_Otel: missing_feature (OTel extraction endpoint not implemented) + tests/test_library_conf.py::Test_ExtractBehavior_Restart_Otel: v1.22.0 tests/test_library_conf.py::Test_ExtractBehavior_Restart_With_Extract_First: incomplete_test_app (/make_distant_call endpoint is not correctly implemented) tests/test_library_conf.py::Test_HeaderTags: v0.68.2 tests/test_library_conf.py::Test_HeaderTags_Colon_Leading: v0.74.0 From b8a4af3722cbd860b2e6c28061bc5f2cdc3181e8 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Mon, 22 Jun 2026 15:18:29 +0200 Subject: [PATCH 3/7] lowercase headers before returning them in php weblog utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php --- .../php/common/otel_drop_in_extract_and_make_distant_call.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php index ea6086a9457..59a96cf0883 100644 --- a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php +++ b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php @@ -53,7 +53,7 @@ foreach (explode("\r\n", $raw) as $line) { if (strpos($line, ':') !== false) { [$k, $v] = explode(':', $line, 2); - $request_headers[trim($k)] = trim($v); + $request_headers[strtolower(trim($k))] = trim($v); } } } From 016a21ed58b70386e9ef60563ac7012831edcf1f Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Tue, 23 Jun 2026 14:34:30 +0200 Subject: [PATCH 4/7] Fix: Only enable versions who have the drop-in endpoint --- manifests/php.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/manifests/php.yml b/manifests/php.yml index 4a9c919d9b5..220ba53ab12 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -1044,7 +1044,16 @@ manifest: tests/test_library_conf.py::Test_ExtractBehavior_Default::test_single_tracecontext: v1.17.0 tests/test_library_conf.py::Test_ExtractBehavior_Ignore: incomplete_test_app (/make_distant_call endpoint is not correctly implemented) tests/test_library_conf.py::Test_ExtractBehavior_Restart: incomplete_test_app (/make_distant_call endpoint is not correctly implemented) - tests/test_library_conf.py::Test_ExtractBehavior_Restart_Otel: v1.22.0 + tests/test_library_conf.py::Test_ExtractBehavior_Restart_Otel: + - weblog_declaration: + "*": missing_feature (OTel SDK requires PHP >= 8.1) + apache-mod-8.1: v1.22.0 + apache-mod-8.1-zts: v1.22.0 + apache-mod-8.2: v1.22.0 + apache-mod-8.2-zts: v1.22.0 + php-fpm-8.1: v1.22.0 + php-fpm-8.2: v1.22.0 + php-fpm-8.5: v1.22.0 tests/test_library_conf.py::Test_ExtractBehavior_Restart_With_Extract_First: incomplete_test_app (/make_distant_call endpoint is not correctly implemented) tests/test_library_conf.py::Test_HeaderTags: v0.68.2 tests/test_library_conf.py::Test_HeaderTags_Colon_Leading: v0.74.0 From 81cc81dd0aca9c9ccba6c7f641729f7d27799b32 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Tue, 23 Jun 2026 16:15:41 +0200 Subject: [PATCH 5/7] no manual span start->end (handled by autoinstrumentation) --- ...l_drop_in_extract_and_make_distant_call.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php index 59a96cf0883..07175055990 100644 --- a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php +++ b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php @@ -18,18 +18,13 @@ } } -// Extract upstream context via OTel propagator. DDTrace bridges this call into -// consume_distributed_tracing_headers, so DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT -// applies (restart: fresh trace + span link; ignore: drop all context). +// In PHP, DDTrace auto-instrumentation processes incoming headers via request_init_hook +// before userland code runs, so the auto-generated web.request span is already the +// restarted root (with span links) when DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart. +// We activate the OTel-extracted context to ensure incoming baggage (e.g. key1=value1) +// is present in the active OTel context so DDTrace propagates it in the curl call. $context = OpenTelemetry\API\Globals::propagator()->extract($carrier); - -$tracer = OpenTelemetry\API\Globals::tracerProvider()->getTracer('dd-trace'); -$span = $tracer - ->spanBuilder('otel_extract_distant_call') - ->setSpanKind(OpenTelemetry\API\Trace\SpanKind::KIND_SERVER) - ->setParent($context) - ->startSpan(); -$scope = $span->activate(); +$scope = $context->activate(); try { $ch = curl_init($url); @@ -67,6 +62,5 @@ 'response_headers' => $response_headers, ]); } finally { - $span->end(); $scope->detach(); } From dc096603613a5d2c5f05180af43aabb5276dd621 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Tue, 23 Jun 2026 16:59:33 +0200 Subject: [PATCH 6/7] Update otel_drop_in_extract_and_make_distant_call.php --- ..._drop_in_extract_and_make_distant_call.php | 92 ++++++++----------- 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php index 07175055990..83e1206dd04 100644 --- a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php +++ b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php @@ -1,66 +1,48 @@ 'url parameter required']); - exit; + header("Content-Type: application/json"); + echo json_encode(["error" => "url parameter required"]); + exit(); } -// Build carrier from $_SERVER HTTP_* keys -$carrier = []; -foreach ($_SERVER as $key => $value) { - if (str_starts_with($key, 'HTTP_')) { - $carrier[strtolower(str_replace('_', '-', substr($key, 5)))] = $value; +$ch = curl_init($url); +$response_headers = []; +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); +curl_setopt($ch, CURLOPT_HEADER, false); +curl_setopt($ch, CURLINFO_HEADER_OUT, true); +curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use ( + &$response_headers, +) { + $len = strlen($header); + $parts = explode(":", $header, 2); + if (count($parts) === 2) { + $response_headers[strtolower(trim($parts[0]))] = trim($parts[1]); } -} - -// In PHP, DDTrace auto-instrumentation processes incoming headers via request_init_hook -// before userland code runs, so the auto-generated web.request span is already the -// restarted root (with span links) when DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT=restart. -// We activate the OTel-extracted context to ensure incoming baggage (e.g. key1=value1) -// is present in the active OTel context so DDTrace propagates it in the curl call. -$context = OpenTelemetry\API\Globals::propagator()->extract($carrier); -$scope = $context->activate(); - -try { - $ch = curl_init($url); - $response_headers = []; - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HEADER, false); - curl_setopt($ch, CURLINFO_HEADER_OUT, true); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$response_headers) { - $len = strlen($header); - $parts = explode(':', $header, 2); - if (count($parts) === 2) { - $response_headers[strtolower(trim($parts[0]))] = trim($parts[1]); - } - return $len; - }); - curl_exec($ch); - $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $request_headers = []; - $raw = curl_getinfo($ch, CURLINFO_HEADER_OUT); - if ($raw) { - foreach (explode("\r\n", $raw) as $line) { - if (strpos($line, ':') !== false) { - [$k, $v] = explode(':', $line, 2); - $request_headers[strtolower(trim($k))] = trim($v); - } + return $len; +}); +curl_exec($ch); +$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$request_headers = []; +$raw = curl_getinfo($ch, CURLINFO_HEADER_OUT); +if ($raw) { + foreach (explode("\r\n", $raw) as $line) { + if (strpos($line, ":") !== false) { + [$k, $v] = explode(":", $line, 2); + $request_headers[strtolower(trim($k))] = trim($v); } } - curl_close($ch); - - header('Content-Type: application/json'); - echo json_encode([ - 'url' => $url, - 'status_code' => $status_code, - 'request_headers' => $request_headers, - 'response_headers' => $response_headers, - ]); -} finally { - $scope->detach(); } +curl_close($ch); + +header("Content-Type: application/json"); +echo json_encode([ + "url" => $url, + "status_code" => $status_code, + "request_headers" => $request_headers, + "response_headers" => $response_headers, +]); From 5b1112d37106c5097c8310dd0953044be1131854 Mon Sep 17 00:00:00 2001 From: Milan Garnier Date: Thu, 2 Jul 2026 11:55:42 +0200 Subject: [PATCH 7/7] Update otel_drop_in_extract_and_make_distant_call.php --- ..._drop_in_extract_and_make_distant_call.php | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php index 83e1206dd04..25632f63b66 100644 --- a/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php +++ b/utils/build/docker/php/common/otel_drop_in_extract_and_make_distant_call.php @@ -10,6 +10,24 @@ exit(); } +// Collect incoming headers (lowercase keys) for OTel propagator extraction. +// In restart extract mode, the DD drop-in creates a new root context with +// span links to the incoming trace, rather than propagating it directly. +$headers = []; +foreach (getallheaders() as $k => $v) { + $headers[strtolower($k)] = $v; +} + +$context = \OpenTelemetry\API\Globals::propagator()->extract($headers); + +$tracer = \OpenTelemetry\API\Globals::tracerProvider()->getTracer('dd-trace-php/system-tests'); +$span = $tracer->spanBuilder('otel_extract_distant_call') + ->setParent($context) + ->setSpanKind(\OpenTelemetry\API\Trace\SpanKind::KIND_SERVER) + ->startSpan(); +$scope = $span->activate(); + +// Make the distant call; auto-instrumentation propagates the active span context. $ch = curl_init($url); $response_headers = []; curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); @@ -25,19 +43,25 @@ } return $len; }); -curl_exec($ch); -$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$status_code = 0; $request_headers = []; -$raw = curl_getinfo($ch, CURLINFO_HEADER_OUT); -if ($raw) { - foreach (explode("\r\n", $raw) as $line) { - if (strpos($line, ":") !== false) { - [$k, $v] = explode(":", $line, 2); - $request_headers[strtolower(trim($k))] = trim($v); +try { + curl_exec($ch); + $status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $raw = curl_getinfo($ch, CURLINFO_HEADER_OUT); + if ($raw) { + foreach (explode("\r\n", $raw) as $line) { + if (strpos($line, ":") !== false) { + [$k, $v] = explode(":", $line, 2); + $request_headers[strtolower(trim($k))] = trim($v); + } } } + curl_close($ch); + $span->end(); +} finally { + $scope->detach(); } -curl_close($ch); header("Content-Type: application/json"); echo json_encode([