From 842e535147f2ba9ded31e13910cebf615218924d Mon Sep 17 00:00:00 2001 From: Matthew Li Date: Fri, 25 Sep 2026 15:06:07 -0400 Subject: [PATCH] Add native OTLP export marker to trace chunks Tag the first span of each chunk with _dd.sdk.otlp_export=false, next to _dd.tags.process, so the backend can tell native-exported spans from spans that reached it through the datadogreceiver instead of inferring it at ingest time. PHP has no OTLP trace export, so the value is always false. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../client_init_record_span_tags.phpt | 1 + appsec/tests/extension/ddtrace_basic.phpt | 1 + .../extension/rinit_record_span_tags.phpt | 1 + .../rinit_record_span_tags_fail.phpt | 1 + .../extension/rinit_root_span_add_tag.phpt | 1 + appsec/tests/extension/root_span_add_tag.phpt | 4 ++- ..._span_add_tag_with_intermediate_spans.phpt | 1 + tests/Common/SnapshotTestTrait.php | 3 ++ tests/Common/SpanChecker.php | 5 +++ ...stributed_trace_overwrite_active_span.phpt | 2 +- tests/ext/extract_server_values.phpt | 4 ++- tests/ext/referrer_extraction_01.phpt | 4 ++- tests/ext/referrer_extraction_02.phpt | 4 ++- tests/ext/referrer_extraction_03.phpt | 4 ++- tests/ext/referrer_extraction_04.phpt | 4 ++- tests/ext/referrer_extraction_05.phpt | 4 ++- .../ext/root_span_url_as_resource_names.phpt | 4 ++- ...ot_span_url_as_resource_names_no_host.phpt | 4 ++- .../ext/root_span_url_with_post_no_param.phpt | 4 ++- .../root_span_url_with_post_no_param_set.phpt | 4 ++- tests/ext/sandbox/dd_dumper.inc | 2 ++ .../ext/sandbox/safe_to_string_metadata.phpt | 2 +- tests/ext/sdk_otlp_export_marker.phpt | 32 +++++++++++++++++++ tracer/serializer.c | 2 ++ 24 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 tests/ext/sdk_otlp_export_marker.phpt diff --git a/appsec/tests/extension/client_init_record_span_tags.phpt b/appsec/tests/extension/client_init_record_span_tags.phpt index 2e1e78569e3..cac10102fe8 100644 --- a/appsec/tests/extension/client_init_record_span_tags.phpt +++ b/appsec/tests/extension/client_init_record_span_tags.phpt @@ -92,6 +92,7 @@ Array [_dd.p.dm] => -0 [_dd.p.tid] => %s [_dd.runtime_family] => php + [_dd.sdk.otlp_export] => false [appsec.event] => true [http.endpoint] => /foo [http.method] => GET diff --git a/appsec/tests/extension/ddtrace_basic.phpt b/appsec/tests/extension/ddtrace_basic.phpt index 68ce30ae31a..e329e66273d 100644 --- a/appsec/tests/extension/ddtrace_basic.phpt +++ b/appsec/tests/extension/ddtrace_basic.phpt @@ -105,6 +105,7 @@ Array ( [_dd.p.dm] => -0 [_dd.p.tid] => %s + [_dd.sdk.otlp_export] => false [ddappsec] => true [runtime-id] => %s ) \ No newline at end of file diff --git a/appsec/tests/extension/rinit_record_span_tags.phpt b/appsec/tests/extension/rinit_record_span_tags.phpt index d3cfe2642a2..3562a1d81c0 100644 --- a/appsec/tests/extension/rinit_record_span_tags.phpt +++ b/appsec/tests/extension/rinit_record_span_tags.phpt @@ -87,6 +87,7 @@ Array [_dd.p.dm] => -0 [_dd.p.tid] => %s [_dd.runtime_family] => php + [_dd.sdk.otlp_export] => false [appsec.event] => true [http.endpoint] => /foo [http.method] => GET diff --git a/appsec/tests/extension/rinit_record_span_tags_fail.phpt b/appsec/tests/extension/rinit_record_span_tags_fail.phpt index 504489b844f..b4160a00ffb 100644 --- a/appsec/tests/extension/rinit_record_span_tags_fail.phpt +++ b/appsec/tests/extension/rinit_record_span_tags_fail.phpt @@ -56,5 +56,6 @@ Array ( [_dd.p.dm] => -0 [_dd.p.tid] => %s + [_dd.sdk.otlp_export] => false [runtime-id] => %s ) \ No newline at end of file diff --git a/appsec/tests/extension/rinit_root_span_add_tag.phpt b/appsec/tests/extension/rinit_root_span_add_tag.phpt index 922a31b8705..144575c8478 100644 --- a/appsec/tests/extension/rinit_root_span_add_tag.phpt +++ b/appsec/tests/extension/rinit_root_span_add_tag.phpt @@ -62,6 +62,7 @@ Array ( [_dd.p.dm] => -0 [_dd.p.tid] => %s + [_dd.sdk.otlp_export] => false [ddappsec] => true [env] => staging [http.method] => GET diff --git a/appsec/tests/extension/root_span_add_tag.phpt b/appsec/tests/extension/root_span_add_tag.phpt index 0448bdc6ac1..3d1b8b72c52 100644 --- a/appsec/tests/extension/root_span_add_tag.phpt +++ b/appsec/tests/extension/root_span_add_tag.phpt @@ -47,11 +47,13 @@ array(1) { ["type"]=> string(3) "cli" ["meta"]=> - array(4) { + array(5) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["after"]=> string(9) "root_span" ["runtime-id"]=> diff --git a/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt b/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt index 18325e0e794..b9a33c28ff5 100644 --- a/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt +++ b/appsec/tests/extension/root_span_add_tag_with_intermediate_spans.phpt @@ -72,6 +72,7 @@ Array [_dd.p.dm] => -0 [_dd.p.tid] => %s [_dd.runtime_family] => php + [_dd.sdk.otlp_export] => false [after] => root_span [before] => root_span [runtime-id] => %s diff --git a/tests/Common/SnapshotTestTrait.php b/tests/Common/SnapshotTestTrait.php index feb12737c2d..be61583431b 100644 --- a/tests/Common/SnapshotTestTrait.php +++ b/tests/Common/SnapshotTestTrait.php @@ -139,6 +139,9 @@ private function stopAndCompareSnapshotSession( if (!in_array('meta._dd.svc_src', $fieldsToIgnore, true)) { $fieldsToIgnore[] = 'meta._dd.svc_src'; } + if (!in_array('meta._dd.sdk.otlp_export', $fieldsToIgnore, true)) { + $fieldsToIgnore[] = 'meta._dd.sdk.otlp_export'; + } $url = self::$testAgentUrl . '/test/session/snapshot?ignores=' . implode(',', $fieldsToIgnore) . '&test_session_token=' . $token; diff --git a/tests/Common/SpanChecker.php b/tests/Common/SpanChecker.php index 6c7552f34d4..20dd5b22ab5 100644 --- a/tests/Common/SpanChecker.php +++ b/tests/Common/SpanChecker.php @@ -88,6 +88,7 @@ public static function dumpSpansGraph(array $spansGraph, int $indent = 0) if (isset($span['meta'])) { unset($span['meta']['_dd.p.dm']); unset($span['meta']['_dd.p.tid']); + unset($span['meta']['_dd.sdk.otlp_export']); unset($span['meta']['http.client_ip']); foreach ($span['meta'] as $k => $v) { $out .= str_repeat(' ', $indent) . ' ' . $k . ' => ' . $v . "\n"; @@ -490,6 +491,10 @@ function ($key) use ($pattern) { if (!isset($expectedTags['runtime-id'])) { unset($filtered['runtime-id']); } + // Ignore _dd.sdk.otlp_export unless explicitly tested + if (!isset($expectedTags['_dd.sdk.otlp_export'])) { + unset($filtered['_dd.sdk.otlp_export']); + } // http.client_ip is present depending on target SAPI and not helpful here to test if (!isset($expectedTags['http.client_ip'])) { unset($filtered['http.client_ip']); diff --git a/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt b/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt index f2e7b34f194..10bc4e9a015 100644 --- a/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt +++ b/tests/ext/distributed_tracing/distributed_trace_overwrite_active_span.phpt @@ -41,7 +41,7 @@ function largeBaseConvert($numString, $fromBase, $toBase) function dump_spans() { foreach (dd_trace_serialize_closed_spans() as $span) { - unset($span["meta"]["process_id"], $span["meta"]["runtime-id"], $span["meta"]["_dd.p.dm"], $span["meta"]["_dd.tags.process"]); + unset($span["meta"]["process_id"], $span["meta"]["runtime-id"], $span["meta"]["_dd.p.dm"], $span["meta"]["_dd.tags.process"], $span["meta"]["_dd.sdk.otlp_export"]); echo "parent: ", $span["parent_id"] ?? 0, ", trace: {$span["trace_id"]}, meta: " . json_encode($span["meta"] ?? []) . "\n"; } return $span; diff --git a/tests/ext/extract_server_values.phpt b/tests/ext/extract_server_values.phpt index 05f51ae9a07..69d8ad24e0a 100644 --- a/tests/ext/extract_server_values.phpt +++ b/tests/ext/extract_server_values.phpt @@ -31,11 +31,13 @@ var_dump(dd_trace_serialize_closed_spans()[0]["meta"]); ?> --EXPECTF-- -array(5) { +array(6) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.request.headers.0"]=> diff --git a/tests/ext/referrer_extraction_01.phpt b/tests/ext/referrer_extraction_01.phpt index e5dd8112877..6d41422406f 100644 --- a/tests/ext/referrer_extraction_01.phpt +++ b/tests/ext/referrer_extraction_01.phpt @@ -18,11 +18,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(8) { +array(9) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/referrer_extraction_02.phpt b/tests/ext/referrer_extraction_02.phpt index b1bd2435941..ce4817172a8 100644 --- a/tests/ext/referrer_extraction_02.phpt +++ b/tests/ext/referrer_extraction_02.phpt @@ -18,11 +18,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(9) { +array(10) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/referrer_extraction_03.phpt b/tests/ext/referrer_extraction_03.phpt index 466b388c86c..2a0f673213f 100644 --- a/tests/ext/referrer_extraction_03.phpt +++ b/tests/ext/referrer_extraction_03.phpt @@ -18,11 +18,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(8) { +array(9) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/referrer_extraction_04.phpt b/tests/ext/referrer_extraction_04.phpt index 18fe177d5a9..181a7684919 100644 --- a/tests/ext/referrer_extraction_04.phpt +++ b/tests/ext/referrer_extraction_04.phpt @@ -18,11 +18,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(8) { +array(9) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/referrer_extraction_05.phpt b/tests/ext/referrer_extraction_05.phpt index 85abb9c4484..edb0b56b5b2 100644 --- a/tests/ext/referrer_extraction_05.phpt +++ b/tests/ext/referrer_extraction_05.phpt @@ -18,11 +18,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(9) { +array(10) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/root_span_url_as_resource_names.phpt b/tests/ext/root_span_url_as_resource_names.phpt index 9840f0d8ac4..6ea6f960276 100644 --- a/tests/ext/root_span_url_as_resource_names.phpt +++ b/tests/ext/root_span_url_as_resource_names.phpt @@ -20,11 +20,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(8) { +array(9) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/root_span_url_as_resource_names_no_host.phpt b/tests/ext/root_span_url_as_resource_names_no_host.phpt index eec467b9242..e7aaa05caf1 100644 --- a/tests/ext/root_span_url_as_resource_names_no_host.phpt +++ b/tests/ext/root_span_url_as_resource_names_no_host.phpt @@ -19,11 +19,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(8) { +array(9) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["http.method"]=> diff --git a/tests/ext/root_span_url_with_post_no_param.phpt b/tests/ext/root_span_url_with_post_no_param.phpt index 8553f5f4ef0..ee48fa4a2a0 100644 --- a/tests/ext/root_span_url_with_post_no_param.phpt +++ b/tests/ext/root_span_url_with_post_no_param.phpt @@ -18,11 +18,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(4) { +array(5) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["runtime-id"]=> diff --git a/tests/ext/root_span_url_with_post_no_param_set.phpt b/tests/ext/root_span_url_with_post_no_param_set.phpt index 21c0c516765..2472ca28fdb 100644 --- a/tests/ext/root_span_url_with_post_no_param_set.phpt +++ b/tests/ext/root_span_url_with_post_no_param_set.phpt @@ -17,11 +17,13 @@ $spans = dd_trace_serialize_closed_spans(); var_dump($spans[0]['meta']); ?> --EXPECTF-- -array(4) { +array(5) { ["_dd.p.dm"]=> string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" + ["_dd.sdk.otlp_export"]=> + string(5) "false" ["_dd.tags.process"]=> string(%d) "%s" ["runtime-id"]=> diff --git a/tests/ext/sandbox/dd_dumper.inc b/tests/ext/sandbox/dd_dumper.inc index 9558dac9080..add87210a25 100644 --- a/tests/ext/sandbox/dd_dumper.inc +++ b/tests/ext/sandbox/dd_dumper.inc @@ -41,6 +41,7 @@ function dd_dump_spans($skipMeta = false) echo PHP_EOL; unset($span["meta"]["runtime-id"]); unset($span["meta"]["_dd.tags.process"]); + unset($span["meta"]["_dd.sdk.otlp_export"]); foreach ($span['meta'] as $k => $v) { echo str_repeat(' ', $indent) . ' ' . $k . ' => ' . $v . PHP_EOL; } @@ -82,6 +83,7 @@ function dd_clean_spans() { foreach ($spans as &$span) { if (isset($span['meta'])) { unset($span['meta']['_dd.tags.process']); + unset($span['meta']['_dd.sdk.otlp_export']); if (empty($span['meta'])) { unset($span['meta']); } diff --git a/tests/ext/sandbox/safe_to_string_metadata.phpt b/tests/ext/sandbox/safe_to_string_metadata.phpt index 0a6cc5c60a4..58efbfa21b2 100644 --- a/tests/ext/sandbox/safe_to_string_metadata.phpt +++ b/tests/ext/sandbox/safe_to_string_metadata.phpt @@ -57,7 +57,7 @@ $allTheTypes[0][1] = &$allTheTypes[0]; call_user_func_array('meta_to_string', $allTheTypes); list($span) = dd_trace_serialize_closed_spans(); -unset($span['meta']['process_id'], $span['meta']['_dd.tags.process']); +unset($span['meta']['process_id'], $span['meta']['_dd.tags.process'], $span['meta']['_dd.sdk.otlp_export']); $last = -1; foreach ($span['meta'] as $key => $value) { $index = (int)substr($key, 4); diff --git a/tests/ext/sdk_otlp_export_marker.phpt b/tests/ext/sdk_otlp_export_marker.phpt new file mode 100644 index 00000000000..1c04645c8fb --- /dev/null +++ b/tests/ext/sdk_otlp_export_marker.phpt @@ -0,0 +1,32 @@ +--TEST-- +_dd.sdk.otlp_export marker is set to "false" on the first span of each trace chunk only +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--FILE-- +name = "root_$i"; + \DDTrace\start_span()->name = "child_$i"; + \DDTrace\start_span()->name = "grandchild_$i"; + \DDTrace\close_span(); + \DDTrace\close_span(); + \DDTrace\close_span(); +} + +$seen = []; +foreach (dd_trace_serialize_closed_spans() as $span) { + $first = !isset($seen[$span['trace_id']]); + $seen[$span['trace_id']] = true; + echo ($first ? "first" : "other"), ": ", var_export($span['meta']['_dd.sdk.otlp_export'] ?? null, true), "\n"; +} +echo count($seen), " chunks\n"; +?> +--EXPECT-- +first: 'false' +other: NULL +other: NULL +first: 'false' +other: NULL +other: NULL +2 chunks diff --git a/tracer/serializer.c b/tracer/serializer.c index e1fc3e71721..d18c29f8179 100644 --- a/tracer/serializer.c +++ b/tracer/serializer.c @@ -1573,6 +1573,8 @@ ddog_SpanBytes *ddtrace_serialize_span_to_rust_span(ddtrace_span_data *span, ddo ddog_set_span_duration(rust_span, span->duration); if (is_first_span) { + ddog_add_str_span_meta_str(rust_span, "_dd.sdk.otlp_export", "false"); + zend_string *process_tags = datadog_process_tags_get_serialized(); if (ZSTR_LEN(process_tags)) { const char *svc_tag_appendix = NULL;