From 8c85f78502b507455c08a433807e6f7bbb174e5e Mon Sep 17 00:00:00 2001 From: Alexandre Choura <42672104+PROFeNoM@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:22:15 +0100 Subject: [PATCH 01/16] Update appsec tests (128-bit) (#2368) --- appsec/tests/extension/client_init_record_span_tags.phpt | 1 + appsec/tests/extension/ddtrace_basic.phpt | 5 +++-- appsec/tests/extension/rinit_record_span_tags.phpt | 1 + appsec/tests/extension/rinit_record_span_tags_fail.phpt | 1 + appsec/tests/extension/rinit_root_span_add_tag.phpt | 1 + appsec/tests/extension/root_span_add_tag.phpt | 4 +++- .../extension/root_span_add_tag_with_intermediate_spans.phpt | 1 + 7 files changed, 11 insertions(+), 3 deletions(-) diff --git a/appsec/tests/extension/client_init_record_span_tags.phpt b/appsec/tests/extension/client_init_record_span_tags.phpt index de70aee308b..c6313ee5acb 100644 --- a/appsec/tests/extension/client_init_record_span_tags.phpt +++ b/appsec/tests/extension/client_init_record_span_tags.phpt @@ -86,6 +86,7 @@ Array ( [_dd.appsec.json] => {"triggers":[{"found":"attack"},{"another":"attack"},{"yet another":"attack"}]} [_dd.p.dm] => -1 + [_dd.p.tid] => %s [_dd.runtime_family] => php [appsec.event] => true [http.method] => GET diff --git a/appsec/tests/extension/ddtrace_basic.phpt b/appsec/tests/extension/ddtrace_basic.phpt index 983319779cf..911b8245e43 100644 --- a/appsec/tests/extension/ddtrace_basic.phpt +++ b/appsec/tests/extension/ddtrace_basic.phpt @@ -44,7 +44,7 @@ echo 'number of commands: ', count($c), "\n"; DDTrace\start_span(); -// Compatibility with pre 0.81.0 +// Compatibility with pre 0.81.0 $root_span = \DDTrace\root_span(); var_dump($root_span->name); var_dump($root_span->service); @@ -53,7 +53,7 @@ var_dump($root_span->id); var_dump($root_span->meta); var_dump($root_span->metrics); -$trace_id = \DDTrace\trace_id(); +$trace_id = \DDTrace\root_span()->id; echo 'trace id: ', $trace_id, "\n"; echo "ddtrace_rshutdown\n"; @@ -104,4 +104,5 @@ Array [runtime-id] => %s [ddappsec] => true [_dd.p.dm] => -1 + [_dd.p.tid] => %s ) diff --git a/appsec/tests/extension/rinit_record_span_tags.phpt b/appsec/tests/extension/rinit_record_span_tags.phpt index 6f5233b9ae6..ad86473322d 100644 --- a/appsec/tests/extension/rinit_record_span_tags.phpt +++ b/appsec/tests/extension/rinit_record_span_tags.phpt @@ -81,6 +81,7 @@ Array ( [_dd.appsec.json] => {"triggers":[{"found":"attack"},{"another":"attack"},{"yet another":"attack"}]} [_dd.p.dm] => -1 + [_dd.p.tid] => %s [_dd.runtime_family] => php [appsec.event] => true [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 62cafee8f97..2f03eb5039c 100644 --- a/appsec/tests/extension/rinit_record_span_tags_fail.phpt +++ b/appsec/tests/extension/rinit_record_span_tags_fail.phpt @@ -54,4 +54,5 @@ Array ( [runtime-id] => %s [_dd.p.dm] => -1 + [_dd.p.tid] => %s ) diff --git a/appsec/tests/extension/rinit_root_span_add_tag.phpt b/appsec/tests/extension/rinit_root_span_add_tag.phpt index 1d5b1ae873c..015e84c4925 100644 --- a/appsec/tests/extension/rinit_root_span_add_tag.phpt +++ b/appsec/tests/extension/rinit_root_span_add_tag.phpt @@ -60,6 +60,7 @@ tags: Array ( [_dd.p.dm] => -1 + [_dd.p.tid] => %s [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 2a9469baaee..6d1d075914c 100644 --- a/appsec/tests/extension/root_span_add_tag.phpt +++ b/appsec/tests/extension/root_span_add_tag.phpt @@ -45,13 +45,15 @@ array(1) { ["type"]=> string(3) "cli" ["meta"]=> - array(3) { + array(4) { ["runtime-id"]=> string(%d) %s ["after"]=> string(9) "root_span" ["_dd.p.dm"]=> string(2) "-1" + ["_dd.p.tid"]=> + string(16) "%s" } ["metrics"]=> array(4) { 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 d9e2cac96a7..e18b55a0739 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 @@ -70,6 +70,7 @@ tags: Array ( [_dd.p.dm] => -1 + [_dd.p.tid] => %s [_dd.runtime_family] => php [after] => root_span [before] => root_span From 330f953cf13acc4c94e50bba1a15916eb0c334e5 Mon Sep 17 00:00:00 2001 From: pablomartinezbernardo <134320516+pablomartinezbernardo@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:30:33 +0100 Subject: [PATCH 02/16] CakePHP http.route implementation (#2360) * CakePHP http.route implementation * Fix test for cakephp & make it non-required for others * Remove cakephp 28 tests from php 7.1 tests * Reintroduce cakephp tests in php 7.1 and pass params in router --- .../CakePHP/CakePHPIntegration.php | 13 +++++++++ .../CakePHP/Version_2_8/app/Config/routes.php | 4 +++ .../Controller/ParameterizedController.php | 12 ++++++++ tests/Frameworks/TestScenarios.php | 1 + .../Util/CommonScenariosDataProviderTrait.php | 5 ++++ .../CakePHP/V2_8/CommonScenariosTest.php | 29 +++++++++++++++++++ 6 files changed, 64 insertions(+) create mode 100644 tests/Frameworks/CakePHP/Version_2_8/app/Controller/ParameterizedController.php diff --git a/src/Integrations/Integrations/CakePHP/CakePHPIntegration.php b/src/Integrations/Integrations/CakePHP/CakePHPIntegration.php index 1efc85911d3..92d8e8483f9 100644 --- a/src/Integrations/Integrations/CakePHP/CakePHPIntegration.php +++ b/src/Integrations/Integrations/CakePHP/CakePHPIntegration.php @@ -132,6 +132,19 @@ function (SpanData $span, array $args) use ($integration) { $span->meta[Tag::COMPONENT] = CakePHPIntegration::NAME; }); + \DDTrace\hook_method( + 'CakeRoute', + 'parse', + null, + function ($app, $appClass, $args, $retval) use ($integration) { + if (!$retval) { + return; + } + + $integration->rootSpan->meta[Tag::HTTP_ROUTE] = $app->template; + } + ); + return Integration::LOADED; } } diff --git a/tests/Frameworks/CakePHP/Version_2_8/app/Config/routes.php b/tests/Frameworks/CakePHP/Version_2_8/app/Config/routes.php index 07b57003286..eb8d23c183e 100644 --- a/tests/Frameworks/CakePHP/Version_2_8/app/Config/routes.php +++ b/tests/Frameworks/CakePHP/Version_2_8/app/Config/routes.php @@ -31,6 +31,10 @@ */ Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display')); + Router::connect('/parameterized/:param', + array('controller' => 'Parameterized', 'action' => 'customAction'), + array('pass' => array('param')) + ); /** * Load all plugin routes. See the CakePlugin documentation on * how to customize the loading of plugin routes. diff --git a/tests/Frameworks/CakePHP/Version_2_8/app/Controller/ParameterizedController.php b/tests/Frameworks/CakePHP/Version_2_8/app/Controller/ParameterizedController.php new file mode 100644 index 00000000000..9dffb963641 --- /dev/null +++ b/tests/Frameworks/CakePHP/Version_2_8/app/Controller/ParameterizedController.php @@ -0,0 +1,12 @@ +autoRender = false; + echo 'Hello ' + $param; + } +} diff --git a/tests/Frameworks/TestScenarios.php b/tests/Frameworks/TestScenarios.php index fb2ff6a6d33..10f13cd9b93 100644 --- a/tests/Frameworks/TestScenarios.php +++ b/tests/Frameworks/TestScenarios.php @@ -17,6 +17,7 @@ public static function all() )->expectStatusCode(500), GetSpec::create('A GET request to a missing route', '/does_not_exist?key=value&pwd=should_redact'), GetSpec::create('A GET request to a dynamic route returning a string', '/dynamic_route/dynamic01/static/dynamic02'), + GetSpec::create('A GET request to a route with a parameter', '/parameterized/paramValue'), ]; } } diff --git a/tests/Frameworks/Util/CommonScenariosDataProviderTrait.php b/tests/Frameworks/Util/CommonScenariosDataProviderTrait.php index 4ed91f4ed77..0b225075b5f 100644 --- a/tests/Frameworks/Util/CommonScenariosDataProviderTrait.php +++ b/tests/Frameworks/Util/CommonScenariosDataProviderTrait.php @@ -46,6 +46,11 @@ public function buildDataProvider($definedExpectations) if ($i !== false) { unset($unexpectedRequest[$i]); } + //Only available in frameworks implementing http.route + $i = array_search('A GET request to a route with a parameter', $unexpectedRequest, true); + if ($i !== false) { + unset($unexpectedRequest[$i]); + } if ($unexpectedRequest) { throw new \Exception( 'Found the following scenarios not having any expectation defined: ' diff --git a/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php b/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php index fdcab293078..1e84c4fa9ab 100644 --- a/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php +++ b/tests/Integrations/CakePHP/V2_8/CommonScenariosTest.php @@ -52,6 +52,7 @@ public function provideSpecs() 'http.method' => 'GET', 'http.url' => 'http://localhost:9999/simple?key=value&', 'http.status_code' => '200', + 'http.route' => '/:controller', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'cakephp', ])->withChildren([ @@ -77,6 +78,7 @@ public function provideSpecs() 'http.method' => 'GET', 'http.url' => 'http://localhost:9999/simple_view?key=value&', 'http.status_code' => '200', + 'http.route' => '/:controller', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'cakephp', ])->withChildren([ @@ -111,6 +113,7 @@ public function provideSpecs() 'http.method' => 'GET', 'http.url' => 'http://localhost:9999/error?key=value&', 'http.status_code' => '500', + 'http.route' => '/:controller', Tag::SPAN_KIND => 'server', Tag::COMPONENT => 'cakephp', ])->withExistingTagsNames([ @@ -140,6 +143,32 @@ public function provideSpecs() ]), ]), ], + 'A GET request to a route with a parameter' => [ + SpanAssertion::build( + 'cakephp.request', + 'cakephp_test_app', + 'web', + 'GET ParameterizedController@customAction' + )->withExactTags([ + 'cakephp.route.controller' => 'Parameterized', + 'cakephp.route.action' => 'customAction', + 'http.method' => 'GET', + 'http.url' => 'http://localhost:9999/parameterized/paramValue', + 'http.status_code' => '200', + 'http.route' => '/parameterized/:param', + Tag::SPAN_KIND => 'server', + Tag::COMPONENT => 'cakephp', + ])->withChildren([ + SpanAssertion::build( + 'Controller.invokeAction', + 'cakephp_test_app', + 'web', + 'Controller.invokeAction' + )->withExactTags([ + Tag::COMPONENT => 'cakephp', + ]) + ]), + ] ] ); } From 29a83cc4469c063d05623e9f9d1ae0e5ac91863f Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Fri, 17 Nov 2023 15:39:57 +0100 Subject: [PATCH 03/16] Retain tracestate from tracecontext if extracted at all (#2359) Signed-off-by: Bob Weinand --- ext/distributed_tracing_headers.c | 340 ++++++++++++------ ext/distributed_tracing_headers.h | 2 +- .../Integrations/Curl/CurlIntegrationTest.php | 9 + 3 files changed, 232 insertions(+), 119 deletions(-) diff --git a/ext/distributed_tracing_headers.c b/ext/distributed_tracing_headers.c index ef9a9d238cc..aa9355fe475 100644 --- a/ext/distributed_tracing_headers.c +++ b/ext/distributed_tracing_headers.c @@ -18,26 +18,81 @@ static inline bool dd_is_hex_char(char chr) { return (chr >= '0' && chr <= '9') || (chr >= 'a' && chr <= 'f'); } - -ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_read_header *read_header, void *data) { - zend_string *trace_id_str, *parent_id_str, *priority_str, *propagated_tags, *b3_header_str, *traceparent, *tracestate; - +static ddtrace_distributed_tracing_result dd_init_empty_result(void) { ddtrace_distributed_tracing_result result = {0}; result.priority_sampling = DDTRACE_PRIORITY_SAMPLING_UNKNOWN; zend_hash_init(&result.tracestate_unknown_dd_keys, 8, unused, ZVAL_PTR_DTOR, 0); zend_hash_init(&result.propagated_tags, 8, unused, ZVAL_PTR_DTOR, 0); zend_hash_init(&result.meta_tags, 8, unused, ZVAL_PTR_DTOR, 0); + return result; +} - zend_array *extract = zai_config_is_modified(DDTRACE_CONFIG_DD_TRACE_PROPAGATION_STYLE) - && !zai_config_is_modified(DDTRACE_CONFIG_DD_TRACE_PROPAGATION_STYLE_EXTRACT) - ? get_DD_TRACE_PROPAGATION_STYLE() : get_DD_TRACE_PROPAGATION_STYLE_EXTRACT(); - bool parse_datadog = zend_hash_str_exists(extract, ZEND_STRL("datadog")); - bool parse_tracestate = zend_hash_str_exists(extract, ZEND_STRL("tracecontext")); - bool parse_b3 = zend_hash_str_exists(extract, ZEND_STRL("b3")) || zend_hash_str_exists(extract, ZEND_STRL("b3multi")); - bool parse_b3_single = zend_hash_str_exists(extract, ZEND_STRL("b3 single header")); - bool parse_datadog_meta_headers = parse_datadog || parse_b3 || parse_b3_single; +static void dd_check_tid(ddtrace_distributed_tracing_result *result) { + zval *tidzv = zend_hash_str_find(&result->meta_tags, ZEND_STRL("_dd.p.tid")); + if (tidzv && result->trace_id.low) { + uint64_t tid = ddtrace_parse_hex_span_id(tidzv); + uint64_t cur_high = result->trace_id.high; + if (tid && Z_TYPE_P(tidzv) == IS_STRING && Z_STRLEN_P(tidzv) == 16) { + if (!cur_high || tid == cur_high) { + result->trace_id.high = tid; + } else { + zval error; + ZVAL_STR(&error, zend_strpprintf(0, "inconsistent_tid %s", Z_STRVAL_P(tidzv))); + zend_hash_str_update(&result->meta_tags, ZEND_STRL("_dd.propagation_error"), &error); + } + } else if (Z_TYPE_P(tidzv) == IS_STRING && strcmp(Z_STRVAL_P(tidzv), "0") != 0) { + zval error; + ZVAL_STR(&error, zend_strpprintf(0, "malformed_tid %s", Z_STRVAL_P(tidzv))); + zend_hash_str_update(&result->meta_tags, ZEND_STRL("_dd.propagation_error"), &error); + } + zend_hash_str_del(&result->meta_tags, ZEND_STRL("_dd.p.tid")); + } +} + +static ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids_datadog(ddtrace_read_header *read_header, void *data) { + zend_string *trace_id_str, *parent_id_str, *priority_str, *propagated_tags; + ddtrace_distributed_tracing_result result = dd_init_empty_result(); - if (parse_b3_single && read_header(ZAI_STRL("B3"), "b3", &b3_header_str, data)) { + read_header(ZAI_STRL("X_DATADOG_ORIGIN"), "x-datadog-origin", &result.origin, data); + + if (read_header(ZAI_STRL("X_DATADOG_TRACE_ID"), "x-datadog-trace-id", &trace_id_str, data)) { + zval trace_zv; + ZVAL_STR(&trace_zv, trace_id_str); + result.trace_id = (ddtrace_trace_id) {.low = ddtrace_parse_userland_span_id(&trace_zv)}; + zend_string_release(trace_id_str); + } + + if (!result.trace_id.low && !result.trace_id.high) { + return result; + } + + if (read_header(ZAI_STRL("X_DATADOG_PARENT_ID"), "x-datadog-parent-id", &parent_id_str, data)) { + zval parent_zv; + ZVAL_STR(&parent_zv, parent_id_str); + result.parent_id = ddtrace_parse_userland_span_id(&parent_zv); + zend_string_release(parent_id_str); + } + + if (read_header(ZAI_STRL("X_DATADOG_SAMPLING_PRIORITY"), "x-datadog-sampling-priority", &priority_str, data)) { + result.priority_sampling = strtol(ZSTR_VAL(priority_str), NULL, 10); + zend_string_release(priority_str); + } + + if (read_header(ZAI_STRL("X_DATADOG_TAGS"), "x-datadog-tags", &propagated_tags, data)) { + ddtrace_add_tracer_tags_from_header(propagated_tags, &result.meta_tags, &result.propagated_tags); + zend_string_release(propagated_tags); + + dd_check_tid(&result); + } + + return result; +} + +static ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids_b3_single_header(ddtrace_read_header *read_header, void *data) { + zend_string *b3_header_str; + ddtrace_distributed_tracing_result result = dd_init_empty_result(); + + if (read_header(ZAI_STRL("B3"), "b3", &b3_header_str, data)) { char *b3_ptr = ZSTR_VAL(b3_header_str), *b3_end = b3_ptr + ZSTR_LEN(b3_header_str); char *b3_traceid = b3_ptr; while (b3_ptr < b3_end && *b3_ptr != '-') { @@ -74,42 +129,30 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_ zend_string_release(b3_header_str); } - if (parse_datadog_meta_headers) { - read_header(ZAI_STRL("X_DATADOG_ORIGIN"), "x-datadog-origin", &result.origin, data); - } + return result; +} - if (parse_datadog && read_header(ZAI_STRL("X_DATADOG_TRACE_ID"), "x-datadog-trace-id", &trace_id_str, data)) { - zval trace_zv; - ZVAL_STR(&trace_zv, trace_id_str); - result.trace_id = (ddtrace_trace_id){ .low = ddtrace_parse_userland_span_id(&trace_zv) }; - zend_string_release(trace_id_str); - } else if (parse_b3 && read_header(ZAI_STRL("X_B3_TRACEID"), "x-b3-traceid", &trace_id_str, data)) { +static ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids_b3(ddtrace_read_header *read_header, void *data) { + zend_string *trace_id_str, *parent_id_str, *priority_str; + ddtrace_distributed_tracing_result result = dd_init_empty_result(); + + if (read_header(ZAI_STRL("X_B3_TRACEID"), "x-b3-traceid", &trace_id_str, data)) { result.trace_id = dd_parse_b3_trace_id(ZSTR_VAL(trace_id_str), ZSTR_LEN(trace_id_str)); zend_string_release(trace_id_str); } - if (result.trace_id.low || result.trace_id.high) { - if (parse_datadog && read_header(ZAI_STRL("X_DATADOG_PARENT_ID"), "x-datadog-parent-id", &parent_id_str, data)) { - zval parent_zv; - ZVAL_STR(&parent_zv, parent_id_str); - result.parent_id = ddtrace_parse_userland_span_id(&parent_zv); - zend_string_release(parent_id_str); - } else if (parse_b3 && read_header(ZAI_STRL("X_B3_SPANID"), "x-b3-spanid", &parent_id_str, data)) { - zval parent_zv; - ZVAL_STR(&parent_zv, parent_id_str); - result.parent_id = ddtrace_parse_hex_span_id(&parent_zv); - zend_string_release(parent_id_str); - } - } else { - // skip, if no valid trace is present - parse_datadog_meta_headers = 0; - parse_datadog = 0; + if (!result.trace_id.low && !result.trace_id.high) { + return result; } - if (parse_datadog && read_header(ZAI_STRL("X_DATADOG_SAMPLING_PRIORITY"), "x-datadog-sampling-priority", &priority_str, data)) { - result.priority_sampling = strtol(ZSTR_VAL(priority_str), NULL, 10); - zend_string_release(priority_str); - } else if (parse_b3 && read_header(ZAI_STRL("X_B3_SAMPLED"), "x-b3-sampled", &priority_str, data)) { + if (read_header(ZAI_STRL("X_B3_SPANID"), "x-b3-spanid", &parent_id_str, data)) { + zval parent_zv; + ZVAL_STR(&parent_zv, parent_id_str); + result.parent_id = ddtrace_parse_hex_span_id(&parent_zv); + zend_string_release(parent_id_str); + } + + if (read_header(ZAI_STRL("X_B3_SAMPLED"), "x-b3-sampled", &priority_str, data)) { if (ZSTR_LEN(priority_str) == 1) { if (ZSTR_VAL(priority_str)[0] == '0') { result.priority_sampling = 0; @@ -122,76 +165,77 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_ result.priority_sampling = 0; } zend_string_release(priority_str); - } else if (parse_b3 && read_header(ZAI_STRL("X_B3_FLAGS"), "x-b3-flags", &priority_str, data)) { + } else if (read_header(ZAI_STRL("X_B3_FLAGS"), "x-b3-flags", &priority_str, data)) { if (ZSTR_LEN(priority_str) == 1 && ZSTR_VAL(priority_str)[1] == '1') { result.priority_sampling = PRIORITY_SAMPLING_USER_KEEP; } zend_string_release(priority_str); } - if (parse_datadog_meta_headers && read_header(ZAI_STRL("X_DATADOG_TAGS"), "x-datadog-tags", &propagated_tags, data)) { - ddtrace_add_tracer_tags_from_header(propagated_tags, &result.meta_tags, &result.propagated_tags); - zend_string_release(propagated_tags); - } + return result; +} - // "{version:2}-{trace-id:32}-{parent-id:16}-{trace-flags:2}" - if (parse_tracestate && read_header(ZAI_STRL("TRACEPARENT"), "traceparent", &traceparent, data)) { - do { - // skip whitespace - char *ws = ZSTR_VAL(traceparent), *wsend = ws + ZSTR_LEN(traceparent); - while (ws < wsend && isspace(*ws)) { - ++ws; - } - if (ws == wsend) { - break; - } - while (isspace(*--wsend)); - - size_t tracedata_len = wsend + 1 - ws; - struct { - char version[2]; - char version_hyphen; - char trace_id[32]; - char trace_id_hyphen; - char parent_id[16]; - char parent_id_hyphen; - char trace_flags[2]; - char trailing_data[]; - } *tracedata = (void *)ws; - - if (tracedata_len < sizeof(*tracedata) - || !dd_is_hex_char(tracedata->version[0]) || !dd_is_hex_char(tracedata->version[1]) - || *(uint16_t *)tracedata->version == ('f' << 8) + 'f' // 0xFF is invalid version - || tracedata->version_hyphen != '-' - || tracedata->trace_id_hyphen != '-' - || tracedata->parent_id_hyphen != '-' - || !dd_is_hex_char(tracedata->trace_flags[0]) || !dd_is_hex_char(tracedata->trace_flags[1]) - || (tracedata_len > sizeof(*tracedata) - && ((tracedata->version[0] == '0' && tracedata->version[1] == '0') || tracedata->trailing_data[0] != '-')) - ) { - parse_tracestate = 0; - break; - } +static ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids_tracecontext(ddtrace_read_header *read_header, void *data) { + zend_string *traceparent, *tracestate; + ddtrace_distributed_tracing_result result = dd_init_empty_result(); - ddtrace_trace_id trace_id = { - .high = ddtrace_parse_hex_span_id_str(tracedata->trace_id, 16), - .low = ddtrace_parse_hex_span_id_str(&tracedata->trace_id[16], 16) - }; - uint64_t parent_id = ddtrace_parse_hex_span_id_str(tracedata->parent_id, 16); + // "{version:2}-{trace-id:32}-{parent-id:16}-{trace-flags:2}" + if (read_header(ZAI_STRL("TRACEPARENT"), "traceparent", &traceparent, data)) { + // skip whitespace + char *ws = ZSTR_VAL(traceparent), *wsend = ws + ZSTR_LEN(traceparent); + while (ws < wsend && isspace(*ws)) { + ++ws; + } + if (ws == wsend) { + zend_string_release(traceparent); + return result; + } + while (isspace(*--wsend)); + + size_t tracedata_len = wsend + 1 - ws; + struct { + char version[2]; + char version_hyphen; + char trace_id[32]; + char trace_id_hyphen; + char parent_id[16]; + char parent_id_hyphen; + char trace_flags[2]; + char trailing_data[]; + } *tracedata = (void *)ws; + + if (tracedata_len < sizeof(*tracedata) + || !dd_is_hex_char(tracedata->version[0]) || !dd_is_hex_char(tracedata->version[1]) + || *(uint16_t *)tracedata->version == ('f' << 8) + 'f' // 0xFF is invalid version + || tracedata->version_hyphen != '-' + || tracedata->trace_id_hyphen != '-' + || tracedata->parent_id_hyphen != '-' + || !dd_is_hex_char(tracedata->trace_flags[0]) || !dd_is_hex_char(tracedata->trace_flags[1]) + || (tracedata_len > sizeof(*tracedata) + && ((tracedata->version[0] == '0' && tracedata->version[1] == '0') || tracedata->trailing_data[0] != '-')) + ) { + zend_string_release(traceparent); + return result; + } - if ((!trace_id.low && !trace_id.high) || !parent_id) { - parse_tracestate = 0; - break; - } + ddtrace_trace_id trace_id = { + .high = ddtrace_parse_hex_span_id_str(tracedata->trace_id, 16), + .low = ddtrace_parse_hex_span_id_str(&tracedata->trace_id[16], 16) + }; + uint64_t parent_id = ddtrace_parse_hex_span_id_str(tracedata->parent_id, 16); - result.trace_id = trace_id; - result.parent_id = parent_id; - result.priority_sampling = (tracedata->trace_flags[1] & 1) == (tracedata->trace_flags[1] <= '9'); // ('a' & 1) == 1 - } while (0); zend_string_release(traceparent); + if ((!trace_id.low && !trace_id.high) || !parent_id) { + return result; + } + + result.trace_id = trace_id; + result.parent_id = parent_id; + result.priority_sampling = (tracedata->trace_flags[1] & 1) == (tracedata->trace_flags[1] <= '9'); // ('a' & 1) == 1 + // header format: "[*,]dd=s:1;o:rum;t.dm:-4;t.usr.id:12345[,*]" - if (parse_tracestate && read_header(ZAI_STRL("TRACESTATE"), "tracestate", &tracestate, data)) { + if (read_header(ZAI_STRL("TRACESTATE"), "tracestate", &tracestate, data)) { bool last_comma = true; result.tracestate = zend_string_alloc(ZSTR_LEN(tracestate), 0); char *persist = ZSTR_VAL(result.tracestate); @@ -200,9 +244,6 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_ // dd member if (last_comma && ptr + 2 < end && ptr[0] == 'd' && ptr[1] == 'd' && (ptr[2] == '=' || ptr[2] == '\t' || ptr[2] == ' ')) { // If there's dd= members, ignore x-datadog-tags fully - zend_hash_clean(&result.meta_tags); - zend_hash_clean(&result.propagated_tags); - while (ptr < end && *ptr != '=') { ++ptr; } @@ -281,26 +322,89 @@ ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_ ZSTR_LEN(result.tracestate) = persist - ZSTR_VAL(result.tracestate); zend_string_release(tracestate); } + + dd_check_tid(&result); } - zval *tidzv = zend_hash_str_find(&result.meta_tags, ZEND_STRL("_dd.p.tid")); - if (tidzv && result.trace_id.low) { - uint64_t tid = ddtrace_parse_hex_span_id(tidzv); - uint64_t cur_high = result.trace_id.high; - if (tid && Z_TYPE_P(tidzv) == IS_STRING && Z_STRLEN_P(tidzv) == 16) { - if (!cur_high || tid == cur_high) { - result.trace_id.high = tid; - } else { - zval error; - ZVAL_STR(&error, zend_strpprintf(0, "inconsistent_tid %s", Z_STRVAL_P(tidzv))); - zend_hash_str_update(&result.meta_tags, ZEND_STRL("_dd.propagation_error"), &error); + return result; +} + +ddtrace_distributed_tracing_result ddtrace_read_distributed_tracing_ids(ddtrace_read_header *read_header, void *data) { + ddtrace_distributed_tracing_result result = {0}; + + zend_array *extract = zai_config_is_modified(DDTRACE_CONFIG_DD_TRACE_PROPAGATION_STYLE) + && !zai_config_is_modified(DDTRACE_CONFIG_DD_TRACE_PROPAGATION_STYLE_EXTRACT) + ? get_DD_TRACE_PROPAGATION_STYLE() : get_DD_TRACE_PROPAGATION_STYLE_EXTRACT(); + + zend_string *extraction_style; + ddtrace_distributed_tracing_result (*func)(ddtrace_read_header *read_header, void *data) = NULL; + ZEND_HASH_FOREACH_STR_KEY(extract, extraction_style) { + bool has_trace = result.trace_id.low || result.trace_id.high; + + if (!has_trace && zend_string_equals_literal(extraction_style, "datadog")) { + func = ddtrace_read_distributed_tracing_ids_datadog; + } else if (zend_string_equals_literal(extraction_style, "tracecontext")) { + func = ddtrace_read_distributed_tracing_ids_tracecontext; + } else if (!has_trace && (zend_string_equals_literal(extraction_style, "b3") || zend_string_equals_literal(extraction_style, "b3multi"))) { + func = ddtrace_read_distributed_tracing_ids_b3; + } else if (!has_trace && zend_string_equals_literal(extraction_style, "b3 single header")) { + func = ddtrace_read_distributed_tracing_ids_b3_single_header; + } else { + continue; + } + + if (!has_trace) { + zend_string *existing_origin = result.origin; + if (result.meta_tags.arData) { + zend_hash_destroy(&result.meta_tags); } - } else if (Z_TYPE_P(tidzv) == IS_STRING && strcmp(Z_STRVAL_P(tidzv), "0") != 0) { - zval error; - ZVAL_STR(&error, zend_strpprintf(0, "malformed_tid %s", Z_STRVAL_P(tidzv))); - zend_hash_str_update(&result.meta_tags, ZEND_STRL("_dd.propagation_error"), &error); + if (result.propagated_tags.arData) { + zend_hash_destroy(&result.propagated_tags); + } + if (result.tracestate_unknown_dd_keys.arData) { + zend_hash_destroy(&result.tracestate_unknown_dd_keys); + } + + result = func(read_header, data); + + // As an exception, the x-datadog-origin can be submitted standalone, without valid trace id + if (existing_origin) { + if (result.trace_id.low || result.trace_id.high) { + zend_string_release(existing_origin); + } else { + if (result.origin) { + zend_string_release(result.origin); + } + result.origin = existing_origin; + } + } + } else { + ddtrace_distributed_tracing_result new_result = func(read_header, data); + if (result.trace_id.low == new_result.trace_id.low && result.trace_id.high == new_result.trace_id.high) { + if (!result.tracestate && new_result.tracestate) { + result.tracestate = new_result.tracestate; + new_result.tracestate = NULL; + + zend_hash_destroy(&result.tracestate_unknown_dd_keys); + result.tracestate_unknown_dd_keys = new_result.tracestate_unknown_dd_keys; + zend_hash_init(&new_result.tracestate_unknown_dd_keys, 0, NULL, NULL, 0); + } + } + + if (new_result.tracestate) { + zend_string_release(new_result.tracestate); + } + if (new_result.origin) { + zend_string_release(new_result.origin); + } + zend_hash_destroy(&new_result.meta_tags); + zend_hash_destroy(&new_result.propagated_tags); + zend_hash_destroy(&new_result.tracestate_unknown_dd_keys); } - zend_hash_str_del(&result.meta_tags, ZEND_STRL("_dd.p.tid")); + } ZEND_HASH_FOREACH_END(); + + if (!func) { + return dd_init_empty_result(); } return result; diff --git a/ext/distributed_tracing_headers.h b/ext/distributed_tracing_headers.h index e8840c61b98..b69120cdf28 100644 --- a/ext/distributed_tracing_headers.h +++ b/ext/distributed_tracing_headers.h @@ -15,7 +15,7 @@ typedef struct { HashTable meta_tags; int priority_sampling; enum dd_sampling_mechanism sampling_mechanism; - bool conflicting_sampling_priority; // propagated priorty does not match tracestate priority + bool conflicting_sampling_priority; // propagated priority does not match tracestate priority } ddtrace_distributed_tracing_result; typedef bool (ddtrace_read_header)(zai_str zai_header, const char *lowercase_header, zend_string **header_value, void *data); diff --git a/tests/Integrations/Curl/CurlIntegrationTest.php b/tests/Integrations/Curl/CurlIntegrationTest.php index e34714306d4..aa8ca0ac2c1 100644 --- a/tests/Integrations/Curl/CurlIntegrationTest.php +++ b/tests/Integrations/Curl/CurlIntegrationTest.php @@ -49,6 +49,7 @@ protected function envsToCleanUpAtTearDown() 'DD_TRACE_MEMORY_LIMIT', 'DD_TRACE_SPANS_LIMIT', 'DD_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED', + 'DD_TRACE_PROPAGATION_STYLE_INJECT', ]; } @@ -362,6 +363,8 @@ public function testNonExistingHost() public function testOriginIsPropagatedAndSetsRootSpanTag() { + ini_set("datadog.trace.propagation_style_inject", ""); + $found = []; $traces = $this->inWebServer( function ($execute) use (&$found) { @@ -387,6 +390,8 @@ function ($execute) use (&$found) { public function testDistributedTracingIsPropagatedOnCopiedHandle() { + ini_set("datadog.trace.propagation_style_inject", ""); + $found = []; $traces = $this->inWebServer( function ($execute) use (&$found) { @@ -414,6 +419,8 @@ function ($execute) use (&$found) { public function testDistributedTracingIsNotPropagatedIfDisabled() { + ini_set("datadog.trace.propagation_style_inject", ""); + $this->inWebServer( function ($execute) use (&$found) { $found = json_decode($execute(GetSpec::create( @@ -439,6 +446,8 @@ function ($execute) use (&$found) { public function testTracerIsRunningAtLimitedCapacityWeStillPropagateTheSpan() { + ini_set("datadog.trace.propagation_style_inject", ""); + $traces = $this->inWebServer( function ($execute) use (&$found) { $found = json_decode($execute(GetSpec::create( From 92240ae4d78c540c8cbfc4dd84bf799ba22d912c Mon Sep 17 00:00:00 2001 From: Alexandre Choura <42672104+PROFeNoM@users.noreply.github.com> Date: Fri, 17 Nov 2023 16:03:52 +0100 Subject: [PATCH 04/16] Respect `DD_LOGS_INJECTION` default value (#2365) * fix: Disable the logs integration by default * Update tests --- ext/configuration.h | 10 +++--- ext/integrations/integrations.h | 2 +- .../Custom/Autoloaded/InstrumentationTest.php | 15 +++++--- tests/Integrations/Logs/BaseLogsTest.php | 9 +++-- .../Logs/LaminasLogV2/LaminasLogV2Test.php | 3 ++ tests/ext/logging_default_value.phpt | 19 ++++++++++ tests/ext/telemetry/integration.phpt | 36 +++++++++++-------- 7 files changed, 68 insertions(+), 26 deletions(-) create mode 100644 tests/ext/logging_default_value.phpt diff --git a/ext/configuration.h b/ext/configuration.h index 19e0824944d..f41609d15a8 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -52,13 +52,15 @@ enum ddtrace_dbm_propagation_mode { #define DD_CFG_STR(str) #str #define DD_CFG_EXPSTR(str) DD_CFG_STR(str) -#define INTEGRATION_ALIAS(id, _, alias) \ - CALIAS(BOOL, DD_TRACE_##id##_ENABLED, "true", CALIASES(DD_CFG_STR(alias))) +#define INTEGRATION_ALIAS(id, _, initial, alias) \ + CALIAS(BOOL, DD_TRACE_##id##_ENABLED, initial, CALIASES(DD_CFG_STR(alias))) +#define INTEGRATION_WITH_DEFAULT(id, _, initial) \ + CONFIG(BOOL, DD_TRACE_##id##_ENABLED, initial) #define INTEGRATION_NORMAL(id, _) \ CONFIG(BOOL, DD_TRACE_##id##_ENABLED, "true") -#define GET_INTEGRATION_CONFIG_MACRO(_1, _2, NAME, ...) NAME +#define GET_INTEGRATION_CONFIG_MACRO(_1, _2, DEFAULT, NAME, ...) NAME #define INTEGRATION(id, ...) \ - GET_INTEGRATION_CONFIG_MACRO(__VA_ARGS__, INTEGRATION_ALIAS, INTEGRATION_NORMAL)(id, __VA_ARGS__) \ + GET_INTEGRATION_CONFIG_MACRO(__VA_ARGS__, INTEGRATION_ALIAS, INTEGRATION_WITH_DEFAULT, INTEGRATION_NORMAL)(id, __VA_ARGS__) \ CALIAS(BOOL, DD_TRACE_##id##_ANALYTICS_ENABLED, DD_CFG_EXPSTR(DD_INTEGRATION_ANALYTICS_ENABLED_DEFAULT), \ CALIASES(DD_CFG_STR(DD_##id##_ANALYTICS_ENABLED), DD_CFG_STR(DD_TRACE_##id##_ANALYTICS_ENABLED))) \ CALIAS(DOUBLE, DD_TRACE_##id##_ANALYTICS_SAMPLE_RATE, DD_CFG_EXPSTR(DD_INTEGRATION_ANALYTICS_SAMPLE_RATE_DEFAULT), \ diff --git a/ext/integrations/integrations.h b/ext/integrations/integrations.h index f8bd894f9ac..3763bc7d2b4 100644 --- a/ext/integrations/integrations.h +++ b/ext/integrations/integrations.h @@ -19,7 +19,7 @@ INTEGRATION(LAMINAS, "laminas") \ INTEGRATION(LARAVEL, "laravel") \ INTEGRATION(LARAVELQUEUE, "laravelqueue") \ - INTEGRATION(LOGS, "logs", DD_LOGS_INJECTION) \ + INTEGRATION(LOGS, "logs", "false", DD_LOGS_INJECTION) \ INTEGRATION(LUMEN, "lumen") \ INTEGRATION(MAGENTO, "magento") \ INTEGRATION(MEMCACHE, "memcache") \ diff --git a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php index bc77752166c..2099ef898d6 100644 --- a/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php +++ b/tests/Integrations/Custom/Autoloaded/InstrumentationTest.php @@ -78,9 +78,16 @@ public function testInstrumentation() $this->assertEquals("app-started", $payloads[0]["request_type"]); $this->assertEquals("app-dependencies-loaded", $payloads[1]["request_type"]); $this->assertEquals("app-integrations-change", $payloads[2]["request_type"]); - $this->assertEquals([[ - "name" => "pdo", - "enabled" => true, - ]], $payloads[2]["payload"]["integrations"]); + $this->assertEquals([ + [ + "name" => "pdo", + "enabled" => true, + ], + [ + "name" => "logs", + "enabled" => false, + "version" => "" + ] + ], $payloads[2]["payload"]["integrations"]); } } diff --git a/tests/Integrations/Logs/BaseLogsTest.php b/tests/Integrations/Logs/BaseLogsTest.php index f760fa1e3e4..b42485a8fa3 100644 --- a/tests/Integrations/Logs/BaseLogsTest.php +++ b/tests/Integrations/Logs/BaseLogsTest.php @@ -37,7 +37,8 @@ protected function withPlaceholders( 'DD_ENV=my-env', 'DD_SERVICE=my-service', 'DD_VERSION=4.2', - 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=' . ($is128bit ? '1' : '0') + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=' . ($is128bit ? '1' : '0'), + 'DD_LOGS_INJECTION=1' ]); $this->isolateTracer(function () use ($levelNameFn, $logger, $is128bit, $logLevelName) { @@ -72,7 +73,8 @@ protected function inContext( 'DD_ENV=my-env', 'DD_SERVICE=my-service', 'DD_VERSION=4.2', - 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=' . ($is128bit ? '1' : '0') + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=' . ($is128bit ? '1' : '0'), + 'DD_LOGS_INJECTION=1' ]); $this->isolateTracer(function () use ($levelNameFn, $logger, $is128bit, $logLevelName) { @@ -107,7 +109,8 @@ protected function appended( 'DD_ENV=my-env', 'DD_SERVICE=my-service', 'DD_VERSION=4.2', - 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=' . ($is128bit ? '1' : '0') + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=' . ($is128bit ? '1' : '0'), + 'DD_LOGS_INJECTION=1' ]); $this->isolateTracer(function () use ($levelNameFn, $logger, $is128bit, $logLevelName) { diff --git a/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php b/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php index 93e7c680f26..e6deb9c4b1d 100644 --- a/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php +++ b/tests/Integrations/Logs/LaminasLogV2/LaminasLogV2Test.php @@ -13,6 +13,9 @@ class LaminasLogV2Test extends BaseLogsTest protected function ddSetUp() { parent::ddSetUp(); + $this->putEnvAndReloadConfig([ + 'DD_LOGS_INJECTION=1' + ]); $integration = new LaminasIntegration(); $integration->init(); } diff --git a/tests/ext/logging_default_value.phpt b/tests/ext/logging_default_value.phpt new file mode 100644 index 00000000000..6ea045103ec --- /dev/null +++ b/tests/ext/logging_default_value.phpt @@ -0,0 +1,19 @@ +--TEST-- +Assess that the log integration is disabled by default +--FILE-- + +--EXPECT-- +bool(false) +bool(false) +bool(true) +bool(true) diff --git a/tests/ext/telemetry/integration.phpt b/tests/ext/telemetry/integration.phpt index 0ab7a42cbb8..2e1f29577b2 100644 --- a/tests/ext/telemetry/integration.phpt +++ b/tests/ext/telemetry/integration.phpt @@ -54,7 +54,7 @@ namespace $batch = $json["request_type"] == "message-batch" ? $json["payload"] : [$json]; foreach ($batch as $json) { if ($json["request_type"] == "app-integrations-change") { - print_r($json["payload"]); + var_dump($json["payload"]); break 3; } } @@ -68,19 +68,27 @@ namespace --EXPECT-- PUBLIC STATIC METHOD test_access hook -Array -( - [integrations] => Array - ( - [0] => Array - ( - [name] => ddtrace\test\testsandboxedintegration - [enabled] => 1 - ) - - ) - -) +array(1) { + ["integrations"]=> + array(2) { + [0]=> + array(2) { + ["name"]=> + string(37) "ddtrace\test\testsandboxedintegration" + ["enabled"]=> + bool(true) + } + [1]=> + array(3) { + ["name"]=> + string(4) "logs" + ["enabled"]=> + bool(false) + ["version"]=> + string(0) "" + } + } +} --CLEAN-- Date: Fri, 17 Nov 2023 16:18:16 +0100 Subject: [PATCH 05/16] Fix orphans removal when 128-bit is enabled (#2366) * fix: 128-bit friendly trace-id/span-id comparison when handling orphans * Add some regression tests * Add some regression tests * Typo * Strictly check for root span instance --- .../Laravel/LaravelIntegration.php | 3 +- .../PHPRedis/PHPRedisIntegration.php | 3 +- .../Integrations/Predis/PredisIntegration.php | 3 +- .../Integrations/PHPRedis/V5/PHPRedisTest.php | 31 +++++++++++++++++++ tests/Integrations/Predis/PredisTest.php | 29 +++++++++++++++++ 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/src/Integrations/Integrations/Laravel/LaravelIntegration.php b/src/Integrations/Integrations/Laravel/LaravelIntegration.php index 0fb79069e4a..50a3a8a43d4 100644 --- a/src/Integrations/Integrations/Laravel/LaravelIntegration.php +++ b/src/Integrations/Integrations/Laravel/LaravelIntegration.php @@ -3,6 +3,7 @@ namespace DDTrace\Integrations\Laravel; use DDTrace\Integrations\Lumen\LumenIntegration; +use DDTrace\RootSpanData; use DDTrace\SpanData; use DDTrace\Integrations\Integration; use DDTrace\Tag; @@ -57,7 +58,7 @@ public static function handleOrphan(SpanData $span) && ( \DDTrace\get_priority_sampling() == DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP || \DDTrace\get_priority_sampling() == DD_TRACE_PRIORITY_SAMPLING_USER_KEEP - ) && \DDTrace\trace_id() == $span->id + ) && $span instanceof RootSpanData && empty($span->parentId) ) { \DDTrace\set_priority_sampling(DD_TRACE_PRIORITY_SAMPLING_AUTO_REJECT); } diff --git a/src/Integrations/Integrations/PHPRedis/PHPRedisIntegration.php b/src/Integrations/Integrations/PHPRedis/PHPRedisIntegration.php index b8bb26d85bc..b5354b4ea4d 100644 --- a/src/Integrations/Integrations/PHPRedis/PHPRedisIntegration.php +++ b/src/Integrations/Integrations/PHPRedis/PHPRedisIntegration.php @@ -4,6 +4,7 @@ use DDTrace\Integrations\DatabaseIntegrationHelper; use DDTrace\Integrations\Integration; +use DDTrace\RootSpanData; use DDTrace\SpanData; use DDTrace\Tag; use DDTrace\Type; @@ -47,7 +48,7 @@ public static function handleOrphan(SpanData $span) && ( \DDTrace\get_priority_sampling() == DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP || \DDTrace\get_priority_sampling() == DD_TRACE_PRIORITY_SAMPLING_USER_KEEP - ) && \DDTrace\trace_id() == $span->id + ) && $span instanceof RootSpanData && empty($span->parentId) ) { \DDTrace\set_priority_sampling(DD_TRACE_PRIORITY_SAMPLING_AUTO_REJECT); } diff --git a/src/Integrations/Integrations/Predis/PredisIntegration.php b/src/Integrations/Integrations/Predis/PredisIntegration.php index 0da93bf8d1f..af29e620501 100644 --- a/src/Integrations/Integrations/Predis/PredisIntegration.php +++ b/src/Integrations/Integrations/Predis/PredisIntegration.php @@ -3,6 +3,7 @@ namespace DDTrace\Integrations\Predis; use DDTrace\Integrations\Integration; +use DDTrace\RootSpanData; use DDTrace\SpanData; use DDTrace\Tag; use DDTrace\Type; @@ -37,7 +38,7 @@ public static function handleOrphan(SpanData $span) && ( \DDTrace\get_priority_sampling() == DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP || \DDTrace\get_priority_sampling() == DD_TRACE_PRIORITY_SAMPLING_USER_KEEP - ) && \DDTrace\trace_id() == $span->id + ) && $span instanceof RootSpanData && empty($span->parentId) ) { \DDTrace\set_priority_sampling(DD_TRACE_PRIORITY_SAMPLING_AUTO_REJECT); } diff --git a/tests/Integrations/PHPRedis/V5/PHPRedisTest.php b/tests/Integrations/PHPRedis/V5/PHPRedisTest.php index 947ac1ccde2..f8e1d043be2 100644 --- a/tests/Integrations/PHPRedis/V5/PHPRedisTest.php +++ b/tests/Integrations/PHPRedis/V5/PHPRedisTest.php @@ -2453,4 +2453,35 @@ public function testNoFakeServices() Tag::COMPONENT => 'phpredis', Tag::DB_SYSTEM => 'redis', Tag::TARGET_HOST => $this->host]), ]); } + + public function testOrphansRemoval() + { + $this->putEnvAndReloadConfig([ + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS=1' + ]); + + $redis = $this->redis; + $traces = $this->isolateTracer(function () use ($redis) { + $redis->save(); + }); + + $span = $traces[0][0]; + $this->assertEquals(0, $span['metrics']['_sampling_priority_v1']); + } + + public function testOrphansRemoval64bit() + { + $this->putEnvAndReloadConfig([ + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS=1', + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=0' + ]); + + $redis = $this->redis; + $traces = $this->isolateTracer(function () use ($redis) { + $redis->save(); + }); + + $span = $traces[0][0]; + $this->assertEquals(0, $span['metrics']['_sampling_priority_v1']); + } } diff --git a/tests/Integrations/Predis/PredisTest.php b/tests/Integrations/Predis/PredisTest.php index 6ef15a5edcc..77c553dc297 100644 --- a/tests/Integrations/Predis/PredisTest.php +++ b/tests/Integrations/Predis/PredisTest.php @@ -338,6 +338,35 @@ public function testSplitByHostForErrorSpans() $this->assertSame('redis-non_existing', $traces[0][1]['service']); } + public function testOrphansRemoval() + { + $this->putEnvAndReloadConfig([ + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS=1' + ]); + + $traces = $this->isolateTracer(function () { + new \Predis\Client(["host" => $this->host]); + }); + + $span = $traces[0][0]; + $this->assertEquals(0, $span['metrics']['_sampling_priority_v1']); + } + + public function testOrphansRemoval64bit() + { + $this->putEnvAndReloadConfig([ + 'DD_TRACE_REMOVE_AUTOINSTRUMENTATION_ORPHANS=1', + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=0' + ]); + + $traces = $this->isolateTracer(function () { + new \Predis\Client(["host" => $this->host]); + }); + + $span = $traces[0][0]; + $this->assertEquals(0, $span['metrics']['_sampling_priority_v1']); + } + private function baseTags() { return [ From e5d7e68c3c6ff56c27f3e1bf2f18082dae3d058c Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Sat, 18 Nov 2023 11:22:26 +0100 Subject: [PATCH 06/16] feat(profiling): add idle phases to timeline (#2343) Co-authored-by: Levi Morrison --- profiling/src/lib.rs | 22 ++- profiling/src/profiling/mod.rs | 41 ++++ profiling/src/timeline.rs | 223 +++++++++++++++++++++- profiling/tests/correctness/timeline.json | 13 ++ 4 files changed, 291 insertions(+), 8 deletions(-) diff --git a/profiling/src/lib.rs b/profiling/src/lib.rs index d24e4efebb9..b373144c157 100644 --- a/profiling/src/lib.rs +++ b/profiling/src/lib.rs @@ -361,7 +361,8 @@ extern "C" fn prshutdown() -> ZendResult { */ unsafe { bindings::zai_config_rshutdown() }; - TAGS.with(|cell| cell.replace(Arc::default())); + #[cfg(feature = "timeline")] + timeline::timeline_prshutdown(); ZendResult::Success } @@ -634,11 +635,16 @@ extern "C" fn rinit(_type: c_int, _module_number: c_int) -> ZendResult { profiler.add_interrupt(interrupt); } }); + } else { + TAGS.with(|cell| cell.replace(Arc::default())); } #[cfg(feature = "allocation_profiling")] allocation::allocation_profiling_rinit(); + #[cfg(feature = "timeline")] + timeline::timeline_rinit(); + ZendResult::Success } @@ -905,6 +911,12 @@ extern "C" fn mshutdown(_type: c_int, _module_number: c_int) -> ZendResult { #[cfg(debug_assertions)] trace!("MSHUTDOWN({_type}, {_module_number})"); + #[cfg(feature = "timeline")] + timeline::timeline_mshutdown(); + + #[cfg(feature = "exception_profiling")] + exception::exception_profiling_mshutdown(); + unsafe { bindings::zai_config_mshutdown() }; let mut profiler = PROFILER.lock().unwrap(); @@ -912,9 +924,6 @@ extern "C" fn mshutdown(_type: c_int, _module_number: c_int) -> ZendResult { profiler.stop(Duration::from_secs(1)); } - #[cfg(feature = "exception_profiling")] - exception::exception_profiling_mshutdown(); - ZendResult::Success } @@ -949,7 +958,10 @@ extern "C" fn startup(extension: *mut ZendExtension) -> ZendResult { }; // Safety: calling this in zend_extension startup. - unsafe { pcntl::startup() }; + unsafe { + pcntl::startup(); + timeline::timeline_startup(); + } #[cfg(feature = "allocation_profiling")] allocation::allocation_profiling_startup(); diff --git a/profiling/src/profiling/mod.rs b/profiling/src/profiling/mod.rs index 15bab216f1f..4c20b2c30f8 100644 --- a/profiling/src/profiling/mod.rs +++ b/profiling/src/profiling/mod.rs @@ -873,6 +873,47 @@ impl Profiler { } } + #[cfg(feature = "timeline")] + /// This function can be called to collect any kind of inactivity that is happening + pub fn collect_idle( + &self, + now: i64, + duration: i64, + reason: &'static str, + locals: &RequestLocals, + ) { + let mut labels = Profiler::message_labels(); + + labels.push(Label { + key: "event", + value: LabelValue::Str(reason.into()), + }); + + let n_labels = labels.len(); + + match self.send_sample(Profiler::prepare_sample_message( + vec![ZendFrame { + function: "[idle]".into(), + file: None, + line: 0, + }], + SampleValues { + timeline: duration, + ..Default::default() + }, + labels, + locals, + now, + )) { + Ok(_) => { + trace!("Sent event 'idle' with {n_labels} labels to profiler.") + } + Err(err) => { + warn!("Failed to send event 'idle' with {n_labels} labels to profiler: {err}") + } + } + } + #[cfg(feature = "timeline")] /// collect a stack frame for garbage collection. /// as we do not know about the overhead currently, we only collect a fake frame. diff --git a/profiling/src/timeline.rs b/profiling/src/timeline.rs index 4608cf12e51..27e39cc71da 100644 --- a/profiling/src/timeline.rs +++ b/profiling/src/timeline.rs @@ -1,8 +1,12 @@ -use crate::bindings as zend; -use crate::zend::{zai_str_from_zstr, zend_get_executed_filename_ex}; +use crate::zend::{ + self, zai_str_from_zstr, zend_execute_data, zend_get_executed_filename_ex, zval, + InternalFunctionHandler, +}; use crate::{PROFILER, REQUEST_LOCALS}; use libc::c_char; -use log::{error, trace}; +use log::{error, trace, warn}; +use std::cell::RefCell; +use std::ffi::CStr; use std::mem::MaybeUninit; use std::ptr; use std::time::Instant; @@ -18,6 +22,112 @@ static mut PREV_ZEND_COMPILE_STRING: Option = None; /// The engine's original (or neighbouring extensions) `zend_compile_file()` function static mut PREV_ZEND_COMPILE_FILE: Option = None; +static mut SLEEP_HANDLER: InternalFunctionHandler = None; +static mut USLEEP_HANDLER: InternalFunctionHandler = None; +static mut TIME_NANOSLEEP_HANDLER: InternalFunctionHandler = None; +static mut TIME_SLEEP_UNTIL_HANDLER: InternalFunctionHandler = None; + +thread_local! { + static IDLE_SINCE: RefCell = RefCell::new(Instant::now()); +} + +#[inline] +fn try_sleeping_fn( + func: unsafe extern "C" fn(execute_data: *mut zend_execute_data, return_value: *mut zval), + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) -> anyhow::Result<()> { + let start = Instant::now(); + + // SAFETY: simple forwarding to original func with original args. + unsafe { func(execute_data, return_value) }; + + let duration = start.elapsed(); + + // > Returns an Err if earlier is later than self, and the error contains + // > how far from self the time is. + // This shouldn't ever happen (now is always later than the epoch) but in + // case it does, short-circuit the function. + let now = SystemTime::now().duration_since(UNIX_EPOCH)?; + + // Consciously not holding request locals/profiler during the forwarded + // call. If they are, then it's possible to get a deadlock/bad borrow + // because the call triggers something to happen like a time/allocation + // sample and the extension tries to re-acquire these. + REQUEST_LOCALS.with(|cell| { + // try to borrow and bail out if not successful + let locals = cell.try_borrow()?; + + match PROFILER.lock() { + Ok(guard) => match guard.as_ref() { + Some(profiler) => profiler.collect_idle( + now.as_nanos() as i64, + duration.as_nanos() as i64, + "sleeping", + &locals, + ), + None => { /* Profiling is probably disabled, no worries */ } + }, + Err(err) => anyhow::bail!("profiler mutex: {err:#}"), + } + Ok(()) + }) +} + +fn sleeping_fn( + func: unsafe extern "C" fn(execute_data: *mut zend_execute_data, return_value: *mut zval), + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + if let Err(err) = try_sleeping_fn(func, execute_data, return_value) { + warn!("error creating profiling timeline sample for an internal function: {err:#}"); + } +} + +/// Wrapping the PHP `sleep()` function to take the time it is blocking the current thread +#[no_mangle] +unsafe extern "C" fn ddog_php_prof_sleep( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + if let Some(func) = SLEEP_HANDLER { + sleeping_fn(func, execute_data, return_value) + } +} + +/// Wrapping the PHP `usleep()` function to take the time it is blocking the current thread +#[no_mangle] +unsafe extern "C" fn ddog_php_prof_usleep( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + if let Some(func) = USLEEP_HANDLER { + sleeping_fn(func, execute_data, return_value) + } +} + +/// Wrapping the PHP `time_nanosleep()` function to take the time it is blocking the current thread +#[no_mangle] +unsafe extern "C" fn ddog_php_prof_time_nanosleep( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + if let Some(func) = TIME_NANOSLEEP_HANDLER { + sleeping_fn(func, execute_data, return_value) + } +} + +/// Wrapping the PHP `time_sleep_until()` function to take the time it is blocking the current thread +#[no_mangle] +unsafe extern "C" fn ddog_php_prof_time_sleep_until( + execute_data: *mut zend_execute_data, + return_value: *mut zval, +) { + if let Some(func) = TIME_SLEEP_UNTIL_HANDLER { + sleeping_fn(func, execute_data, return_value) + } +} + /// This functions needs to be called in MINIT of the module pub fn timeline_minit() { unsafe { @@ -35,6 +145,113 @@ pub fn timeline_minit() { } } +/// This function is run during the STARTUP phase and hooks into the execution of some functions +/// that we'd like to observe in regards of visualization on the timeline +pub unsafe fn timeline_startup() { + let handlers = [ + zend::datadog_php_zif_handler::new( + CStr::from_bytes_with_nul_unchecked(b"sleep\0"), + &mut SLEEP_HANDLER, + Some(ddog_php_prof_sleep), + ), + zend::datadog_php_zif_handler::new( + CStr::from_bytes_with_nul_unchecked(b"usleep\0"), + &mut USLEEP_HANDLER, + Some(ddog_php_prof_usleep), + ), + zend::datadog_php_zif_handler::new( + CStr::from_bytes_with_nul_unchecked(b"time_nanosleep\0"), + &mut TIME_NANOSLEEP_HANDLER, + Some(ddog_php_prof_time_nanosleep), + ), + zend::datadog_php_zif_handler::new( + CStr::from_bytes_with_nul_unchecked(b"time_sleep_until\0"), + &mut TIME_SLEEP_UNTIL_HANDLER, + Some(ddog_php_prof_time_sleep_until), + ), + ]; + + for handler in handlers.into_iter() { + // Safety: we've set all the parameters correctly for this C call. + zend::datadog_php_install_handler(handler); + } +} + +/// This function is run during the RINIT phase and reports any `IDLE_SINCE` duration as an idle +/// period for this PHP thread +pub fn timeline_rinit() { + REQUEST_LOCALS.with(|cell| { + // try to borrow and bail out if not successful + let Ok(locals) = cell.try_borrow() else { + return; + }; + + IDLE_SINCE.with(|cell| { + // try to borrow and bail out if not successful + let Ok(idle_since) = cell.try_borrow() else { + return; + }; + + if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + profiler.collect_idle( + // Safety: checked for `is_err()` above + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() as i64, + idle_since.elapsed().as_nanos() as i64, + "idle", + &locals, + ); + } + }); + }); +} + +/// This function is run during the P-RSHUTDOWN phase and resets the `IDLE_SINCE` thread local to +/// "now", indicating the start of a new idle phase +pub fn timeline_prshutdown() { + IDLE_SINCE.with(|cell| { + // try to borrow and bail out if not successful + let Ok(mut idle_since) = cell.try_borrow_mut() else { + return; + }; + *idle_since = Instant::now(); + }) +} + +/// This function is run during the MSHUTDOWN phase and reports any `IDLE_SINCE` duration as an idle +/// period for this PHP thread. This will report the last `IDLE_SINCE` duration created in the last +/// `P-RSHUTDOWN` (just above) when the PHP process is shutting down. +pub(crate) fn timeline_mshutdown() { + REQUEST_LOCALS.with(|cell| { + // try to borrow and bail out if not successful + let Ok(locals) = cell.try_borrow() else { + return; + }; + + IDLE_SINCE.with(|cell| { + // try to borrow and bail out if not successful + let Ok(idle_since) = cell.try_borrow() else { + return; + }; + + if let Some(profiler) = PROFILER.lock().unwrap().as_ref() { + profiler.collect_idle( + // Safety: checked for `is_err()` above + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() as i64, + idle_since.elapsed().as_nanos() as i64, + "idle", + &locals, + ); + } + }); + }); +} + /// This function gets called when a `eval()` is being called. This is done by letting the /// `zend_compile_string` function pointer point to this function. /// When called, we call the previous function and measure the wall-time it took to compile the diff --git a/profiling/tests/correctness/timeline.json b/profiling/tests/correctness/timeline.json index 6da31eca074..a45aa823b5e 100644 --- a/profiling/tests/correctness/timeline.json +++ b/profiling/tests/correctness/timeline.json @@ -61,6 +61,19 @@ ] } ] + }, + { + "regular_expression": "^\\[idle\\]$", + "percent": 100, + "error_margin": 100, + "labels": [ + { + "key": "event", + "values": [ + "sleeping" + ] + } + ] } ] } From 86214fface78eda5f051d29aaddf8badecf12de9 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Sun, 19 Nov 2023 14:53:43 +0100 Subject: [PATCH 07/16] Add xfail for Zend/tests/stack_limit/stack_limit_013.phpt on PHP 8.3 (#2371) Signed-off-by: Bob Weinand --- dockerfiles/ci/xfail_tests/8.3.list | 1 + dockerfiles/ci/xfail_tests/README.md | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/dockerfiles/ci/xfail_tests/8.3.list b/dockerfiles/ci/xfail_tests/8.3.list index a1d345fb1bc..52ae22a870f 100644 --- a/dockerfiles/ci/xfail_tests/8.3.list +++ b/dockerfiles/ci/xfail_tests/8.3.list @@ -30,6 +30,7 @@ Zend/tests/list_keyed_conversions.phpt Zend/tests/multibyte/multibyte_encoding_004.phpt Zend/tests/object_gc_in_shutdown.phpt Zend/tests/offset_array.phpt +Zend/tests/stack_limit/stack_limit_013.phpt Zend/tests/strict_001.phpt Zend/tests/type_declarations/scalar_return_basic_64bit.phpt Zend/tests/type_declarations/scalar_strict_64bit.phpt diff --git a/dockerfiles/ci/xfail_tests/README.md b/dockerfiles/ci/xfail_tests/README.md index f37597fc8a2..29eb2db2a5a 100644 --- a/dockerfiles/ci/xfail_tests/README.md +++ b/dockerfiles/ci/xfail_tests/README.md @@ -157,6 +157,10 @@ ddtrace request init hook consumes more than 2 MB of memory and fails too early ddtrace affects the order of destructor execution due to creating span stacks etc. +## `Zend/tests/stack_limit/stack_limit_013.phpt` + +This particular test is very close to the stack limit, and thus sometimes fails to actually exceed the stack limit with ddtrace. + ## `ext/zend_test/tests/`, `Zend/tests/gh10346.phpt` Observer tests trace all functions, including dd setup. Exclude these from being observed. From 774224e44b56ac67e3d4e9278589960eead93e4d Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Sun, 19 Nov 2023 15:11:16 +0100 Subject: [PATCH 08/16] Use _dd.agent_psr instead of _dd.rule_psr for agent sampling (#2370) Signed-off-by: Bob Weinand --- .../client_init_record_span_tags.phpt | 4 +- appsec/tests/extension/ddtrace_basic.phpt | 2 +- .../extension/rinit_record_span_tags.phpt | 4 +- .../rinit_record_span_tags_fail.phpt | 2 +- .../extension/rinit_root_span_add_tag.phpt | 2 +- appsec/tests/extension/root_span_add_tag.phpt | 4 +- ..._span_add_tag_with_intermediate_spans.phpt | 4 +- ext/ddtrace.c | 10 +- ext/priority_sampling/priority_sampling.c | 20 +- tests/Integrations/Memcache/MemcacheTest.php | 4 +- .../Integrations/Memcached/MemcachedTest.php | 18 +- tests/Integrations/Mysqli/MysqliTest.php | 16 +- tests/Integrations/PDO/PDOTest.php | 14 +- tests/Integrations/SQLSRV/SQLSRVTest.php | 20 +- .../distributed_trace_bogus_ids.phpt | 4 +- ...tributed_trace_span_link_from_headers.phpt | 4 +- tests/ext/extract_server_values.phpt | 2 +- tests/ext/fibers/fiber_observer_bailout.phpt | 2 +- tests/ext/fibers/fiber_stack_switch.phpt | 2 +- ...distributed_tracing_curl_invalid_tags.phpt | 4 +- ...ed_tracing_curl_propagate_custom_tags.phpt | 2 +- .../001-default-sampling.phpt | 10 +- .../006-rule-name-reject.phpt | 4 +- .../008-rule-service-reject.phpt | 4 +- .../priority_sampling/invalid-regex-rule.phpt | 2 +- .../ext/root_span_url_as_resource_names.phpt | 2 +- ...ot_span_url_as_resource_names_no_host.phpt | 2 +- .../ext/root_span_url_with_post_no_param.phpt | 2 +- .../root_span_url_with_post_no_param_set.phpt | 2 +- .../ext/sandbox-prehook/dd_trace_method.phpt | 8 +- .../nested_dropped_spans.phpt | 2 +- .../ext/sandbox/dd_trace_function_alias.phpt | 2 +- .../sandbox/dd_trace_function_complex.phpt | 12 +- .../sandbox/dd_trace_function_internal.phpt | 4 +- tests/ext/sandbox/dd_trace_method.phpt | 8 +- tests/ext/sandbox/dd_trace_method_alias.phpt | 2 +- .../ext/sandbox/default_span_properties.phpt | 2 +- .../default_span_properties_method.phpt | 2 +- .../errors_are_flagged_from_userland.phpt | 4 +- .../install_hook/hook_scoped_file.phpt | 2 +- .../ext/sandbox/install_hook/trace_file.phpt | 4 +- tests/ext/sandbox/span_clone.phpt | 4 +- tests/ext/span_stack/span_stack_clone.phpt | 10 +- tests/ext/span_stack/span_stack_swap.phpt | 4 +- .../span_stack_swap_traced_function.phpt | 2 +- .../span_trace_stack_autoclose.phpt | 2 +- tests/ext/span_stack/span_trace_swap.phpt | 4 +- .../ext/span_stack/start_span_new_trace.phpt | 4 +- tests/ext/span_stack/start_span_stack.phpt | 2 +- .../start_top_level_span_stack.phpt | 2 +- tests/ext/span_with_removed_exception.phpt | 12 +- tests/ext/start_span_with_all_properties.phpt | 8 +- tests/ext/start_span_without_closing.phpt | 4 +- tests/ext/traced_attribute.phpt | 6 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ....v1_9.rest_test.test_scenario_rest2xx.json | 2 +- ....v1_9.rest_test.test_scenario_rest4xx.json | 2 +- ....v1_9.rest_test.test_scenario_rest5xx.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...acks_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...gacy_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...acks_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...gacy_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...acks_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...gacy_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...st.test_scenario_get_to_missing_route.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...acks_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...gacy_test.test_scenario_get_with_view.json | 2 +- ..._test.test_scenario_get_return_string.json | 2 +- ...test.test_scenario_get_with_exception.json | 2 +- ...rios_test.test_scenario_get_with_view.json | 2 +- zend_abstract_interface/config/config.c | 6 +- .../config/config_decode.c | 90 +------ .../config/config_decode.h | 1 - zend_abstract_interface/config/config_ini.c | 9 +- zend_abstract_interface/json/json.c | 231 +++++++++++++++++- zend_abstract_interface/json/json.h | 23 +- zend_abstract_interface/json/tests/json.cc | 2 +- 139 files changed, 469 insertions(+), 337 deletions(-) diff --git a/appsec/tests/extension/client_init_record_span_tags.phpt b/appsec/tests/extension/client_init_record_span_tags.phpt index c6313ee5acb..51a9a71decf 100644 --- a/appsec/tests/extension/client_init_record_span_tags.phpt +++ b/appsec/tests/extension/client_init_record_span_tags.phpt @@ -85,7 +85,7 @@ tags: Array ( [_dd.appsec.json] => {"triggers":[{"found":"attack"},{"another":"attack"},{"yet another":"attack"}]} - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 [_dd.p.tid] => %s [_dd.runtime_family] => php [appsec.event] => true @@ -106,7 +106,7 @@ Array [metric_1] => 2 [metric_2] => 10 [_dd.appsec.enabled] => 1 - [_dd.rule_psr] => 1 + [_dd.agent_psr] => 1 [_sampling_priority_v1] => 1 [php.compilation.total_time_ms] => %f ) diff --git a/appsec/tests/extension/ddtrace_basic.phpt b/appsec/tests/extension/ddtrace_basic.phpt index 911b8245e43..9e6940cd36f 100644 --- a/appsec/tests/extension/ddtrace_basic.phpt +++ b/appsec/tests/extension/ddtrace_basic.phpt @@ -103,6 +103,6 @@ Array ( [runtime-id] => %s [ddappsec] => true - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 [_dd.p.tid] => %s ) diff --git a/appsec/tests/extension/rinit_record_span_tags.phpt b/appsec/tests/extension/rinit_record_span_tags.phpt index ad86473322d..569bcd68faa 100644 --- a/appsec/tests/extension/rinit_record_span_tags.phpt +++ b/appsec/tests/extension/rinit_record_span_tags.phpt @@ -80,7 +80,7 @@ tags: Array ( [_dd.appsec.json] => {"triggers":[{"found":"attack"},{"another":"attack"},{"yet another":"attack"}]} - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 [_dd.p.tid] => %s [_dd.runtime_family] => php [appsec.event] => true @@ -99,7 +99,7 @@ Array [%s] => %d [rshutdown_metric] => 2.1 [_dd.appsec.enabled] => 1 - [_dd.rule_psr] => 1 + [_dd.agent_psr] => 1 [_sampling_priority_v1] => 1 [php.compilation.total_time_ms] => %f ) diff --git a/appsec/tests/extension/rinit_record_span_tags_fail.phpt b/appsec/tests/extension/rinit_record_span_tags_fail.phpt index 2f03eb5039c..dbf7b9d7be3 100644 --- a/appsec/tests/extension/rinit_record_span_tags_fail.phpt +++ b/appsec/tests/extension/rinit_record_span_tags_fail.phpt @@ -53,6 +53,6 @@ tags: Array ( [runtime-id] => %s - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 [_dd.p.tid] => %s ) diff --git a/appsec/tests/extension/rinit_root_span_add_tag.phpt b/appsec/tests/extension/rinit_root_span_add_tag.phpt index 015e84c4925..84137655cfd 100644 --- a/appsec/tests/extension/rinit_root_span_add_tag.phpt +++ b/appsec/tests/extension/rinit_root_span_add_tag.phpt @@ -59,7 +59,7 @@ bool(true) tags: Array ( - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 [_dd.p.tid] => %s [ddappsec] => true [env] => staging diff --git a/appsec/tests/extension/root_span_add_tag.phpt b/appsec/tests/extension/root_span_add_tag.phpt index 6d1d075914c..fbb7086f030 100644 --- a/appsec/tests/extension/root_span_add_tag.phpt +++ b/appsec/tests/extension/root_span_add_tag.phpt @@ -51,7 +51,7 @@ array(1) { ["after"]=> string(9) "root_span" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -59,7 +59,7 @@ array(1) { array(4) { [%s"]=> float(%d) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) 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 e18b55a0739..8815b943366 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 @@ -69,7 +69,7 @@ int(2) tags: Array ( - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 [_dd.p.tid] => %s [_dd.runtime_family] => php [after] => root_span @@ -81,7 +81,7 @@ Array ( [process_id] => %d [_dd.appsec.enabled] => 1 - [_dd.rule_psr] => 1 + [_dd.agent_psr] => 1 [_sampling_priority_v1] => 1 [php.compilation.total_time_ms] => %s ) diff --git a/ext/ddtrace.c b/ext/ddtrace.c index bb6fc47828f..a855615ba79 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -881,6 +881,11 @@ static PHP_MSHUTDOWN_FUNCTION(ddtrace) { return SUCCESS; } + if (DDTRACE_G(agent_rate_by_service)) { + zai_json_release_persistent_array(DDTRACE_G(agent_rate_by_service)); + DDTRACE_G(agent_rate_by_service) = NULL; + } + ddtrace_integrations_mshutdown(); ddtrace_signals_mshutdown(); @@ -1092,11 +1097,6 @@ static PHP_RSHUTDOWN_FUNCTION(ddtrace) { dd_shutdown_hooks_and_observer(); } - if (DDTRACE_G(agent_rate_by_service)) { - zend_array_release(DDTRACE_G(agent_rate_by_service)); - DDTRACE_G(agent_rate_by_service) = NULL; - } - if (!DDTRACE_G(disable)) { OBJ_RELEASE(&DDTRACE_G(active_stack)->std); DDTRACE_G(active_stack) = NULL; diff --git a/ext/priority_sampling/priority_sampling.c b/ext/priority_sampling/priority_sampling.c index fe5d0c1df0a..e91e2ef0113 100644 --- a/ext/priority_sampling/priority_sampling.c +++ b/ext/priority_sampling/priority_sampling.c @@ -16,20 +16,19 @@ void ddtrace_try_read_agent_rate(void) { ddog_CharSlice data; if (DDTRACE_G(remote_config_reader) && ddog_agent_remote_config_read(DDTRACE_G(remote_config_reader), &data)) { zval json; - if ((int)data.len > 0) { - zai_json_decode_assoc(&json, data.ptr, (int)data.len, 3); + if ((int)data.len > 0 && zai_json_decode_assoc_safe(&json, data.ptr, (int)data.len, 3, true) == SUCCESS) { if (Z_TYPE(json) == IS_ARRAY) { zval *rules = zend_hash_str_find(Z_ARR(json), ZEND_STRL("rate_by_service")); if (rules && Z_TYPE_P(rules) == IS_ARRAY) { if (DDTRACE_G(agent_rate_by_service)) { - zend_array_release(DDTRACE_G(agent_rate_by_service)); + zai_json_release_persistent_array(DDTRACE_G(agent_rate_by_service)); } Z_TRY_ADDREF_P(rules); DDTRACE_G(agent_rate_by_service) = Z_ARR_P(rules); } } - zval_ptr_dtor(&json); + zai_json_dtor_pzval(&json); } } } @@ -130,18 +129,19 @@ static void dd_decide_on_sampling(ddtrace_root_span_data *span) { bool sampling = (double)genrand64_int64() < sample_rate * (double)~0ULL; bool limited = ddtrace_limiter_active() && (sampling && !ddtrace_limiter_allow()); + zval sample_rate_zv; + ZVAL_DOUBLE(&sample_rate_zv, sample_rate); + if (explicit_rule) { mechanism = DD_MECHANISM_RULE; priority = sampling && !limited ? PRIORITY_SAMPLING_USER_KEEP : PRIORITY_SAMPLING_USER_REJECT; + zend_hash_str_update(ddtrace_property_array(&span->property_metrics), ZEND_STRL("_dd.rule_psr"), &sample_rate_zv); } else { - mechanism = DD_MECHANISM_AGENT_RATE; + mechanism = DDTRACE_G(agent_rate_by_service) ? DD_MECHANISM_AGENT_RATE : DD_MECHANISM_DEFAULT; priority = sampling && !limited ? PRIORITY_SAMPLING_AUTO_KEEP : PRIORITY_SAMPLING_AUTO_REJECT; - } - zval sample_rate_zv; - ZVAL_DOUBLE(&sample_rate_zv, sample_rate); - zend_hash_str_update(ddtrace_property_array(&span->property_metrics), ZEND_STRL("_dd.rule_psr"), - &sample_rate_zv); + zend_hash_str_update(ddtrace_property_array(&span->property_metrics), ZEND_STRL("_dd.agent_psr"), &sample_rate_zv); + } if (limited) { zval limit_zv; diff --git a/tests/Integrations/Memcache/MemcacheTest.php b/tests/Integrations/Memcache/MemcacheTest.php index 54561df19ec..6115b9e3256 100644 --- a/tests/Integrations/Memcache/MemcacheTest.php +++ b/tests/Integrations/Memcache/MemcacheTest.php @@ -183,7 +183,7 @@ public function testGet() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -206,7 +206,7 @@ public function testGetMissingKey() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); diff --git a/tests/Integrations/Memcached/MemcachedTest.php b/tests/Integrations/Memcached/MemcachedTest.php index af3ef00141c..0032fe9647a 100644 --- a/tests/Integrations/Memcached/MemcachedTest.php +++ b/tests/Integrations/Memcached/MemcachedTest.php @@ -484,7 +484,7 @@ public function testGet() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -507,7 +507,7 @@ public function testGetMissingKey() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -530,7 +530,7 @@ public function testGetMulti() 'memcached.command' => 'getMulti', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 2, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -553,7 +553,7 @@ public function testGetMultiNotAllExist() 'memcached.command' => 'getMulti', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -571,7 +571,7 @@ public function testGetMultiNoneExist() 'memcached.command' => 'getMulti', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -594,7 +594,7 @@ public function testGetByKey() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -622,7 +622,7 @@ public function testGetMultiByKey() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 2, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -894,7 +894,7 @@ public function testMultiPeerServiceEnabled() 'memcached.command' => 'getMulti', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 2, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -924,7 +924,7 @@ public function testMultiByKeyPeerServiceEnabled() Tag::SPAN_KIND => 'client', ]))->withExactMetrics([ Tag::DB_ROW_COUNT => 2, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); diff --git a/tests/Integrations/Mysqli/MysqliTest.php b/tests/Integrations/Mysqli/MysqliTest.php index 4d5a4bb3e68..8faa53b58fd 100644 --- a/tests/Integrations/Mysqli/MysqliTest.php +++ b/tests/Integrations/Mysqli/MysqliTest.php @@ -122,7 +122,7 @@ public function testProceduralQuery() ->withExactTags(self::baseTags(true, false)) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -144,7 +144,7 @@ public function testProceduralQueryPeerServiceEnabled() ->withExactTags(self::baseTags(true, true)) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -210,7 +210,7 @@ public function testProceduralQueryRealConnect() ->withExactTags(self::baseTags()) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -236,7 +236,7 @@ public function testProceduralQueryRealConnectPeerServiceEnabled() ->withExactTags(self::baseTags(true, true)) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -257,7 +257,7 @@ public function testConstructorQuery() ->withExactTags(self::baseTags()) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -280,7 +280,7 @@ public function testConstructorQueryPeerServiceEnabled() ->withExactTags(self::baseTags(true, true)) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -305,7 +305,7 @@ public function testEmptyConstructorQuery() ->withExactTags(self::baseTags()) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -332,7 +332,7 @@ public function testEmptyConstructorQueryPeerServiceEnabled() ->withExactTags(self::baseTags(true, true)) ->withExactMetrics([ Tag::DB_ROW_COUNT => 1, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); diff --git a/tests/Integrations/PDO/PDOTest.php b/tests/Integrations/PDO/PDOTest.php index 841c3087ff9..773c98a0ea5 100644 --- a/tests/Integrations/PDO/PDOTest.php +++ b/tests/Integrations/PDO/PDOTest.php @@ -84,7 +84,7 @@ public function testCustomPDOPrepareWithStringableStatement() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -201,7 +201,7 @@ public function testPDOExecOk() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), SpanAssertion::exists('PDO.commit'), @@ -271,7 +271,7 @@ public function testPDOQuery() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -295,7 +295,7 @@ public function testPDOQueryPeerServiceEnabled() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -416,7 +416,7 @@ public function testPDOStatementOk() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -456,7 +456,7 @@ public function testPDOStatementOkPeerServiceEnabled() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); @@ -495,7 +495,7 @@ public function testPDOStatementSplitByDomain() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), ]); diff --git a/tests/Integrations/SQLSRV/SQLSRVTest.php b/tests/Integrations/SQLSRV/SQLSRVTest.php index 352f5522975..4ee3fb944d2 100644 --- a/tests/Integrations/SQLSRV/SQLSRVTest.php +++ b/tests/Integrations/SQLSRV/SQLSRVTest.php @@ -106,7 +106,7 @@ public function testQueryOk() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -131,7 +131,7 @@ public function testQueryOkPeerServiceEnabled() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -157,7 +157,7 @@ public function testQueryError() self::getArchitecture() === 'x86_64' ? SQLSRVTest::ERROR_QUERY_17 : SQLSRVTest::ERROR_QUERY_18 )->withExactMetrics([ Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -185,7 +185,7 @@ public function testQueryErrorPeerServiceEnabled() self::getArchitecture() === 'x86_64' ? SQLSRVTest::ERROR_QUERY_17 : SQLSRVTest::ERROR_QUERY_18 )->withExactMetrics([ Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -211,7 +211,7 @@ public function testCommitOk() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]), SpanAssertion::build('sqlsrv_commit', 'sqlsrv', 'sql', 'sqlsrv_commit') @@ -239,7 +239,7 @@ public function testPrepareOk() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -267,7 +267,7 @@ public function testPrepareOkPeerServiceEnabled() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -294,7 +294,7 @@ public function testPrepareError() )->withExactTags(self::baseTags($query)) ->withExactMetrics([ Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -323,7 +323,7 @@ public function testPrepareErrorPeerServiceEnabled() )->withExactTags(self::baseTags($query, true)) ->withExactMetrics([ Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); @@ -447,7 +447,7 @@ public function testNoFakeServices() ->withExactMetrics([ Tag::DB_ROW_COUNT => 1.0, Tag::ANALYTICS_KEY => 1.0, - '_dd.rule_psr' => 1.0, + '_dd.agent_psr' => 1.0, '_sampling_priority_v1' => 1.0, ]) ]); diff --git a/tests/ext/distributed_tracing/distributed_trace_bogus_ids.phpt b/tests/ext/distributed_tracing/distributed_trace_bogus_ids.phpt index be061eb7cf3..7621e5e1547 100644 --- a/tests/ext/distributed_tracing/distributed_trace_bogus_ids.phpt +++ b/tests/ext/distributed_tracing/distributed_trace_bogus_ids.phpt @@ -40,7 +40,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.origin"]=> string(7) "datadog" ["_dd.p.tid"]=> @@ -50,7 +50,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/distributed_tracing/distributed_trace_span_link_from_headers.phpt b/tests/ext/distributed_tracing/distributed_trace_span_link_from_headers.phpt index ca2519f608a..0efc39bf45b 100644 --- a/tests/ext/distributed_tracing/distributed_trace_span_link_from_headers.phpt +++ b/tests/ext/distributed_tracing/distributed_trace_span_link_from_headers.phpt @@ -16,12 +16,12 @@ DDTrace\SpanLink Object ( [traceId] => 0000000000000000000000000000002a [spanId] => %s - [traceState] => dd=o:datadog;t.custom_tag:inherited;t.second_tag:bar;t.dm:-1 + [traceState] => dd=o:datadog;t.custom_tag:inherited;t.second_tag:bar;t.dm:-0 [attributes] => Array ( [_dd.p.custom_tag] => inherited [_dd.p.second_tag] => bar - [_dd.p.dm] => -1 + [_dd.p.dm] => -0 ) ) diff --git a/tests/ext/extract_server_values.phpt b/tests/ext/extract_server_values.phpt index 3478704d0bb..6949d0b7621 100644 --- a/tests/ext/extract_server_values.phpt +++ b/tests/ext/extract_server_values.phpt @@ -28,7 +28,7 @@ array(4) { ["http.request.headers.0"]=> string(16) "http_zero_header" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } diff --git a/tests/ext/fibers/fiber_observer_bailout.phpt b/tests/ext/fibers/fiber_observer_bailout.phpt index 393b778506a..45ade74c5fe 100644 --- a/tests/ext/fibers/fiber_observer_bailout.phpt +++ b/tests/ext/fibers/fiber_observer_bailout.phpt @@ -50,7 +50,7 @@ spans(\DDTrace\SpanData) (1) { #2 %s(%d): Fiber->resume() #3 %s(%d): outer() #4 {main} - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s inFiber (fiber_observer_bailout.php, inFiber, cli) (error: Allowed memory size of %d bytes exhausted %s) error.type => E_ERROR diff --git a/tests/ext/fibers/fiber_stack_switch.phpt b/tests/ext/fibers/fiber_stack_switch.phpt index 0036bbd2e7d..1229a5e6c9d 100644 --- a/tests/ext/fibers/fiber_stack_switch.phpt +++ b/tests/ext/fibers/fiber_stack_switch.phpt @@ -77,7 +77,7 @@ Hook: Fiber->resume Caught ex spans(\DDTrace\SpanData) (1) { fiber_stack_switch.php (fiber_stack_switch.php, fiber_stack_switch.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s Fiber.start (fiber_stack_switch.php, Fiber.start, cli) inFiber (fiber_stack_switch.php, inFiber, cli) diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_invalid_tags.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_invalid_tags.phpt index a647fbe1402..5e5524a1055 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_invalid_tags.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_invalid_tags.phpt @@ -44,9 +44,9 @@ echo 'Done.' . PHP_EOL; --EXPECTF-- b3: %s248869c998246a2e-248869c998246a2e-1 traceparent: 00-%s248869c998246a2e-248869c998246a2e-01 -tracestate: dd=o:_______~_: ;t.tid:%s;t.escaped:_~_: ;t.dm:-1 +tracestate: dd=o:_______~_: ;t.tid:%s;t.escaped:_~_: ;t.dm:-0 x-datadog-origin: ∂~,=;: -x-datadog-tags: _dd.p.tid=%s,_dd.p.escaped=_=;: ,_dd.p.dm=-1 +x-datadog-tags: _dd.p.tid=%s,_dd.p.escaped=_=;: ,_dd.p.dm=-0 bool(false) Done. No finished traces to be sent to the agent diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_propagate_custom_tags.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_propagate_custom_tags.phpt index 0c6ef79fe44..fda031a87d8 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_propagate_custom_tags.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_propagate_custom_tags.phpt @@ -30,4 +30,4 @@ dt_dump_headers_from_httpbin(query_headers(), ['x-datadog-tags']); ?> --EXPECTF-- string(4) "1234" -x-datadog-tags: _dd.p.tid=%s,_dd.p.usr.id=1234,_dd.p.dm=-1 +x-datadog-tags: _dd.p.tid=%s,_dd.p.usr.id=1234,_dd.p.dm=-0 diff --git a/tests/ext/priority_sampling/001-default-sampling.phpt b/tests/ext/priority_sampling/001-default-sampling.phpt index 42baf8cd392..e9a0133f4aa 100644 --- a/tests/ext/priority_sampling/001-default-sampling.phpt +++ b/tests/ext/priority_sampling/001-default-sampling.phpt @@ -12,8 +12,8 @@ if (\DDTrace\get_priority_sampling() == \DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP) { if ($root->samplingPriority == \DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP) { echo "metrics[_sampling_priority_v1] OK\n"; - if ($root->metrics["_dd.rule_psr"] === 1.0) { - echo "metrics[_dd.rule_psr] OK\n"; + if ($root->metrics["_dd.agent_psr"] === 1.0) { + echo "metrics[_dd.agent_psr] OK\n"; if (\DDTrace\get_priority_sampling() == \DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP) { echo "\DDTrace\get_priority_sampling() OK\n"; @@ -21,7 +21,7 @@ if (\DDTrace\get_priority_sampling() == \DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP) { echo "Default priority sampling changed\n"; } } else { - echo "_dd.rule_psr is missing from root span metrics\n"; + echo "_dd.agent_psr is missing from root span metrics\n"; } } else { echo "_sampling_priority_v1 metric is missing from root span metrics\n"; @@ -34,6 +34,6 @@ if (\DDTrace\get_priority_sampling() == \DD_TRACE_PRIORITY_SAMPLING_AUTO_KEEP) { --EXPECT-- \DDTrace\get_priority_sampling() OK metrics[_sampling_priority_v1] OK -metrics[_dd.rule_psr] OK +metrics[_dd.agent_psr] OK \DDTrace\get_priority_sampling() OK -_dd.p.dm = -1 +_dd.p.dm = -0 diff --git a/tests/ext/priority_sampling/006-rule-name-reject.phpt b/tests/ext/priority_sampling/006-rule-name-reject.phpt index f0e513da149..8aa14420d83 100644 --- a/tests/ext/priority_sampling/006-rule-name-reject.phpt +++ b/tests/ext/priority_sampling/006-rule-name-reject.phpt @@ -10,7 +10,7 @@ $root->name = "fooname"; \DDTrace\get_priority_sampling(); -if ($root->metrics["_dd.rule_psr"] != 0.3) { +if ($root->metrics["_dd.rule_psr"] ?? -1 != 0.3 && $root->metrics["_dd.agent_psr"] == 1) { echo "Rule OK\n"; } else { var_dump($root->metrics); @@ -19,4 +19,4 @@ echo "_dd.p.dm = {$root->meta["_dd.p.dm"]}\n"; ?> --EXPECT-- Rule OK -_dd.p.dm = -1 +_dd.p.dm = -0 diff --git a/tests/ext/priority_sampling/008-rule-service-reject.phpt b/tests/ext/priority_sampling/008-rule-service-reject.phpt index 0fd532ad466..8b50a0b7b5a 100644 --- a/tests/ext/priority_sampling/008-rule-service-reject.phpt +++ b/tests/ext/priority_sampling/008-rule-service-reject.phpt @@ -16,7 +16,7 @@ $root->service = "barservice"; \DDTrace\get_priority_sampling(); -if ($root->metrics["_dd.rule_psr"] != 0.3) { +if (($root->metrics["_dd.rule_psr"] ?? 0) != 0.3 && $root->metrics["_dd.agent_psr"] == 1) { echo "Rule OK\n"; } else { var_dump($root->metrics); @@ -25,4 +25,4 @@ echo "_dd.p.dm = {$root->meta["_dd.p.dm"]}\n"; ?> --EXPECT-- Rule OK -_dd.p.dm = -1 +_dd.p.dm = -0 diff --git a/tests/ext/priority_sampling/invalid-regex-rule.phpt b/tests/ext/priority_sampling/invalid-regex-rule.phpt index 851fb0e21b6..1abf25faea5 100644 --- a/tests/ext/priority_sampling/invalid-regex-rule.phpt +++ b/tests/ext/priority_sampling/invalid-regex-rule.phpt @@ -15,7 +15,7 @@ if (getenv("USE_ZEND_ALLOC") === "0") { \DDTrace\get_priority_sampling(); $root = \DDTrace\root_span(); -if ($root->metrics["_dd.rule_psr"] != 0.3) { +if (($root->metrics["_dd.rule_psr"] ?? 0) != 0.3 && $root->metrics["_dd.agent_psr"] == 1) { echo "Rule OK\n"; } else { var_dump($root->metrics); diff --git a/tests/ext/root_span_url_as_resource_names.phpt b/tests/ext/root_span_url_as_resource_names.phpt index f42f5aa6332..4097014c5e9 100644 --- a/tests/ext/root_span_url_as_resource_names.phpt +++ b/tests/ext/root_span_url_as_resource_names.phpt @@ -30,7 +30,7 @@ array(6) { ["http.method"]=> string(3) "GET" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["http.status_code"]=> string(3) "200" ["_dd.p.tid"]=> 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 b4033a73c67..851744933ee 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 @@ -26,7 +26,7 @@ array(6) { ["http.method"]=> string(3) "GET" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["http.status_code"]=> string(3) "200" ["_dd.p.tid"]=> 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 71c1d418ee2..be024ed20ad 100644 --- a/tests/ext/root_span_url_with_post_no_param.phpt +++ b/tests/ext/root_span_url_with_post_no_param.phpt @@ -20,7 +20,7 @@ array(3) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } 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 91aa61e1551..a15bd739710 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 @@ -19,7 +19,7 @@ array(3) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } diff --git a/tests/ext/sandbox-prehook/dd_trace_method.phpt b/tests/ext/sandbox-prehook/dd_trace_method.phpt index 492154b9f89..5b09b27dd51 100644 --- a/tests/ext/sandbox-prehook/dd_trace_method.phpt +++ b/tests/ext/sandbox-prehook/dd_trace_method.phpt @@ -116,7 +116,7 @@ array(3) { ["args.0"]=> string(18) "tracing is awesome" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -128,7 +128,7 @@ array(3) { float(100) ["bar"]=> float(0) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -185,7 +185,7 @@ array(3) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -193,7 +193,7 @@ array(3) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/sandbox-regression/nested_dropped_spans.phpt b/tests/ext/sandbox-regression/nested_dropped_spans.phpt index 0ec065985a2..940f8c59b14 100644 --- a/tests/ext/sandbox-regression/nested_dropped_spans.phpt +++ b/tests/ext/sandbox-regression/nested_dropped_spans.phpt @@ -28,7 +28,7 @@ dd_dump_spans(); --EXPECTF-- spans(\DDTrace\SpanData) (1) { root span (nested_dropped_spans.php, root span, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s inner span (nested_dropped_spans.php, inner span, cli) } diff --git a/tests/ext/sandbox/dd_trace_function_alias.phpt b/tests/ext/sandbox/dd_trace_function_alias.phpt index 0aac060a0bc..c795eb7bce1 100644 --- a/tests/ext/sandbox/dd_trace_function_alias.phpt +++ b/tests/ext/sandbox/dd_trace_function_alias.phpt @@ -25,6 +25,6 @@ dd_dump_spans(); bar(hello) spans(\DDTrace\SpanData) (1) { bar (alias, bar, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/sandbox/dd_trace_function_complex.phpt b/tests/ext/sandbox/dd_trace_function_complex.phpt index 891596bc2d1..0394cb5023b 100644 --- a/tests/ext/sandbox/dd_trace_function_complex.phpt +++ b/tests/ext/sandbox/dd_trace_function_complex.phpt @@ -123,7 +123,7 @@ array(5) { ["retval.rand"]=> string(%d) "%d" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -135,7 +135,7 @@ array(5) { float(1.2) ["bar"]=> float(25) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -218,7 +218,7 @@ array(5) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -226,7 +226,7 @@ array(5) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -257,7 +257,7 @@ array(5) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -265,7 +265,7 @@ array(5) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/sandbox/dd_trace_function_internal.phpt b/tests/ext/sandbox/dd_trace_function_internal.phpt index 9dfe1b4a379..fc1acb3d625 100644 --- a/tests/ext/sandbox/dd_trace_function_internal.phpt +++ b/tests/ext/sandbox/dd_trace_function_internal.phpt @@ -46,7 +46,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -54,7 +54,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/sandbox/dd_trace_method.phpt b/tests/ext/sandbox/dd_trace_method.phpt index 012b04829e9..5274ed6e675 100644 --- a/tests/ext/sandbox/dd_trace_method.phpt +++ b/tests/ext/sandbox/dd_trace_method.phpt @@ -131,7 +131,7 @@ array(3) { ["retval.rand"]=> string(%d) "%d" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -143,7 +143,7 @@ array(3) { float(100) ["bar"]=> float(0) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -204,7 +204,7 @@ array(3) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -212,7 +212,7 @@ array(3) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/sandbox/dd_trace_method_alias.phpt b/tests/ext/sandbox/dd_trace_method_alias.phpt index 992bb74ef16..cfa8f1a211b 100644 --- a/tests/ext/sandbox/dd_trace_method_alias.phpt +++ b/tests/ext/sandbox/dd_trace_method_alias.phpt @@ -29,6 +29,6 @@ dd_dump_spans(); Foo::bar(hello) spans(\DDTrace\SpanData) (1) { Foo.bar (alias, Foo.bar, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/sandbox/default_span_properties.phpt b/tests/ext/sandbox/default_span_properties.phpt index 975c5ce7da6..7135d89b8c3 100644 --- a/tests/ext/sandbox/default_span_properties.phpt +++ b/tests/ext/sandbox/default_span_properties.phpt @@ -35,7 +35,7 @@ dd_dump_spans(); spans(\DDTrace\SpanData) (1) { main (default_span_properties.php, main, cli) max => 6 - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s MyRange (default_span_properties.php, MyRange, cli) array_sum (default_span_properties.php, array_sum, cli) diff --git a/tests/ext/sandbox/default_span_properties_method.phpt b/tests/ext/sandbox/default_span_properties_method.phpt index 35074233f80..634341dc1c8 100644 --- a/tests/ext/sandbox/default_span_properties_method.phpt +++ b/tests/ext/sandbox/default_span_properties_method.phpt @@ -42,7 +42,7 @@ dd_dump_spans(); spans(\DDTrace\SpanData) (1) { Foo.main (default_span_properties_method.php, Foo.main, cli) year => 2020 - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s DateTime.__construct (default_span_properties_method.php, DateTime.__construct, cli) date => 2020-06-15 diff --git a/tests/ext/sandbox/errors_are_flagged_from_userland.phpt b/tests/ext/sandbox/errors_are_flagged_from_userland.phpt index ef014db6ab8..2214480f581 100644 --- a/tests/ext/sandbox/errors_are_flagged_from_userland.phpt +++ b/tests/ext/sandbox/errors_are_flagged_from_userland.phpt @@ -50,7 +50,7 @@ array(1) { ["error.message"]=> string(9) "Foo error" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -58,7 +58,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/sandbox/install_hook/hook_scoped_file.phpt b/tests/ext/sandbox/install_hook/hook_scoped_file.phpt index 069512e1de1..5e547527ac4 100644 --- a/tests/ext/sandbox/install_hook/hook_scoped_file.phpt +++ b/tests/ext/sandbox/install_hook/hook_scoped_file.phpt @@ -29,7 +29,7 @@ dd_dump_spans(); test spans(\DDTrace\SpanData) (1) { A.include (hook_scoped_file.php, A.include, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s %s/testinclude.inc (hook_scoped_file.php, %s/testinclude.inc, cli) } diff --git a/tests/ext/sandbox/install_hook/trace_file.phpt b/tests/ext/sandbox/install_hook/trace_file.phpt index 4326463f01e..09a8f118f6f 100644 --- a/tests/ext/sandbox/install_hook/trace_file.phpt +++ b/tests/ext/sandbox/install_hook/trace_file.phpt @@ -22,9 +22,9 @@ test test spans(\DDTrace\SpanData) (2) { %s/testinclude.inc (trace_file.php, %s/install_hook/testinclude.inc, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s %s/testinclude.inc (trace_file.php, %s/install_hook/testinclude.inc, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/sandbox/span_clone.phpt b/tests/ext/sandbox/span_clone.phpt index a51cb8c642a..3824ddf4283 100644 --- a/tests/ext/sandbox/span_clone.phpt +++ b/tests/ext/sandbox/span_clone.phpt @@ -199,7 +199,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -207,7 +207,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/span_stack/span_stack_clone.phpt b/tests/ext/span_stack/span_stack_clone.phpt index 8319d73bdfd..176d7e6229c 100644 --- a/tests/ext/span_stack/span_stack_clone.phpt +++ b/tests/ext/span_stack/span_stack_clone.phpt @@ -51,18 +51,18 @@ A clone of the primary trace has the root stack as parent: bool(true) Switching to an initial stacks parent has no effect: bool(true) spans(\DDTrace\SpanData) (5) { primary (span_stack_clone.php, primary, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s root (span_stack_clone.php, root, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s root clone (span_stack_clone.php, root clone, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s primary clone (span_stack_clone.php, primary clone, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s initial clone (span_stack_clone.php, initial clone, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/span_stack/span_stack_swap.phpt b/tests/ext/span_stack/span_stack_swap.phpt index 0436a1b7270..63e607f7cc9 100644 --- a/tests/ext/span_stack/span_stack_swap.phpt +++ b/tests/ext/span_stack/span_stack_swap.phpt @@ -79,12 +79,12 @@ But we can still swap to stacks started before that: bool(true) We closed the active stack after all other stacks were closed. No other span is active right now: bool(true) spans(\DDTrace\SpanData) (2) { span_stack_swap.php (span_stack_swap.php, span_stack_swap.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s (span_stack_swap.php, cli) (span_stack_swap.php, cli) (span_stack_swap.php, cli) other root (span_stack_swap.php, other root, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/span_stack/span_stack_swap_traced_function.phpt b/tests/ext/span_stack/span_stack_swap_traced_function.phpt index 93ba4c97278..e5d5ca5310e 100644 --- a/tests/ext/span_stack/span_stack_swap_traced_function.phpt +++ b/tests/ext/span_stack/span_stack_swap_traced_function.phpt @@ -57,7 +57,7 @@ Now, we have explicitly closed it: bool(true) We closed the active stack after all other stacks were closed. No other span is active right now: bool(true) spans(\DDTrace\SpanData) (1) { span_stack_swap_traced_function.php (span_stack_swap_traced_function.php, span_stack_swap_traced_function.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s outer (span_stack_swap_traced_function.php, outer, cli) creates_span_stack (span_stack_swap_traced_function.php, creates_span_stack, cli) diff --git a/tests/ext/span_stack/span_trace_stack_autoclose.phpt b/tests/ext/span_stack/span_trace_stack_autoclose.phpt index f07b01fe28e..e1c61ca213e 100644 --- a/tests/ext/span_stack/span_trace_stack_autoclose.phpt +++ b/tests/ext/span_stack/span_trace_stack_autoclose.phpt @@ -34,7 +34,7 @@ We are back on our primary stack: bool(true) Having lost all references to the that span stacks objects, it is autoclosed: bool(true) spans(\DDTrace\SpanData) (1) { span_trace_stack_autoclose.php (span_trace_stack_autoclose.php, span_trace_stack_autoclose.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s (span_trace_stack_autoclose.php, cli) } diff --git a/tests/ext/span_stack/span_trace_swap.phpt b/tests/ext/span_stack/span_trace_swap.phpt index cd2eb8007ca..a6bdd2c6a98 100644 --- a/tests/ext/span_stack/span_trace_swap.phpt +++ b/tests/ext/span_stack/span_trace_swap.phpt @@ -40,9 +40,9 @@ We closed the active stack after all other stacks were closed. No other span is This automatically switches back to the parent stack: bool(true) spans(\DDTrace\SpanData) (2) { span_trace_swap.php (span_trace_swap.php, span_trace_swap.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s other root (span_trace_swap.php, other root, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/span_stack/start_span_new_trace.phpt b/tests/ext/span_stack/start_span_new_trace.phpt index eaed3b0527e..c7ef24cd7a0 100644 --- a/tests/ext/span_stack/start_span_new_trace.phpt +++ b/tests/ext/span_stack/start_span_new_trace.phpt @@ -59,12 +59,12 @@ After closing the trace root, we swap back to the previously active stack: bool( With the trace root also accordingly updated: bool(true) spans(\DDTrace\SpanData) (2) { start_span_new_trace.php (start_span_new_trace.php, start_span_new_trace.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s (start_span_new_trace.php, cli) (start_span_new_trace.php, cli) other root (start_span_new_trace.php, other root, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s (start_span_new_trace.php, cli) } diff --git a/tests/ext/span_stack/start_span_stack.phpt b/tests/ext/span_stack/start_span_stack.phpt index 91edf844543..9d467df4bbe 100644 --- a/tests/ext/span_stack/start_span_stack.phpt +++ b/tests/ext/span_stack/start_span_stack.phpt @@ -53,7 +53,7 @@ Active stack is swapped back when a span below the current span stack is closed: The stack still retains its direct parent as active: bool(true) spans(\DDTrace\SpanData) (1) { start_span_stack.php (start_span_stack.php, start_span_stack.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s (start_span_stack.php, cli) (start_span_stack.php, cli) diff --git a/tests/ext/span_stack/start_top_level_span_stack.phpt b/tests/ext/span_stack/start_top_level_span_stack.phpt index f46898e6079..63d3d6729f9 100644 --- a/tests/ext/span_stack/start_top_level_span_stack.phpt +++ b/tests/ext/span_stack/start_top_level_span_stack.phpt @@ -44,6 +44,6 @@ Now, we are back on the global span stack: bool(true) Impliying we also have no active span: bool(true) spans(\DDTrace\SpanData) (1) { start_top_level_span_stack.php (start_top_level_span_stack.php, start_top_level_span_stack.php, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/ext/span_with_removed_exception.phpt b/tests/ext/span_with_removed_exception.phpt index ef6cd1ffde7..0231ce64692 100644 --- a/tests/ext/span_with_removed_exception.phpt +++ b/tests/ext/span_with_removed_exception.phpt @@ -72,7 +72,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -80,7 +80,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -113,7 +113,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -121,7 +121,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -154,7 +154,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -162,7 +162,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/start_span_with_all_properties.phpt b/tests/ext/start_span_with_all_properties.phpt index 1da40ad0e02..6bf0c725ff4 100644 --- a/tests/ext/start_span_with_all_properties.phpt +++ b/tests/ext/start_span_with_all_properties.phpt @@ -77,7 +77,7 @@ array(2) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -85,7 +85,7 @@ array(2) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) @@ -118,7 +118,7 @@ array(2) { ["aa"]=> string(2) "bb" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -128,7 +128,7 @@ array(2) { float(%f) ["cc"]=> float(0) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/start_span_without_closing.phpt b/tests/ext/start_span_without_closing.phpt index 4cc8eb6df86..686d2af3676 100644 --- a/tests/ext/start_span_without_closing.phpt +++ b/tests/ext/start_span_without_closing.phpt @@ -49,7 +49,7 @@ array(1) { ["runtime-id"]=> string(36) "%s" ["_dd.p.dm"]=> - string(2) "-1" + string(2) "-0" ["_dd.p.tid"]=> string(16) "%s" } @@ -57,7 +57,7 @@ array(1) { array(4) { ["process_id"]=> float(%f) - ["_dd.rule_psr"]=> + ["_dd.agent_psr"]=> float(1) ["_sampling_priority_v1"]=> float(1) diff --git a/tests/ext/traced_attribute.phpt b/tests/ext/traced_attribute.phpt index 789ab59319e..762478b90fd 100644 --- a/tests/ext/traced_attribute.phpt +++ b/tests/ext/traced_attribute.phpt @@ -58,17 +58,17 @@ dd_dump_spans(); --EXPECTF-- spans(\DDTrace\SpanData) (3) { bar (traced_attribute.php, bar, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s simplename (test, rsrc, typeee) a => b _dd.base_service => traced_attribute.php recursion (traced_attribute.php, recursion, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s recursion (traced_attribute.php, recursion, cli) recursion (traced_attribute.php, recursion, cli) noRecursion (traced_attribute.php, noRecursion, cli) - _dd.p.dm => -1 + _dd.p.dm => -0 _dd.p.tid => %s } diff --git a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json index 60b1377f26f..c024f9c3ca0 100644 --- a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 14729891649013051183, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json index 8e8e2c4a245..9e0c1921a41 100644 --- a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "error.message": "Controller error", "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array)\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/Render/Renderer.php(592): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext(Object(Drupal\\Core\\Render\\RenderContext), Object(Closure))\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array)\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(182): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw(Object(Symfony\\Component\\HttpFoundation\\Request), 1)\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(51): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/core/lib/Drupal/Core/DrupalKernel.php(704): Drupal\\Core\\StackMiddleware\\StackedHttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_10_1/index.php(19): Drupal\\Core\\DrupalKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request))\n#14 {main}", diff --git a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json index f998fc0e7b2..c6c7b140b0b 100644 --- a/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.drupal.v10_1.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 16602002948505053039, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json index 1bc5ab65408..107ebf6b3ee 100644 --- a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 8612363250016351856, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json index 036878ba645..8d0c482e136 100644 --- a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "error.message": "Controller error", "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array)\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/Render/Renderer.php(573): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext(Object(Drupal\\Core\\Render\\RenderContext), Object(Closure))\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array)\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(151): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/symfony/http-kernel/HttpKernel.php(68): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw(Object(Symfony\\Component\\HttpFoundation\\Request), 1)\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/Session.php(57): Symfony\\Component\\HttpKernel\\HttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(47): Drupal\\Core\\StackMiddleware\\Session->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(47): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(52): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/core/lib/Drupal/Core/DrupalKernel.php(708): Stack\\StackedHttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_8_9/index.php(19): Drupal\\Core\\DrupalKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request))\n#14 {main}", diff --git a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json index 8e55c86dcb7..c9369768413 100644 --- a/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.drupal.v8_9.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 3272765232554916325, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json index 0ceb060123f..e71b4094b65 100644 --- a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 3252206533936179831, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json index dac840b8614..848010ebac9 100644 --- a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "error.message": "Controller error", "error.stack": "#0 [internal function]: Drupal\\datadog\\Controller\\DatadogController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array(Array, Array)\n#2 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/Render/Renderer.php(580): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(124): Drupal\\Core\\Render\\Renderer->executeInRenderContext(Object(Drupal\\Core\\Render\\RenderContext), Object(Closure))\n#4 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array)\n#5 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(169): Drupal\\Core\\EventSubscriber\\EarlyRenderingControllerWrapperSubscriber->Drupal\\Core\\EventSubscriber\\{closure}()\n#6 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/symfony/http-kernel/HttpKernel.php(81): Symfony\\Component\\HttpKernel\\HttpKernel->handleRaw(Object(Symfony\\Component\\HttpFoundation\\Request), 1)\n#7 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/Session.php(58): Symfony\\Component\\HttpKernel\\HttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#8 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\\Core\\StackMiddleware\\Session->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#9 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\\Core\\StackMiddleware\\KernelPreHandle->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#10 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\\Core\\StackMiddleware\\ReverseProxyMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#11 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/vendor/stack/builder/src/Stack/StackedHttpKernel.php(23): Drupal\\Core\\StackMiddleware\\NegotiationMiddleware->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#12 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/core/lib/Drupal/Core/DrupalKernel.php(718): Stack\\StackedHttpKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request), 1, true)\n#13 /home/circleci/datadog/tests/Frameworks/Drupal/Version_9_5/index.php(19): Drupal\\Core\\DrupalKernel->handle(Object(Symfony\\Component\\HttpFoundation\\Request))\n#14 {main}", diff --git a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json index 65848d0087a..546d0db7453 100644 --- a/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.drupal.v9_5.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 17928482264370609219, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "drupal", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json index 03cb91ee03b..3bd72d24814 100644 --- a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json +++ b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest2xx.json @@ -8,7 +8,7 @@ "parent_id": 15955735635444038764, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "POST", "http.status_code": "201", diff --git a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json index f0e4f90e23c..55e95286309 100644 --- a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json +++ b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest4xx.json @@ -8,7 +8,7 @@ "parent_id": 7418555806868091952, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "http.status_code": "405", diff --git a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json index beee82635ab..51c0e6e6333 100644 --- a/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json +++ b/tests/snapshots/tests.integrations.laminas.api_tools.v1_9.rest_test.test_scenario_rest5xx.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "error.message": "Attempt to assign property \"b\" on null", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/AbstractResourceListener.php(182): DatadogApi\\V1\\Rest\\DatadogRestService\\DatadogRestServiceResource->fetch('42')\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\ApiTools\\Rest\\AbstractResourceListener->dispatch(Object(Laminas\\ApiTools\\Rest\\ResourceEvent))\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\ApiTools\\Rest\\ResourceEvent), Object(Closure))\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(548): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\ApiTools\\Rest\\ResourceEvent))\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/Resource.php(499): Laminas\\ApiTools\\Rest\\Resource->triggerEvent('fetch', Array)\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(493): Laminas\\ApiTools\\Rest\\Resource->fetch('42')\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(372): Laminas\\ApiTools\\Rest\\RestController->get('42')\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas-api-tools/api-tools-rest/src/RestController.php(335): Laminas\\Mvc\\Controller\\AbstractRestfulController->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\ApiTools\\Rest\\RestController->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#9 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#10 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#11 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Controller/AbstractRestfulController.php(306): Laminas\\Mvc\\Controller\\AbstractController->dispatch(Object(Laminas\\Http\\PhpEnvironment\\Request), Object(Laminas\\Http\\PhpEnvironment\\Response))\n#12 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractRestfulController->dispatch(Object(Laminas\\Http\\PhpEnvironment\\Request), Object(Laminas\\Http\\PhpEnvironment\\Response))\n#13 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#14 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#15 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#16 /home/circleci/datadog/tests/Frameworks/Laminas/ApiTools/Version_1_9/public/index.php(60): Laminas\\Mvc\\Application->run()\n#17 {main}", diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json index e0e4b354f48..f6018e1251d 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 1138859447638824880, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "laminas.route.name": "simple", diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json index a435e1e77e1..1fe603ebae7 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 1625545689840073902, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json index cd0963c8b8a..a1a05947e76 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "error.message": "Controller error", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(71): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(97): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/DispatchListener.php(132): Laminas\\Mvc\\Controller\\AbstractController->dispatch(Object(Laminas\\Http\\PhpEnvironment\\Request), Object(Laminas\\Http\\PhpEnvironment\\Response))\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(319): Laminas\\Mvc\\DispatchListener->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-eventmanager/src/EventManager.php(179): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/vendor/laminas/laminas-mvc/src/Application.php(325): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_1_4/public/index.php(42): Laminas\\Mvc\\Application->run()\n#9 {main}", diff --git a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json index d98f5e120a3..b42283934e3 100644 --- a/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laminas.v1_4.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 3103401253233543803, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "laminas.route.name": "simpleView", diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json index 3693ceed585..02fe659dc17 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 2883696377808074518, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "laminas.route.name": "simple", diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json index 3cff4ccc023..81bd2112f14 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 2747341357639439789, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json index 53e9c0371fa..3e80953fdbb 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "error.message": "Controller error", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractActionController.php(72): Application\\Controller\\CommonSpecsController->errorAction()\n#1 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\Controller\\AbstractActionController->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#2 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#3 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Controller/AbstractController.php(105): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#4 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/DispatchListener.php(117): Laminas\\Mvc\\Controller\\AbstractController->dispatch(Object(Laminas\\Http\\PhpEnvironment\\Request), Object(Laminas\\Http\\PhpEnvironment\\Response))\n#5 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(320): Laminas\\Mvc\\DispatchListener->onDispatch(Object(Laminas\\Mvc\\MvcEvent))\n#6 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-eventmanager/src/EventManager.php(178): Laminas\\EventManager\\EventManager->triggerListeners(Object(Laminas\\Mvc\\MvcEvent), Object(Closure))\n#7 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/vendor/laminas/laminas-mvc/src/Application.php(319): Laminas\\EventManager\\EventManager->triggerEventUntil(Object(Closure), Object(Laminas\\Mvc\\MvcEvent))\n#8 /home/circleci/datadog/tests/Frameworks/Laminas/Version_2_0/public/index.php(37): Laminas\\Mvc\\Application->run()\n#9 {main}", diff --git a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json index bdfe45593d2..17dc87e7775 100644 --- a/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laminas.v2_0.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 11972289175454604309, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laminas", "http.method": "GET", "laminas.route.name": "simpleView", diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json index fbbbd612536..74349e49a5f 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 5443607902760525034, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "http.method": "GET", "http.route": "simple", diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json index cb27655c88f..8abae96f162 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 4578985128299749382, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json index 3facfeadb73..3aa99b27013 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "error.message": "Controller error", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction('error', Array)\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch(Object(Illuminate\\Routing\\Route), Object(App\\Http\\Controllers\\CommonSpecsController), 'error')\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(799): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest(Object(Illuminate\\Http\\Request), Object(Illuminate\\Session\\Store), Object(Closure))\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(777): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(741): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(730): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(200): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(89): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(175): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(144): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_10_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))\n#44 {main}", diff --git a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json index be96b174250..8caa8728d0c 100644 --- a/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v10_x.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 317046915637618955, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "http.method": "GET", "http.route": "simple_view", diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json index 794ac909a9a..90c2e0c21f4 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 9564156679057338630, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "http.method": "GET", "http.route": "simple", diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json index 5b03a195d96..1934aa1ed1f 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 11595373598994821236, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json index c6b368c361a..c12cb5142ef 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "error.message": "Controller error", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\\Http\\Controllers\\CommonSpecsController->error()\n#1 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(43): Illuminate\\Routing\\Controller->callAction('error', Array)\n#2 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(259): Illuminate\\Routing\\ControllerDispatcher->dispatch(Object(Illuminate\\Routing\\Route), Object(App\\Http\\Controllers\\CommonSpecsController), 'error')\n#3 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Route.php(205): Illuminate\\Routing\\Route->runController()\n#4 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(798): Illuminate\\Routing\\Route->run()\n#5 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Routing\\Router->Illuminate\\Routing\\{closure}(Object(Illuminate\\Http\\Request))\n#6 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(50): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#7 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Routing\\Middleware\\SubstituteBindings->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#8 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php(78): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#9 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#10 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/View/Middleware/ShareErrorsFromSession.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#11 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\View\\Middleware\\ShareErrorsFromSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#12 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(121): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#13 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php(64): Illuminate\\Session\\Middleware\\StartSession->handleStatefulRequest(Object(Illuminate\\Http\\Request), Object(Illuminate\\Session\\Store), Object(Closure))\n#14 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Session\\Middleware\\StartSession->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#15 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/AddQueuedCookiesToResponse.php(37): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#16 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#17 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Cookie/Middleware/EncryptCookies.php(67): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#18 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Cookie\\Middleware\\EncryptCookies->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#19 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#20 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(797): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))\n#21 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(776): Illuminate\\Routing\\Router->runRouteWithinStack(Object(Illuminate\\Routing\\Route), Object(Illuminate\\Http\\Request))\n#22 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(740): Illuminate\\Routing\\Router->runRoute(Object(Illuminate\\Http\\Request), Object(Illuminate\\Routing\\Route))\n#23 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Routing/Router.php(729): Illuminate\\Routing\\Router->dispatchToRoute(Object(Illuminate\\Http\\Request))\n#24 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(190): Illuminate\\Routing\\Router->dispatch(Object(Illuminate\\Http\\Request))\n#25 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(141): Illuminate\\Foundation\\Http\\Kernel->Illuminate\\Foundation\\Http\\{closure}(Object(Illuminate\\Http\\Request))\n#26 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#27 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ConvertEmptyStringsToNull.php(31): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#28 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#29 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#30 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TrimStrings.php(40): Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#31 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\TrimStrings->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#32 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#33 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#34 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/PreventRequestsDuringMaintenance.php(86): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#35 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#36 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/HandleCors.php(49): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#37 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\HandleCors->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#38 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Http/Middleware/TrustProxies.php(39): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#39 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(180): Illuminate\\Http\\Middleware\\TrustProxies->handle(Object(Illuminate\\Http\\Request), Object(Closure))\n#40 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(116): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}(Object(Illuminate\\Http\\Request))\n#41 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(165): Illuminate\\Pipeline\\Pipeline->then(Object(Closure))\n#42 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(134): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter(Object(Illuminate\\Http\\Request))\n#43 /home/circleci/datadog/tests/Frameworks/Laravel/Version_9_x/public/index.php(51): Illuminate\\Foundation\\Http\\Kernel->handle(Object(Illuminate\\Http\\Request))\n#44 {main}", diff --git a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json index b507aa49b11..02224d47782 100644 --- a/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.laravel.v9_x.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 5714801379517671581, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "laravel", "http.method": "GET", "http.route": "simple_view", diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json index 7992881606e..d1935c5815d 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 13454368413995182121, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json index 95245733e64..1db20984498 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 6083175307739199431, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json index f418c20d968..7c869772095 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "error.message": "Caught Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/CustomElement/Datadog/Controller/error/Index.php:15", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(24): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Action/Action.php(108): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(39): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(186): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/FrontController.php(118): Magento\\Framework\\App\\FrontController->processRequest()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(73): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/FrontController/Interceptor.php(26): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/generated/code/Magento/Framework/App/Http/Interceptor.php(24): Magento\\Framework\\App\\Http->launch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/lib/internal/Magento/Framework/App/Bootstrap.php(261): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_3/pub/index.php(40): Magento\\Framework\\App\\Bootstrap->run()\n#20 {main}", diff --git a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json index ef45b1ae758..957464d6ec2 100644 --- a/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.magento.v2_3.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 3486890338802727712, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json index 523ba776613..50812fac224 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 14534241447673934000, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json index cea1bc9a02c..4d692939d45 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 10506185486166963197, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json index 01a6cfec372..da7242f8d56 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "error.message": "Caught Exception (500): This is an exception in /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/CustomElement/Datadog/Controller/error/Index.php:15", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): CustomElement\\Datadog\\Controller\\Error\\Index->execute()\n#1 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#2 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#3 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(23): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#4 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Action/Action.php(111): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->execute()\n#5 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\Action\\Action->dispatch()\n#6 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callParent()\n#7 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#8 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/CustomElement/Datadog/Controller/error/Index/Interceptor.php(32): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->___callPlugins()\n#9 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(245): CustomElement\\Datadog\\Controller\\error\\Index\\Interceptor->dispatch()\n#10 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(212): Magento\\Framework\\App\\FrontController->getActionResponse()\n#11 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/FrontController.php(147): Magento\\Framework\\App\\FrontController->processRequest()\n#12 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(58): Magento\\Framework\\App\\FrontController->dispatch()\n#13 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(138): Magento\\Framework\\App\\FrontController\\Interceptor->___callParent()\n#14 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/Store/App/FrontController/Plugin/RequestPreprocessor.php(99): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#15 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\Store\\App\\FrontController\\Plugin\\RequestPreprocessor->aroundDispatch()\n#16 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php(75): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#17 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(135): Magento\\PageCache\\Model\\App\\FrontController\\BuiltinPlugin->aroundDispatch()\n#18 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/Interception/Interceptor.php(153): Magento\\Framework\\App\\FrontController\\Interceptor->Magento\\Framework\\Interception\\{closure}()\n#19 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/FrontController/Interceptor.php(23): Magento\\Framework\\App\\FrontController\\Interceptor->___callPlugins()\n#20 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Http.php(116): Magento\\Framework\\App\\FrontController\\Interceptor->dispatch()\n#21 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/generated/code/Magento/Framework/App/Http/Interceptor.php(23): Magento\\Framework\\App\\Http->launch()\n#22 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/lib/internal/Magento/Framework/App/Bootstrap.php(264): Magento\\Framework\\App\\Http\\Interceptor->launch()\n#23 /home/circleci/datadog/tests/Frameworks/Magento/Version_2_4/pub/index.php(30): Magento\\Framework\\App\\Bootstrap->run()\n#24 {main}", diff --git a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json index 1469f2d71aa..5d239d7b682 100644 --- a/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.magento.v2_4.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 10998067911031505968, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "magento", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json index a0223197ecf..60f03a6a476 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 2587331881374610944, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 569e1705f65..767fc6b0cce 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json index 368e8c6e240..8b1235278a1 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 1480301274519110725, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_return_string.json index ee81777941d..1a2f0a32aee 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 9477166790255416813, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_exception.json index 1bd1115fb5a..c9653fa6565 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_view.json index 744125dff3c..ffdaf744c6c 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_legacy_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 729478495814728347, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json index 669221da36c..a754fb1be21 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 10805381678613173804, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json index df43f5b85c3..4c3d0c4970a 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(298): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp-hook.php(323): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/plugin.php(515): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/class-wp.php(735): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-includes/functions.php(955): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_4_8/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json index c9a163c6c79..ff804d175a1 100644 --- a/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v4_8.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 11949161841683561835, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json index f7e3354bc3e..2c1257d411a 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 18156728975759963063, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json index b4e59746d34..7154fc61821 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 7496115614704111489, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 526a4ccd7ea..7ffe282f11c 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json index eec143fa621..741d640c4fd 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 14874721839110430327, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_return_string.json index b9737645454..71c0e554968 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 2376336973316687556, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json index 8227513bf6d..eddad2ab4ae 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 7208281282619085399, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_exception.json index a539bbe1332..923b8b50221 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_view.json index 8ee0af0ea1e..577f78c5555 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_legacy_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 4277338660967823217, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json index 2436dabf982..b15281c89d4 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 9639977890261987175, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json index 48ae358ce40..a6f55c0dfb8 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 4536844318343104219, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json index 0c991eaf355..e5f0da388a3 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(287): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp-hook.php(311): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/plugin.php(544): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(388): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/class-wp.php(745): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-includes/functions.php(1285): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_5/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json index df48c41e2b4..74a9b28d7c3 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_5.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 1914478145394293009, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json index 5185a4b4033..b23f76eb7ed 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 10231339834288839009, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json index d2df0ad703c..b5eed334ba8 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 7217153423167374317, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index e9ccef35038..d1b8eea442f 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json index 321e8ddf209..7bc7da770d9 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 11672833717113024137, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_return_string.json index aaf00de5c82..3eca9227b3b 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 15360509330838616158, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json index 6062d2a5c0c..f6d3dd81f78 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 2082656520519468046, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_exception.json index 615758667d8..c4c9d9667ea 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_view.json index f242604bdf7..38c756916dc 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_legacy_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 5760996200555978682, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json index 383f59d2836..117350e68c2 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 8699478024760093354, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json index 45505f04020..f70f72db54b 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_to_missing_route.json @@ -8,7 +8,7 @@ "parent_id": 929880556876491322, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "404", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json index d1c3ad74391..629dd79dd0a 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-content/plugins/datadog/datadog.php:23", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(307): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/plugin.php(522): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(396): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/class-wp.php(758): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-includes/functions.php(1310): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_5_9/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json index d4f3e88566a..30b42854dae 100644 --- a/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v5_9.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 8549288142567688443, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json index 01b5072d85e..61920a5a8db 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 7003712820514925341, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json index 16a92cac544..f691674aa93 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json index 8c7350c6902..04f53796f3f 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_callbacks_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 17006417365160231210, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_return_string.json index c0af72bcd01..dbf37e24bd5 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 16020973712181285647, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_exception.json index dd4c931d39d..ccc6ebe9096 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_view.json index fc45593f5c5..3ea08de9e96 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_legacy_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 6254913557649250104, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json index 3d423c85566..9e210ce681e 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_return_string.json @@ -8,7 +8,7 @@ "parent_id": 4743191852396530867, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json index e480410e817..9b1ab08857c 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_exception.json @@ -9,7 +9,7 @@ "type": "web", "error": 1, "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "error.message": "Uncaught Exception: Oops! in /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-content/plugins/datadog/datadog.php:20", "error.stack": "#0 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(308): datadog_parse_request()\n#1 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp-hook.php(332): WP_Hook->apply_filters()\n#2 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/plugin.php(565): WP_Hook->do_action()\n#3 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(399): do_action_ref_array()\n#4 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/class-wp.php(780): WP->parse_request()\n#5 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-includes/functions.php(1332): WP->main()\n#6 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/wp-blog-header.php(16): wp()\n#7 /home/circleci/datadog/tests/Frameworks/WordPress/Version_6_1/index.php(17): require()\n#8 {main}", diff --git a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json index 0624ac381a7..511bf951514 100644 --- a/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json +++ b/tests/snapshots/tests.integrations.word_press.v6_1.common_scenarios_test.test_scenario_get_with_view.json @@ -8,7 +8,7 @@ "parent_id": 17574895213374119198, "type": "web", "meta": { - "_dd.p.dm": "-1", + "_dd.p.dm": "-0", "component": "wordpress", "http.method": "GET", "http.status_code": "200", diff --git a/zend_abstract_interface/config/config.c b/zend_abstract_interface/config/config.c index f160e15b793..db0916e8de6 100644 --- a/zend_abstract_interface/config/config.c +++ b/zend_abstract_interface/config/config.c @@ -38,7 +38,7 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z if (!zai_config_decode_value(env_value, memoized->type, memoized->parser, &tmp, /* persistent */ true)) { // TODO Log decoding error } else { - zai_config_dtor_pzval(&tmp); + zai_json_dtor_pzval(&tmp); value = zai_option_str_from_str(env_value); } break; @@ -56,7 +56,7 @@ static void zai_config_find_and_set_value(zai_config_memoized_entry *memoized, z // TODO If name_index > 0, log deprecation notice zai_config_decode_value(value_view, memoized->type, memoized->parser, &tmp, /* persistent */ true); assert(Z_TYPE(tmp) > IS_NULL); - zai_config_dtor_pzval(&memoized->decoded_value); + zai_json_dtor_pzval(&memoized->decoded_value); ZVAL_COPY_VALUE(&memoized->decoded_value, &tmp); memoized->name_index = name_index; } @@ -125,7 +125,7 @@ bool zai_config_minit(zai_config_entry entries[], size_t entries_count, zai_conf static void zai_config_dtor_memoized_zvals(void) { for (uint8_t i = 0; i < zai_config_memoized_entries_count; i++) { - zai_config_dtor_pzval(&zai_config_memoized_entries[i].decoded_value); + zai_json_dtor_pzval(&zai_config_memoized_entries[i].decoded_value); } } diff --git a/zend_abstract_interface/config/config_decode.c b/zend_abstract_interface/config/config_decode.c index 8eea97ef5b5..6daa3bb5f01 100644 --- a/zend_abstract_interface/config/config_decode.c +++ b/zend_abstract_interface/config/config_decode.c @@ -7,38 +7,6 @@ #include #include -#if PHP_VERSION_ID < 80000 -#if PHP_VERSION_ID < 70300 -#define GC_DELREF(x) (--GC_REFCOUNT(x)) -#endif - -static zend_always_inline void zend_hash_release(zend_array *array) { - if (!(GC_FLAGS(array) & IS_ARRAY_IMMUTABLE)) { - if (GC_DELREF(array) == 0) { - zend_hash_destroy(array); -#if PHP_VERSION_ID < 70300 - pefree(array, array->u.flags & HASH_FLAG_PERSISTENT); -#else - pefree(array, GC_FLAGS(array) & IS_ARRAY_PERSISTENT); -#endif - } - } -} -#endif - -void zai_config_dtor_pzval(zval *pval) { - if (Z_TYPE_P(pval) == IS_ARRAY) { - if (Z_DELREF_P(pval) == 0) { - zend_hash_destroy(Z_ARRVAL_P(pval)); - free(Z_ARRVAL_P(pval)); - } - } else { - zval_internal_ptr_dtor(pval); - } - // Prevent an accidental use after free - ZVAL_UNDEF(pval); -} - static bool zai_config_decode_bool(zai_str value, zval *decoded_value) { if ((value.len == 1 && strcmp(value.ptr, "1") == 0) || (value.len == 2 && strcasecmp(value.ptr, "on") == 0) || (value.len == 3 && strcasecmp(value.ptr, "yes") == 0) || @@ -230,60 +198,20 @@ static bool zai_config_decode_set(zai_str value, zval *decoded_value, bool persi return true; } -static void zai_config_persist_zval(zval *in) { - if (Z_TYPE_P(in) == IS_ARRAY) { - zend_array *array = Z_ARR_P(in); - ZVAL_NEW_PERSISTENT_ARR(in); - zend_hash_init(Z_ARR_P(in), array->nTableSize, NULL, zai_config_dtor_pzval, 1); - if (zend_hash_num_elements(array)) { -#if PHP_VERSION_ID >= 80200 - if (HT_IS_PACKED(array)) { - zval *val; - int idx; - ZEND_HASH_PACKED_FOREACH_KEY_VAL(array, idx, val) { - zai_config_persist_zval(val); - zend_hash_index_add_new(Z_ARR_P(in), idx, val); - ZVAL_NULL(val); - } - ZEND_HASH_FOREACH_END(); - } else -#endif - { - Bucket *bucket; - ZEND_HASH_FOREACH_BUCKET(array, bucket) { - zai_config_persist_zval(&bucket->val); - if (bucket->key) { - zend_hash_str_add_new(Z_ARR_P(in), ZSTR_VAL(bucket->key), ZSTR_LEN(bucket->key), &bucket->val); - } else { - zend_hash_index_add_new(Z_ARR_P(in), bucket->h, &bucket->val); - } - ZVAL_NULL(&bucket->val); - } - ZEND_HASH_FOREACH_END(); - } - } - zend_hash_release(array); - } else if (Z_TYPE_P(in) == IS_STRING) { - zend_string *str = Z_STR_P(in); - if (!(GC_FLAGS(str) & IS_STR_PERSISTENT)) { - ZVAL_PSTRINGL(in, ZSTR_VAL(str), ZSTR_LEN(str)); - zend_string_release(str); - } - } -} - static bool zai_config_decode_json(zai_str value, zval *decoded_value, bool persistent) { - zai_json_decode_assoc(decoded_value, (char *)value.ptr, (int)value.len, 20); - - if (Z_TYPE_P(decoded_value) != IS_ARRAY) { - zval_dtor(decoded_value); + if (zai_json_decode_assoc_safe(decoded_value, (char *)value.ptr, (int)value.len, 20, persistent) != SUCCESS) { ZVAL_NULL(decoded_value); return false; } - // as we do not want to parse the JSON ourselves, we have to ensure persistence ourselves by copying - if (persistent) { - zai_config_persist_zval(decoded_value); + if (Z_TYPE_P(decoded_value) != IS_ARRAY) { + if (persistent) { + zval_internal_ptr_dtor(decoded_value); + } else { + zval_dtor(decoded_value); + } + ZVAL_NULL(decoded_value); + return false; } return true; diff --git a/zend_abstract_interface/config/config_decode.h b/zend_abstract_interface/config/config_decode.h index 8c9faf1950b..754bb43a01b 100644 --- a/zend_abstract_interface/config/config_decode.h +++ b/zend_abstract_interface/config/config_decode.h @@ -20,7 +20,6 @@ typedef enum { typedef bool (*zai_custom_parse)(zai_str value, zval *decoded_value, bool persistent); -void zai_config_dtor_pzval(zval *pval); bool zai_config_decode_value(zai_str value, zai_config_type type, zai_custom_parse custom_parser, zval *decoded_value, bool persistent); #endif // ZAI_CONFIG_DECODE_H diff --git a/zend_abstract_interface/config/config_ini.c b/zend_abstract_interface/config/config_ini.c index ea0cd37785f..6d62858f95f 100644 --- a/zend_abstract_interface/config/config_ini.c +++ b/zend_abstract_interface/config/config_ini.c @@ -1,4 +1,5 @@ #include "config_ini.h" +#include #include #include @@ -61,7 +62,7 @@ int16_t zai_config_initialize_ini_value(zend_ini_entry **entries, if (!zai_config_decode_value(ZAI_STR_FROM_ZSTR(ini_str), memoized->type, memoized->parser, &new_zv, true)) { continue; } - zai_config_dtor_pzval(&new_zv); + zai_json_dtor_pzval(&new_zv); parsed_ini_value = zend_string_copy(ini_str); name_index = i; @@ -85,7 +86,7 @@ int16_t zai_config_initialize_ini_value(zend_ini_entry **entries, if (!zai_config_decode_value(ZAI_STR_FROM_ZSTR(Z_STR_P(inizv)), memoized->type, memoized->parser, &new_zv, true)) { continue; } - zai_config_dtor_pzval(&new_zv); + zai_json_dtor_pzval(&new_zv); parsed_ini_value = zend_string_copy(Z_STR_P(inizv)); name_index = i; @@ -185,7 +186,7 @@ static ZEND_INI_MH(ZaiConfigOnUpdateIni) { /* Ignore calls that happen before runtime (e.g. the default INI values on MINIT). System values are obtained on * first-time RINIT. */ if (global_stage) { - zai_config_dtor_pzval(&new_zv); + zai_json_dtor_pzval(&new_zv); return SUCCESS; } @@ -254,7 +255,7 @@ static void zai_config_add_ini_entry(zai_config_memoized_entry *memoized, zai_st // This should never fail, ideally, as all usages should validate the same way, but at least not crash, just don't accept the value then if (zai_config_decode_value(value_view, memoized->type, memoized->parser, &decoded, 1)) { - zai_config_dtor_pzval(&memoized->decoded_value); + zai_json_dtor_pzval(&memoized->decoded_value); ZVAL_COPY_VALUE(&memoized->decoded_value, &decoded); } } diff --git a/zend_abstract_interface/json/json.c b/zend_abstract_interface/json/json.c index d9c341b2781..73800677f4f 100644 --- a/zend_abstract_interface/json/json.c +++ b/zend_abstract_interface/json/json.c @@ -1,5 +1,74 @@ #include "json.h" +typedef unsigned char php_json_ctype; + +typedef int php_json_error_code; + +typedef struct _php_json_scanner { + php_json_ctype *cursor; /* cursor position */ + php_json_ctype *token; /* token position */ + php_json_ctype *limit; /* the last read character + 1 position */ + php_json_ctype *marker; /* marker position for backtracking */ + php_json_ctype *ctxmarker; /* marker position for context backtracking */ + php_json_ctype *str_start; /* start position of the string */ + php_json_ctype *pstr; /* string pointer for escapes conversion */ + zval value; /* value */ + int str_esc; /* number of extra characters for escaping */ + int state; /* condition state */ + int options; /* options */ + php_json_error_code errcode; /* error type if there is an error */ +#if PHP_VERSION_ID >= 70200 + int utf8_invalid; /* whether utf8 is invalid */ + int utf8_invalid_count; /* number of extra character for invalid utf8 */ +#endif +} php_json_scanner; + +typedef struct _php_json_parser php_json_parser; + +typedef int (*php_json_parser_func_array_create_t)( + php_json_parser *parser, zval *array); +typedef int (*php_json_parser_func_array_append_t)( + php_json_parser *parser, zval *array, zval *zvalue); +typedef int (*php_json_parser_func_array_start_t)( + php_json_parser *parser); +typedef int (*php_json_parser_func_array_end_t)( + php_json_parser *parser, zval *object); +typedef int (*php_json_parser_func_object_create_t)( + php_json_parser *parser, zval *object); +typedef int (*php_json_parser_func_object_update_t)( + php_json_parser *parser, zval *object, zend_string *key, zval *zvalue); +typedef int (*php_json_parser_func_object_start_t)( + php_json_parser *parser); +typedef int (*php_json_parser_func_object_end_t)( + php_json_parser *parser, zval *object); + +typedef struct _php_json_parser_methods { + php_json_parser_func_array_create_t array_create; + php_json_parser_func_array_append_t array_append; + php_json_parser_func_array_start_t array_start; + php_json_parser_func_array_end_t array_end; + php_json_parser_func_object_create_t object_create; + php_json_parser_func_object_update_t object_update; + php_json_parser_func_object_start_t object_start; + php_json_parser_func_object_end_t object_end; +} php_json_parser_methods; + +struct _php_json_parser { + php_json_scanner scanner; + zval *return_value; + int depth; + int max_depth; + php_json_parser_methods methods; +}; + +#if PHP_VERSION_ID >= 70100 +void (*zai_json_parser_init)(php_json_parser *parser, zval *return_value, const char *str, size_t str_len, int options, int max_depth); +int (*zai_json_parse)(php_json_parser *parser); + +__attribute__((weak)) void php_json_parser_init(php_json_parser *parser, zval *return_value, const char *str, size_t str_len, int options, int max_depth); +__attribute__((weak)) int php_json_parse(php_json_parser *parser); +#endif + #if PHP_VERSION_ID < 70100 void (*zai_json_encode)(smart_str *buf, zval *val, int options TSRMLS_DC); void (*zai_json_decode_ex)(zval *return_value, char *str, int str_len, int options, long depth TSRMLS_DC); @@ -9,27 +78,26 @@ __attribute__((weak)) void php_json_decode_ex(zval *return_value, char *str, int long depth TSRMLS_DC); #elif PHP_VERSION_ID < 80000 int (*zai_json_encode)(smart_str *buf, zval *val, int options); -int (*zai_json_decode_ex)(zval *return_value, char *str, size_t str_len, zend_long options, zend_long depth); __attribute__((weak)) int php_json_encode(smart_str *buf, zval *val, int options); -__attribute__((weak)) int php_json_decode_ex(zval *return_value, char *str, size_t str_len, zend_long options, - zend_long depth); #else int (*zai_json_encode)(smart_str *buf, zval *val, int options); -int (*zai_json_decode_ex)(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth); __attribute__((weak)) int php_json_encode(smart_str *buf, zval *val, int options); -__attribute__((weak)) int php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, - zend_long depth); #endif #ifndef __APPLE__ __attribute__((weak)) zend_class_entry *php_json_serializable_ce; #endif bool zai_json_setup_bindings(void) { - if (php_json_encode && php_json_decode_ex && php_json_serializable_ce) { + if (php_json_encode && php_json_serializable_ce) { zai_json_encode = php_json_encode; +#if PHP_VERSION_ID < 70100 zai_json_decode_ex = php_json_decode_ex; +#else + zai_json_parse = php_json_parse; + zai_json_parser_init = php_json_parser_init; +#endif return true; } @@ -42,10 +110,22 @@ bool zai_json_setup_bindings(void) { zai_json_encode = DL_FETCH_SYMBOL(json_me->handle, "_php_json_encode"); } +#if PHP_VERSION_ID < 70100 zai_json_decode_ex = DL_FETCH_SYMBOL(json_me->handle, "php_json_decode_ex"); if (zai_json_decode_ex == NULL) { zai_json_decode_ex = DL_FETCH_SYMBOL(json_me->handle, "_php_json_decode_ex"); } +#else + zai_json_parse = DL_FETCH_SYMBOL(json_me->handle, "php_json_parse"); + if (zai_json_parse == NULL) { + zai_json_parse = DL_FETCH_SYMBOL(json_me->handle, "_php_json_parse"); + } + + zai_json_parser_init = DL_FETCH_SYMBOL(json_me->handle, "php_json_parser_init"); + if (zai_json_parser_init == NULL) { + zai_json_parser_init = DL_FETCH_SYMBOL(json_me->handle, "_php_json_parser_init"); + } +#endif zend_class_entry **tmp_json_serializable_ce = (zend_class_entry **)DL_FETCH_SYMBOL(json_me->handle, "php_json_serializable_ce"); if (tmp_json_serializable_ce == NULL) { @@ -55,5 +135,140 @@ bool zai_json_setup_bindings(void) { php_json_serializable_ce = *tmp_json_serializable_ce; } - return zai_json_encode && zai_json_decode_ex; + return zai_json_encode != NULL; +} + +void zai_json_release_persistent_array(HashTable *ht) { +#if PHP_VERSION_ID < 70300 + if (--GC_REFCOUNT(ht) == 0) +#else + if (GC_DELREF(ht) == 0) +#endif + { + zend_hash_destroy(ht); + free(ht); + } +} + +void zai_json_dtor_pzval(zval *pval) { + if (Z_TYPE_P(pval) == IS_ARRAY) { + zai_json_release_persistent_array(Z_ARR_P(pval)); + } else { + zval_internal_ptr_dtor(pval); + } + // Prevent an accidental use after free + ZVAL_UNDEF(pval); +} + +static inline zend_string *zai_json_persist_string(zend_string *str) { + if (GC_FLAGS(str) & IS_STR_PERSISTENT) { + return str; + } + + zend_string *persistent = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), true); + zend_string_release(str); + return persistent; +} + +#if PHP_VERSION_ID < 70100 +static zend_always_inline void zend_hash_release(zend_array *array) { + if (!(GC_FLAGS(array) & IS_ARRAY_IMMUTABLE)) { + if (--GC_REFCOUNT(array) == 0) { + zend_hash_destroy(array); + pefree(array, array->u.flags & HASH_FLAG_PERSISTENT); + } + } +} + +static void zai_json_persist_zval(zval *in) { + if (Z_TYPE_P(in) == IS_ARRAY) { + zend_array *array = Z_ARR_P(in); + ZVAL_NEW_PERSISTENT_ARR(in); + zend_hash_init(Z_ARR_P(in), array->nTableSize, NULL, zai_json_dtor_pzval, 1); + if (zend_hash_num_elements(array)) { + Bucket *bucket; + ZEND_HASH_FOREACH_BUCKET(array, bucket) { + zai_json_persist_zval(&bucket->val); + if (bucket->key) { + zend_hash_str_add_new(Z_ARR_P(in), ZSTR_VAL(bucket->key), ZSTR_LEN(bucket->key), &bucket->val); + } else { + zend_hash_index_add_new(Z_ARR_P(in), bucket->h, &bucket->val); + } + ZVAL_NULL(&bucket->val); + } ZEND_HASH_FOREACH_END(); + } + zend_hash_release(array); + } else if (Z_TYPE_P(in) == IS_STRING) { + ZVAL_STR(in, zai_json_persist_string(Z_STR_P(in))); + } +} +#else +static int zai_json_parser_array_create(php_json_parser *parser, zval *array) { + (void)parser; + + HashTable *ht = malloc(sizeof(HashTable)); + zend_hash_init(ht, 8, NULL, zai_json_dtor_pzval, 1); + ZVAL_ARR(array, ht); + return SUCCESS; +} + +static int zai_json_parser_array_append(php_json_parser *parser, zval *array, zval *zvalue) { + (void)parser; + + if (Z_TYPE_P(zvalue) == IS_STRING) { + Z_STR_P(zvalue) = zai_json_persist_string(Z_STR_P(zvalue)); + } + + zend_hash_next_index_insert(Z_ARRVAL_P(array), zvalue); + return SUCCESS; +} + +static int zai_json_parser_object_update(php_json_parser *parser, zval *object, zend_string *key, zval *zvalue) { + (void)parser; + + if (Z_TYPE_P(zvalue) == IS_STRING) { + Z_STR_P(zvalue) = zai_json_persist_string(Z_STR_P(zvalue)); + } + + zend_ulong idx; + if (ZEND_HANDLE_NUMERIC(key, idx)) { + zend_hash_index_update(Z_ARR_P(object), idx, zvalue); + } else { + key = zai_json_persist_string(key); + zend_hash_update(Z_ARR_P(object), key, zvalue); + zend_string_release(key); + } + return SUCCESS; +} +#endif + +int zai_json_decode_assoc_safe(zval *return_value, const char *str, int str_len, long depth, bool persistent) { +#if PHP_VERSION_ID < 70100 + ZVAL_UNDEF(return_value); + zai_json_decode_ex(return_value, (char *)str, str_len, PHP_JSON_OBJECT_AS_ARRAY, depth); + if (persistent) { + zai_json_persist_zval(return_value); + } + return Z_ISUNDEF_P(return_value) ? FAILURE : SUCCESS; +#else + php_json_parser parser; + zai_json_parser_init(&parser, return_value, str, str_len, PHP_JSON_OBJECT_AS_ARRAY, (int) depth); + + if (persistent) { + parser.methods.array_create = zai_json_parser_array_create; + parser.methods.array_append = zai_json_parser_array_append; + parser.methods.object_create = zai_json_parser_array_create; + parser.methods.object_update = zai_json_parser_object_update; + } + + if (zai_json_parse(&parser)) { + return FAILURE; + } + + if (persistent && Z_TYPE_P(return_value) == IS_STRING) { + ZVAL_STR(return_value, zai_json_persist_string(Z_STR_P(return_value))); + } + + return SUCCESS; +#endif } diff --git a/zend_abstract_interface/json/json.h b/zend_abstract_interface/json/json.h index 74c7987ebce..848a2c43540 100644 --- a/zend_abstract_interface/json/json.h +++ b/zend_abstract_interface/json/json.h @@ -25,28 +25,15 @@ * on buf or risk shenanigans. */ -#if PHP_VERSION_ID < 70100 -extern void (*zai_json_encode)(smart_str *buf, zval *val, int options TSRMLS_DC); -extern void (*zai_json_decode_ex)(zval *return_value, char *str, int str_len, int options, long depth TSRMLS_DC); +int zai_json_decode_assoc_safe(zval *return_value, const char *str, int str_len, long depth, bool persistent); -static inline void zai_json_decode_assoc(zval *return_value, const char *str, int str_len, long depth TSRMLS_DC) { - zai_json_decode_ex(return_value, (char *)str, str_len, PHP_JSON_OBJECT_AS_ARRAY, depth TSRMLS_CC); -} +#if PHP_VERSION_ID < 70100 +extern void (*zai_json_encode)(smart_str *buf, zval *val, int options); +extern void (*zai_json_decode_ex)(zval *return_value, char *str, int str_len, int options, long depth); #elif PHP_VERSION_ID < 80000 extern int (*zai_json_encode)(smart_str *buf, zval *val, int options); -extern int (*zai_json_decode_ex)(zval *return_value, char *str, size_t str_len, zend_long options, zend_long depth); - -static inline int zai_json_decode_assoc(zval *return_value, const char *str, int str_len, zend_long depth) { - return zai_json_decode_ex(return_value, (char *)str, str_len, PHP_JSON_OBJECT_AS_ARRAY, depth); -} #else extern int (*zai_json_encode)(smart_str *buf, zval *val, int options); -extern int (*zai_json_decode_ex)(zval *return_value, const char *str, size_t str_len, zend_long options, - zend_long depth); - -static inline int zai_json_decode_assoc(zval *return_value, const char *str, int str_len, zend_long depth) { - return zai_json_decode_ex(return_value, str, str_len, PHP_JSON_OBJECT_AS_ARRAY, depth); -} #endif #ifdef __APPLE__ @@ -55,6 +42,8 @@ extern __attribute__((weak, weak_import)) zend_class_entry *php_json_serializabl extern __attribute__((weak)) zend_class_entry *php_json_serializable_ce; #endif +void zai_json_release_persistent_array(HashTable *ht); +void zai_json_dtor_pzval(zval *pval); bool zai_json_setup_bindings(void); #endif // ZAI_JSON_H diff --git a/zend_abstract_interface/json/tests/json.cc b/zend_abstract_interface/json/tests/json.cc index 6095a62ec23..3a48aafdd57 100644 --- a/zend_abstract_interface/json/tests/json.cc +++ b/zend_abstract_interface/json/tests/json.cc @@ -47,7 +47,7 @@ TEST_JSON("decode", { ZVAL_NULL(&val); - zai_json_decode_ex(&val, (char *)buf.s->val, buf.s->len, 0, 1); + zai_json_decode_assoc_safe(&val, (char *)buf.s->val, buf.s->len, 0, 1); REQUIRE(Z_TYPE(val) == IS_LONG); REQUIRE(Z_LVAL(val) == 42); From ef0b61af3ad48dfc78f2245e19c2b5182297bff1 Mon Sep 17 00:00:00 2001 From: Alexandre Choura <42672104+PROFeNoM@users.noreply.github.com> Date: Sun, 19 Nov 2023 18:29:56 +0100 Subject: [PATCH 09/16] OpenTelemetry API (#2332) * Add OTtel composer scenario and API stubs * Start implementing Span.php * Start implementing SpanBuilder.php * Roughly implement the base api skeleton * Debugging composer autoload * test: Add and Pass OTel's Unit Span Tests * Expand arrays and public properties of objects in meta into nested-tag format This will be required for the display of OpenTelemetry array attributes. Note that this also removes the parenthesis from boolean/null values in order to be consistent with other languages, i.e. system-tests. Signed-off-by: Bob Weinand * Start Tracing Tests with OTel API * On par w/ Java * Use traceparent and implement SpanContextInterface * Interoperability * Add even more tests * Remove dead code * Add B3 & W3C Propagation Tests * Check for DDTrace\\install_hook existence * Add Baggage API Tests * Add resource attributes * test: Add Fiber tests for PHP 8.1+ * feat: OTel Remapping - Span operation name convention * style: Stop using largeBaseConvert * fix: Revert unwanted change * fix: [Convention] Use a bare class instead of an enum (PHP8.1+ only) * Remove OpenTelemetryIntegration (not used) * test: Run OTel testsuite in CI * test: Exclude OpenTelemetry from static analysis & skip command args check * test: Move testFiberInteroperabilityStackSwitch to FiberTest (PHP8.1+) * installation: Copy src/ dir * Revert "installation: Copy src/ dir" This reverts commit 5a2aafb2f3fdc1b36b2693abcd8db09b7437266a. * fix: Copy `src/` dir in the `dd-library-php` artifacts * fix: Copy the `src/` directory during the setup * Switch stack to related span before closing * feat: Handle nested args in metrics array * ci: Make installer_tests pass + Fix Memory Leak * feat: Acknowledge span attribute count limit * Handle special attributes in the Core * (wip) Handle starting a span with 64-bit trace identifiers * Revise span name convention * Fix sigsegv * Fix convention typo * Revise special attributes * Test lowercase operation name * Handle OTel special attributes * Revert "(wip) Handle starting a span with 64-bit trace identifiers" This reverts commit cf8146f37e33ce62ae97d0b443db7b376ccfee51. * fix: Wrong duration used by span processors (Jaeger) * style: Naming conventions * Use zend_string_tolower instead of zend_str_tolower * Typo * Please the compiler * Typo * Prevent heap-use-after-free * Prevent heap-use-after-free * Prevent heap-use-after-free * Prevent heap-use-after-free * Prevent heap-use-after-free * test: Add Logs Appender tests (Monolog) + Remove RC2 -> 1.0.0 * CR: README Typo + use latest stable OTel version in tests * CR: Revert minimum stability to dev * Remove useless comment * Handle multiple intertwined trace states * Remove dd parts (if any) before assigning to root span's trace state * Don't end an already-ended span & verify if span was ended after ending procedure * naming: no longer use otel_unknown and default to span kind (i.e., internal) * test: Update _dd.base_service * Update test_special_attributes_bis.phpt * Don't depend anymore on AttributesBuilder for consistency purposes * Remove unused imports * Remove unused imports & (experiment) Run spanProcessor->onEnd() exclusively on Span->end() * fix: Timing issues with span processors (See Notes) Notes: - A span wasn't finished when calling the span processors, hence the span was considered as not ended and its duration marked to 0 - We can use an instance variable $endEpochNanos to circumvent this issue, but it's a bit uglier than just running the span processors after the (dd) span was closed, considering the span cannot be written onto (ReadableSpanInterface) - With partial flushing, there may be an issue depending on whether the spans will be readable after flushed (if flushed) * Update tests to pass with 128-bit enabled by default * Update tests * Update tests (Sampling & Log) --------- Signed-off-by: Bob Weinand Co-authored-by: Bob Weinand --- Makefile | 9 + README.md | 1 + bridge/_files_integrations.php | 1 + bridge/autoload.php | 30 +- .../dd_register_optional_deps_autoloader.php | 11 + composer.json | 10 + datadog-setup.php | 7 + ext/configuration.h | 1 + ext/ddtrace.stub.php | 7 +- ext/ddtrace_arginfo.h | 6 +- ext/handlers_http.h | 5 + ext/serializer.c | 61 +- phpstan.ddtrace.neon | 1 + src/DDTrace/OpenTelemetry/Context.php | 254 +++++ src/DDTrace/OpenTelemetry/Span.php | 403 ++++++++ src/DDTrace/OpenTelemetry/SpanBuilder.php | 215 +++++ src/DDTrace/OpenTelemetry/SpanContext.php | 129 +++ src/Integrations/Util/Convention.php | 60 ++ tests/Common/BaseTestCase.php | 4 +- .../Integration/API/BaggageTest.php | 108 +++ .../Integration/API/TracerTest.php | 873 +++++++++++++++++ .../Integration/Context/FiberTest.php | 251 +++++ .../Integration/InteroperabilityTest.php | 911 ++++++++++++++++++ .../Integration/Logs/HandlerTest.php | 164 ++++ .../Integration/SDK/SpanBuilderTest.php | 666 +++++++++++++ .../Integration/SDK/SpanContextTest.php | 91 ++ .../Integration/SDK/SpanProcessorTest.php | 152 +++ .../Integration/SDK/TracerTest.php | 172 ++++ .../Unit/API/Baggage/BaggageTest.php | 130 +++ .../Propagation/BaggagePropagatorTest.php | 183 ++++ .../Unit/API/Trace/NonRecordingSpanTest.php | 106 ++ .../Unit/API/Trace/SpanContextTest.php | 100 ++ .../Unit/API/Trace/TraceTest.php | 123 +++ .../Unit/Context/ContextStorageTest.php | 100 ++ .../Unit/Context/ContextTest.php | 197 ++++ .../MultiTextMapPropagatorTest.php | 124 +++ .../Propagation/NoopTextMapPropagatorTest.php | 35 + .../OpenTelemetry/Unit/Context/ScopeTest.php | 127 +++ .../Propagation/B3/B3MultiPropagatorTest.php | 481 +++++++++ .../Unit/Propagation/B3/B3PropagatorTest.php | 326 +++++++ .../Propagation/B3/B3SinglePropagatorTest.php | 380 ++++++++ .../Propagation/W3C/W3CPropagatorTest.php | 149 +++ .../Unit/SDK/Trace/NonRecordingSpanTest.php | 26 + .../Unit/SDK/Trace/SpanBuilderTest.php | 88 ++ tests/Unit/Util/ConventionTest.php | 62 ++ tests/composer.json | 11 + .../dd_trace_span_link_with_exception.phpt | 2 +- .../distributed_tracestate_consumption.phpt | 32 + .../distributed_tracing_curl_drop_dm.phpt | 2 +- tests/ext/test_special_attributes.phpt | 63 ++ tests/ext/test_special_attributes_bis.phpt | 65 ++ tests/phpunit.xml | 10 + tooling/bin/generate-final-artifact.sh | 2 + 53 files changed, 7509 insertions(+), 18 deletions(-) create mode 100644 src/DDTrace/OpenTelemetry/Context.php create mode 100644 src/DDTrace/OpenTelemetry/Span.php create mode 100644 src/DDTrace/OpenTelemetry/SpanBuilder.php create mode 100644 src/DDTrace/OpenTelemetry/SpanContext.php create mode 100644 src/Integrations/Util/Convention.php create mode 100644 tests/OpenTelemetry/Integration/API/BaggageTest.php create mode 100644 tests/OpenTelemetry/Integration/API/TracerTest.php create mode 100644 tests/OpenTelemetry/Integration/Context/FiberTest.php create mode 100644 tests/OpenTelemetry/Integration/InteroperabilityTest.php create mode 100644 tests/OpenTelemetry/Integration/Logs/HandlerTest.php create mode 100644 tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php create mode 100644 tests/OpenTelemetry/Integration/SDK/SpanContextTest.php create mode 100644 tests/OpenTelemetry/Integration/SDK/SpanProcessorTest.php create mode 100644 tests/OpenTelemetry/Integration/SDK/TracerTest.php create mode 100644 tests/OpenTelemetry/Unit/API/Baggage/BaggageTest.php create mode 100644 tests/OpenTelemetry/Unit/API/Baggage/Propagation/BaggagePropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/API/Trace/NonRecordingSpanTest.php create mode 100644 tests/OpenTelemetry/Unit/API/Trace/SpanContextTest.php create mode 100644 tests/OpenTelemetry/Unit/API/Trace/TraceTest.php create mode 100644 tests/OpenTelemetry/Unit/Context/ContextStorageTest.php create mode 100644 tests/OpenTelemetry/Unit/Context/ContextTest.php create mode 100644 tests/OpenTelemetry/Unit/Context/Propagation/MultiTextMapPropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/Context/Propagation/NoopTextMapPropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/Context/ScopeTest.php create mode 100644 tests/OpenTelemetry/Unit/Propagation/B3/B3MultiPropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/Propagation/B3/B3PropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/Propagation/B3/B3SinglePropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/Propagation/W3C/W3CPropagatorTest.php create mode 100644 tests/OpenTelemetry/Unit/SDK/Trace/NonRecordingSpanTest.php create mode 100644 tests/OpenTelemetry/Unit/SDK/Trace/SpanBuilderTest.php create mode 100644 tests/Unit/Util/ConventionTest.php create mode 100644 tests/ext/distributed_tracestate_consumption.phpt create mode 100644 tests/ext/test_special_attributes.phpt create mode 100644 tests/ext/test_special_attributes_bis.phpt diff --git a/Makefile b/Makefile index bbe5c89bc19..6ae7305d7b9 100644 --- a/Makefile +++ b/Makefile @@ -689,6 +689,7 @@ TEST_INTEGRATIONS_74 := \ test_integrations_monolog1 \ test_integrations_monolog2 \ test_integrations_mysqli \ + test_opentelemetry_1 \ test_integrations_pdo \ test_integrations_elasticsearch7 \ test_integrations_elasticsearch8 \ @@ -755,6 +756,7 @@ TEST_INTEGRATIONS_80 := \ test_integrations_monolog1 \ test_integrations_monolog2 \ test_integrations_mysqli \ + test_opentelemetry_1 \ test_integrations_pdo \ test_integrations_elasticsearch7 \ test_integrations_guzzle5 \ @@ -802,6 +804,7 @@ TEST_INTEGRATIONS_81 := \ test_integrations_monolog2 \ test_integrations_monolog3 \ test_integrations_mysqli \ + test_opentelemetry_1 \ test_integrations_guzzle7 \ test_integrations_pcntl \ test_integrations_pdo \ @@ -847,6 +850,7 @@ TEST_INTEGRATIONS_82 := \ test_integrations_monolog2 \ test_integrations_monolog3 \ test_integrations_mysqli \ + test_opentelemetry_1 \ test_integrations_guzzle7 \ test_integrations_pcntl \ test_integrations_pdo \ @@ -950,6 +954,11 @@ test_distributed_tracing: global_test_run_dependencies test_metrics: global_test_run_dependencies $(call run_tests,--testsuite=metrics $(TESTS)) +test_opentelemetry_1: global_test_run_dependencies + rm -f tests/.scenarios.lock/opentelemetry1/composer.lock + $(MAKE) test_scenario_opentelemetry1 + $(shell [ $(PHP_MAJOR_MINOR) -ge 81 ] && echo "OTEL_PHP_FIBERS_ENABLED=1" || echo "") DD_TRACE_OTEL_ENABLED=1 DD_TRACE_GENERATE_ROOT_SPAN=0 $(call run_tests,--testsuite=opentelemetry1 $(TESTS)) + test_opentracing_beta5: global_test_run_dependencies $(MAKE) test_scenario_opentracing_beta5 $(call run_tests,tests/OpenTracerUnit) diff --git a/README.md b/README.md index 478bb86ec0e..8cf63e41fe2 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![CircleCI](https://circleci.com/gh/DataDog/dd-trace-php/tree/master.svg?style=svg)](https://circleci.com/gh/DataDog/dd-trace-php/tree/master) [![CodeCov](https://codecov.io/gh/DataDog/dd-trace-php/branch/master/graph/badge.svg?token=eXio8H7vwF)](https://codecov.io/gh/DataDog/dd-trace-php) [![OpenTracing Badge](https://img.shields.io/badge/OpenTracing-enabled-blue.svg)](http://opentracing.io) +[![OpenTelemetry Badge](https://img.shields.io/badge/OpenTelemetry-enabled-blue.svg)](https://opentelemetry.io) [![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%205.4-8892BF.svg)](https://php.net/) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](LICENSE) [![Packagist Version](https://img.shields.io/packagist/v/datadog/dd-trace.svg)](https://packagist.org/packages/datadog/dd-trace) diff --git a/bridge/_files_integrations.php b/bridge/_files_integrations.php index f9044a9c564..c6d1dda04a8 100644 --- a/bridge/_files_integrations.php +++ b/bridge/_files_integrations.php @@ -47,6 +47,7 @@ __DIR__ . '/../src/Integrations/Integrations/WordPress/V1/WordPressIntegrationLoader.php', __DIR__ . '/../src/Integrations/Integrations/WordPress/V2/WordPressIntegrationLoader.php', __DIR__ . '/../src/Integrations/Integrations/ZendFramework/ZendFrameworkIntegration.php', + __DIR__ . '/../src/Integrations/Util/Convention.php', __DIR__ . '/../src/Integrations/Util/Runtime.php', __DIR__ . '/../src/Integrations/Util/Versions.php', __DIR__ . '/../src/Integrations/Util/ObjectKVStore.php', diff --git a/bridge/autoload.php b/bridge/autoload.php index a1c64f79745..dab8ebec0ca 100644 --- a/bridge/autoload.php +++ b/bridge/autoload.php @@ -12,7 +12,6 @@ // internal userland classes and loaded by composer; // - automatic instrumentation runs before it is known that composer exists, and after any `opcache.preload` script; - // Do not trigger the autoloading mechanism if the class is not defined, so 'terminal' autoloaders - that trigger // errors if a class was not found - are supported. // - Class `DDTrace\ComposerBootstrap` is declared in `src/api/ComposerBootstrap.php` and it is loaded when the @@ -36,8 +35,10 @@ spl_autoload_register(function ($class) use ($apiLoadedViaComposerClass) { // If $class is not a DDTrace class, move quickly to the next autoloader $prefix = 'DDTrace\\'; + $openTelemetry = 'OpenTelemetry\\'; $len = strlen($prefix); - if (strncmp($prefix, $class, $len) !== 0) { + $otelLen = strlen($openTelemetry); + if (strncmp($prefix, $class, $len) !== 0 && strncmp($openTelemetry, $class, $otelLen) !== 0) { // move to the next registered autoloader return; } @@ -96,8 +97,10 @@ spl_autoload_register(function ($class) use ($tracerFiles, $tracerFilesWithComposerLoaded, $apiLoadedViaComposerClass) { // If $class is not a DDTrace class, move quickly to the next autoloader $prefix = 'DDTrace\\'; + $openTelemetry = 'OpenTelemetry\\'; $len = strlen($prefix); - if (strncmp($prefix, $class, $len) !== 0) { + $otelLen = strlen($openTelemetry); + if (strncmp($prefix, $class, $len) !== 0 && strncmp($openTelemetry, $class, $otelLen) !== 0) { // move to the next registered autoloader return; } @@ -116,6 +119,27 @@ } }); +if (function_exists('DDTrace\\install_hook')) { + \DDTrace\install_hook( + 'Composer\Autoload\ClassLoader::register', + null, + function (\DDTrace\HookData $hook) { + $args = $hook->args; + if (!empty($args)) { + $prepend = $args[0]; + if ($prepend) { + // We need to unregister and register dd's autoload, prepending it to the list of autoloaders. + // This is needed because composer's autoloader is prepended to the list of autoloaders, and we need to + // make sure that dd's autoloader is prepended to composer's autoloader. + spl_autoload_unregister('DDTrace\Bridge\OptionalDepsAutoloader::load'); + spl_autoload_register('DDTrace\Bridge\OptionalDepsAutoloader::load', true, true); + } + } + } + ); +} + + function ddtrace_legacy_tracer_autoloading_possible() { } diff --git a/bridge/dd_register_optional_deps_autoloader.php b/bridge/dd_register_optional_deps_autoloader.php index 835444f887e..feddff1b455 100644 --- a/bridge/dd_register_optional_deps_autoloader.php +++ b/bridge/dd_register_optional_deps_autoloader.php @@ -15,6 +15,7 @@ class OptionalDepsAutoloader private static $autoloaderMapping = [ "DDTrace\\Integrations\\ZendFramework\\V1\\TraceRequest" => 'DDTrace/Integrations/ZendFramework/V1/TraceRequest.php', "DDTrace\\Log\\PsrLogger" => 'api/Log/PsrLogger.php', + "DDTrace\\OpenTelemetry\\API\\Trace\\SpanContext" => 'DDTrace/OpenTelemetry/SpanContext.php', "DDTrace\\OpenTracer\\Tracer" => 'DDTrace/OpenTracer/Tracer.php', "DDTrace\\OpenTracer\\Span" => 'DDTrace/OpenTracer/Span.php', "DDTrace\\OpenTracer\\Scope" => 'DDTrace/OpenTracer/Scope.php', @@ -35,6 +36,12 @@ class OptionalDepsAutoloader "DDTrace\\NoopSpanContext" => 'api/NoopSpanContext.php', ]; + private static $otelAutoloaderMapping = [ + "OpenTelemetry\\SDK\\Trace\\Span" => 'DDTrace/OpenTelemetry/Span.php', + "OpenTelemetry\\SDK\\Trace\\SpanBuilder" => 'DDTrace/OpenTelemetry/SpanBuilder.php', + "OpenTelemetry\\Context\\Context" => 'DDTrace/OpenTelemetry/Context.php', + ]; + /** * @param string $class */ @@ -43,6 +50,10 @@ public static function load($class) if (array_key_exists($class, self::$autoloaderMapping)) { require __DIR__ . '/../src/' . self::$autoloaderMapping[$class]; } + + if (function_exists('dd_trace_env_config') && dd_trace_env_config('DD_TRACE_OTEL_ENABLED') && array_key_exists($class, self::$otelAutoloaderMapping)) { + require __DIR__ . '/../src/' . self::$otelAutoloaderMapping[$class]; + } } } diff --git a/composer.json b/composer.json index cad7633c585..926fdd19047 100644 --- a/composer.json +++ b/composer.json @@ -83,6 +83,16 @@ }, "extra": { "scenarios": { + "opentelemetry1": { + "require": { + "open-telemetry/sdk": "@stable", + "open-telemetry/extension-propagator-b3": "@stable", + "open-telemetry/opentelemetry-logger-monolog": "@stable" + }, + "scenario-options": { + "create-lockfile": false + } + }, "opentracing_beta5": { "require": { "opentracing/opentracing": "1.0.0-beta5" diff --git a/datadog-setup.php b/datadog-setup.php index c43991c38e3..56b08baf5bb 100644 --- a/datadog-setup.php +++ b/datadog-setup.php @@ -459,6 +459,7 @@ function install($options) $tmpArchiveAppsecEtc = "{$tmpArchiveAppsecRoot}/etc"; $tmpArchiveProfilingRoot = $tmpDir . '/dd-library-php/profiling'; $tmpBridgeDir = $tmpArchiveTraceRoot . '/bridge'; + $tmpSrcDir = $tmpArchiveTraceRoot . '/src'; execute_or_exit("Cannot create directory '$tmpDir'", "mkdir -p " . escapeshellarg($tmpDir)); register_shutdown_function(function () use ($tmpDir) { execute_or_exit("Cannot remove temporary directory '$tmpDir'", "rm -rf " . escapeshellarg($tmpDir)); @@ -498,6 +499,12 @@ function install($options) "Cannot copy files from '$tmpBridgeDir' to '$installDirSourcesDir'", "cp -r " . escapeshellarg("$tmpBridgeDir") . ' ' . escapeshellarg($installDirSourcesDir) ); + if (file_exists($tmpSrcDir)) { + execute_or_exit( + "Cannot copy files from '$tmpSrcDir' to '$installDirSourcesDir'", + "cp -r " . escapeshellarg("$tmpSrcDir") . ' ' . escapeshellarg($installDirSourcesDir) + ); + } echo "Installed required source files to '$installDir'\n"; // Appsec helper and rules diff --git a/ext/configuration.h b/ext/configuration.h index f41609d15a8..cb289e11b5f 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -174,6 +174,7 @@ enum ddtrace_dbm_propagation_mode { CONFIG(SET, DD_TRACE_WORDPRESS_ADDITIONAL_ACTIONS, "") \ CONFIG(BOOL, DD_TRACE_WORDPRESS_CALLBACKS, "false") \ CONFIG(BOOL, DD_TRACE_WORDPRESS_ENHANCED_INTEGRATION, "false") \ + CONFIG(BOOL, DD_TRACE_OTEL_ENABLED, "false") \ DD_INTEGRATIONS #define CALIAS CONFIG diff --git a/ext/ddtrace.stub.php b/ext/ddtrace.stub.php index 1c9ee4733db..9b88a09fda3 100644 --- a/ext/ddtrace.stub.php +++ b/ext/ddtrace.stub.php @@ -136,7 +136,7 @@ class SpanData { public function getDuration(): int {} /** - * @return int Get the start time of the span + * @return int Get the start time of the span, in nanoseconds */ public function getStartTime(): int {} @@ -388,7 +388,7 @@ function active_span(): null|SpanData {} * @return SpanData|null 'null' if tracing isn't enabled or if the active stack doesn't have a root span, * else the root span of the active stack */ - function root_span(): null|SpanData {} + function root_span(): null|RootSpanData {} /** * Start a new custom user-span on the top of the stack. If no active span exists, the new created span will be a @@ -413,9 +413,10 @@ function close_span(float $finishTime = 0): false|null {} * * More precisely, a new root span stack will be created and switched on to, and a new span started. * + * @param float $startTime Start time of the span in seconds. * @return SpanData The newly created root span */ - function start_trace_span(): SpanData {} + function start_trace_span(float $startTime = 0): SpanData {} /** * Get the active stack diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 7b644655761..6563d45b5db 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a7d10bf6cca1521fbf2d8ab00d086c3de6c3c3e1 */ + * Stub hash: 85f2b3a4b45cb7685e5ec115eefa59ae7f20cd79 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_trace_method, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, className, IS_STRING, 0) @@ -45,7 +45,8 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_DDTrace_active_span, 0, 0, DDTrace\\SpanData, 1) ZEND_END_ARG_INFO() -#define arginfo_DDTrace_root_span arginfo_DDTrace_active_span +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_DDTrace_root_span, 0, 0, DDTrace\\RootSpanData, 1) +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_DDTrace_start_span, 0, 0, DDTrace\\SpanData, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, startTime, IS_DOUBLE, 0, "0") @@ -56,6 +57,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_close_span, 0, 0, IS_FAL ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_DDTrace_start_trace_span, 0, 0, DDTrace\\SpanData, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, startTime, IS_DOUBLE, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_DDTrace_active_stack, 0, 0, DDTrace\\SpanStack, 1) diff --git a/ext/handlers_http.h b/ext/handlers_http.h index bca69daf3dc..ff9b6541755 100644 --- a/ext/handlers_http.h +++ b/ext/handlers_http.h @@ -75,6 +75,11 @@ static inline zend_string *ddtrace_format_tracestate(zend_string *tracestate, ze smart_str_append(&str, tracestate); } + if (str.s && ZSTR_VAL(str.s)[ZSTR_LEN(str.s) - 1] == ',') { + ZSTR_VAL(str.s)[ZSTR_LEN(str.s) - 1] = '\0'; + ZSTR_LEN(str.s)--; + } + if (str.s) { zend_string *full_tracestate = zend_strpprintf(0, "%s%.*s", hasdd ? "dd=" : "", (int)ZSTR_LEN(str.s), ZSTR_VAL(str.s)); smart_str_free(&str); diff --git a/ext/serializer.c b/ext/serializer.c index be25f50e004..3363587ceea 100644 --- a/ext/serializer.c +++ b/ext/serializer.c @@ -1232,17 +1232,29 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { add_assoc_long(el, "start", span->start); add_assoc_long(el, "duration", span->duration); + zend_array *meta = ddtrace_property_array(&span->property_meta); + // SpanData::$name defaults to fully qualified called name (set at span close) + zval *operation_name = zend_hash_str_find(meta, ZEND_STRL("operation.name")); zval *prop_name = &span->property_name; - ZVAL_DEREF(prop_name); - if (Z_TYPE_P(prop_name) > IS_NULL) { + zend_string *lcname; + if (operation_name) { + lcname = zend_string_tolower(Z_STR_P(operation_name)); zval prop_name_as_string; - ddtrace_convert_to_string(&prop_name_as_string, prop_name); + ZVAL_STR_COPY(&prop_name_as_string, lcname); prop_name = zend_hash_str_update(Z_ARR_P(el), ZEND_STRL("name"), &prop_name_as_string); + } else { + ZVAL_DEREF(prop_name); + if (Z_TYPE_P(prop_name) > IS_NULL) { + zval prop_name_as_string; + ddtrace_convert_to_string(&prop_name_as_string, prop_name); + prop_name = zend_hash_str_update(Z_ARR_P(el), ZEND_STRL("name"), &prop_name_as_string); + } } // SpanData::$resource defaults to SpanData::$name - zval *prop_resource = &span->property_resource; + zval * resource_name = zend_hash_str_find(meta, ZEND_STRL("resource.name")); + zval *prop_resource = resource_name ? resource_name : &span->property_resource; ZVAL_DEREF(prop_resource); zval prop_resource_as_string; ZVAL_UNDEF(&prop_resource_as_string); @@ -1256,13 +1268,23 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { _add_assoc_zval_copy(el, "resource", &prop_resource_as_string); } + if (resource_name) { + zend_hash_str_del(meta, ZEND_STRL("resource.name")); + } + // TODO: SpanData::$service defaults to parent SpanData::$service or DD_SERVICE if root span + zval *service_name = zend_hash_str_find(meta, ZEND_STRL("service.name")); zval *prop_service = &span->property_service; ZVAL_DEREF(prop_service); zval prop_service_as_string; - if (Z_TYPE_P(prop_service) > IS_NULL) { + ZVAL_UNDEF(&prop_service_as_string); + if (service_name) { + ddtrace_convert_to_string(&prop_service_as_string, service_name); + } else if (Z_TYPE_P(prop_service) > IS_NULL) { ddtrace_convert_to_string(&prop_service_as_string, prop_service); + } + if (Z_TYPE(prop_service_as_string) == IS_STRING) { zend_array *service_mappings = get_DD_SERVICE_MAPPING(); zval *new_name = zend_hash_find(service_mappings, Z_STR(prop_service_as_string)); if (new_name) { @@ -1280,8 +1302,13 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { add_assoc_zval(el, "service", &prop_service_as_string); } + if (service_name) { + zend_hash_str_del(meta, ZEND_STRL("service.name")); + } + // SpanData::$type is optional and defaults to 'custom' at the Agent level - zval *prop_type = &span->property_type; + zval *span_type = zend_hash_str_find(meta, ZEND_STRL("span.type")); + zval *prop_type = span_type ? span_type : &span->property_type; ZVAL_DEREF(prop_type); zval prop_type_as_string; ZVAL_UNDEF(&prop_type_as_string); @@ -1290,6 +1317,23 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { _add_assoc_zval_copy(el, "type", &prop_type_as_string); } + if (span_type) { + zend_hash_str_del(meta, ZEND_STRL("span.type")); + } + + zval *analytics_event = zend_hash_str_find(meta, ZEND_STRL("analytics.event")); + if (analytics_event) { + zval analytics_event_as_double; + if (Z_TYPE_P(analytics_event) == IS_STRING) { + ZVAL_DOUBLE(&analytics_event_as_double, zend_is_true(analytics_event)); // 'true' => 1.0, false => 0.0 + } else { + ZVAL_DOUBLE(&analytics_event_as_double, zval_get_double(analytics_event)); + } + zend_array *metrics = ddtrace_property_array(&span->property_metrics); + zend_hash_str_add_new(metrics, ZEND_STRL("_dd1.sr.eausr"), &analytics_event_as_double); + zend_hash_str_del(meta, ZEND_STRL("analytics.event")); + } + // Notify profiling for Endpoint Profiling. if (profiling_notify_trace_finished && ddtrace_span_is_entrypoint_root(span) && Z_TYPE(prop_resource_as_string) == IS_STRING) { zai_str type = Z_TYPE(prop_type_as_string) == IS_STRING @@ -1429,6 +1473,11 @@ void ddtrace_serialize_span_to_array(ddtrace_span_data *span, zval *array) { ZEND_HASH_FOREACH_END(); } + if (operation_name) { + zend_string_release(lcname); + zend_hash_str_del(meta, ZEND_STRL("operation.name")); + } + _serialize_meta(el, span); diff --git a/phpstan.ddtrace.neon b/phpstan.ddtrace.neon index ac632d90212..cccd1313402 100644 --- a/phpstan.ddtrace.neon +++ b/phpstan.ddtrace.neon @@ -8,6 +8,7 @@ parameters: excludes_analyse: - src/DDTrace/Integrations - src/DDTrace/OpenTracer + - src/DDTrace/OpenTelemetry reportUnmatchedIgnoredErrors: false ignoreErrors: - '#Access to an undefined property DDTrace\\Span::\$internalSpan#' diff --git a/src/DDTrace/OpenTelemetry/Context.php b/src/DDTrace/OpenTelemetry/Context.php new file mode 100644 index 00000000000..dcea348ffee --- /dev/null +++ b/src/DDTrace/OpenTelemetry/Context.php @@ -0,0 +1,254 @@ + */ + private array $context = []; + /** @var array */ + private array $contextKeys = []; + + private function __construct() + { + self::$spanContextKey = ContextKeys::span(); + } + + public static function createKey(string $key): ContextKeyInterface + { + return new ContextKey($key); + } + + /** + * @param ContextStorageInterface&ExecutionContextAwareInterface $storage + */ + public static function setStorage(ContextStorageInterface $storage): void + { + self::$storage = $storage; + } + + /** + * @return ContextStorageInterface&ExecutionContextAwareInterface + */ + public static function storage(): ContextStorageInterface + { + /** @psalm-suppress RedundantPropertyInitializationCheck */ + return self::$storage ??= new ContextStorage(); + } + + /** + * @param ContextInterface|false|null $context + * + * @internal OpenTelemetry + */ + public static function resolve($context, ?ContextStorageInterface $contextStorage = null): ContextInterface + { + $spanFromContext = API\Span::fromContext(self::storage()->current()); + if ($spanFromContext instanceof SDK\Span) { + $ddSpanFromContext = $spanFromContext->getDDSpan(); + self::deactivateEndedParents($ddSpanFromContext); + } + + self::activateParent(active_span()); + + return $context + ?? ($contextStorage ?? self::storage())->current() + ?: self::getRoot(); + } + + /** + * @internal + */ + public static function getRoot(): ContextInterface + { + static $empty; + + return $empty ??= new self(); + } + + private static function getDDInstrumentationScope(): InstrumentationScopeInterface + { + static $instrumentationScope; + + return $instrumentationScope ??= (new InstrumentationScopeFactory(new AttributesFactory()))->create('datadog'); + } + + public static function getCurrent(): ContextInterface + { + $spanFromContext = API\Span::fromContext(self::storage()->current()); + if ($spanFromContext instanceof SDK\Span) { + $ddSpanFromContext = $spanFromContext->getDDSpan(); + self::deactivateEndedParents($ddSpanFromContext); + } + + return self::activateParent(active_span()); + } + + private static function deactivateEndedParents(?SpanData $currentSpan) + { + if ($currentSpan === null) { // Terminal condition - root span + return; + } + + if ($currentSpan->getDuration() === 0) { + // The span is still active, so its parents are still active + return; + } + + // The dd span is ended, so end the OTel span + /** @var SDK\Span $OTelCurrentSpan */ + $OTelCurrentSpan = ObjectKVStore::get($currentSpan, 'otel_span'); // Note: SDK\Span::startSpan() associates the DDTrace span with the OTel span when it is created + if ($OTelCurrentSpan !== null) { + $OTelCurrentSpan->endOTelSpan(); + } + + // End the parent spans + self::deactivateEndedParents($currentSpan->parent); + } + + private static function convertDDSpanKindToOtel(string $spanKind) + { + switch ($spanKind) { + case Tag::SPAN_KIND_VALUE_CLIENT: + return API\SpanKind::KIND_CLIENT; + case Tag::SPAN_KIND_VALUE_SERVER: + return API\SpanKind::KIND_SERVER; + case Tag::SPAN_KIND_VALUE_PRODUCER: + return API\SpanKind::KIND_PRODUCER; + case Tag::SPAN_KIND_VALUE_CONSUMER: + return API\SpanKind::KIND_CONSUMER; + case Tag::SPAN_KIND_VALUE_INTERNAL: + default: + return API\SpanKind::KIND_INTERNAL; + } + } + + private static function activateParent(?SpanData $currentSpan): ContextInterface + { + if ($currentSpan === null) { // Terminal condition - root span + return self::storage()->current(); + } + + /** @var SDK\Span $OTelCurrentSpan */ + $OTelCurrentSpan = ObjectKVStore::get($currentSpan, 'otel_span'); // Note: SDK\Span::startSpan() associates the DDTrace span with the OTel span when it is created + if ($OTelCurrentSpan !== null) { // If the current span has been activated, nothing to do, trigger backtalk + // Return the context associated with the current span + if (ObjectKVStore::get($currentSpan, 'ddtrace_scope_activated')) { + return self::storage()->current()->with(self::$spanContextKey, $OTelCurrentSpan); + } else { + return self::storage()->current(); + } + } + + $parentContext = self::activateParent($currentSpan->parent); // Activates the ancestors first + + // Create a new span from the current span + $currentSpanId = $currentSpan->hexId(); + $currentTraceId = \DDTrace\root_span()->traceId; + $traceContext = generate_distributed_tracing_headers(['tracecontext']); + $traceFlags = isset($traceContext['traceparent']) + ? (substr($traceContext['traceparent'], -2) === '01' ? API\TraceFlags::SAMPLED : API\TraceFlags::DEFAULT) + : null; + $traceState = new API\TraceState($traceContext['tracestate'] ?? null); + + $OTelCurrentSpan = SDK\Span::startSpan( + $currentSpan, + API\SpanContext::create($currentTraceId, $currentSpanId, $traceFlags, $traceState), // $context + self::getDDInstrumentationScope(), // $instrumentationScope + isset($currentSpan->meta[Tag::SPAN_KIND]) ? self::convertDDSpanKindToOtel($currentSpan->meta[Tag::SPAN_KIND]) : API\SpanKind::KIND_INTERNAL, // $kind + API\Span::fromContext($parentContext), // $parentSpan (TODO: Handle null parent span) ? + $parentContext, // $parentContext + NoopSpanProcessor::getInstance(), // $spanProcessor + ResourceInfoFactory::defaultResource(), // $resource + (new AttributesFactory())->builder(), // $attributesBuilder + [], // TODO: Handle Span Links + 0, // TODO: Handle Span Links + false // The span was created using the DD Api + ); + ObjectKVStore::put($currentSpan, 'otel_span', $OTelCurrentSpan); + $currentContext = $parentContext->with(self::$spanContextKey, $OTelCurrentSpan); // Sets the current span in the context + ObjectKVStore::put($currentSpan, 'ddtrace_scope_activated', true); + self::storage()->attach($currentContext); // TODO: Handle Detach + + return $currentContext; + } + + public function activate(): ScopeInterface + { + if ($this->span instanceof SDK\Span) { + ObjectKVStore::put($this->span->getDDSpan(), 'ddtrace_scope_activated', true); + } + + $scope = self::storage()->attach($this); + + return $scope; + } + + public function withContextValue(ImplicitContextKeyedInterface $value): ContextInterface + { + return $value->storeInContext($this); + } + + public function with(ContextKeyInterface $key, $value): self + { + if ($this->get($key) === $value) { + return $this; + } + + $self = clone $this; + + if ($key === self::$spanContextKey) { + $self->span = $value; // @phan-suppress-current-line PhanTypeMismatchPropertyReal + + return $self; + } + + $id = spl_object_id($key); + if ($value !== null) { + $self->context[$id] = $value; + $self->contextKeys[$id] ??= $key; + } else { + unset( + $self->context[$id], + $self->contextKeys[$id], + ); + } + + return $self; + } + + public function get(ContextKeyInterface $key) + { + if ($key === self::$spanContextKey) { + /** @psalm-suppress InvalidReturnStatement */ + return $this->span; + } + + return $this->context[spl_object_id($key)] ?? null; + } +} diff --git a/src/DDTrace/OpenTelemetry/Span.php b/src/DDTrace/OpenTelemetry/Span.php new file mode 100644 index 00000000000..ae823a71c31 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/Span.php @@ -0,0 +1,403 @@ +span = $span; + $this->context = $context; + $this->instrumentationScope = $instrumentationScope; + $this->kind = $kind; + $this->parentSpanContext = $parentSpanContext; + $this->spanProcessor = $spanProcessor; + $this->resource = $resource; + + $this->status = StatusData::unset(); + + if ($isRemapped || empty($span->name)) { + // Since the span was created using the OTel API, it doesn't have an operation name* + // *: This is a bit false. For instance, a span created using the OTel API and DD_TRACE_GENERATE_ROOT_SPAN=0 + // under a cli (e.g., phpunit) process would have a default operation name set (e.g., phpunit). This is + // done in serializer.c:ddtrace_set_root_span_properties (as of v0.92.0) + $span->name = $this->operationNameConvention = Convention::defaultOperationName($span); + } + } + + /** + * This method _MUST_ not be used directly. + * End users should use a {@see API\TracerInterface} in order to create spans. + * + * @param non-empty-string $name + * @psalm-param API\SpanKind::KIND_* $kind + * @param list $links + * + * @internal + * @psalm-internal OpenTelemetry + */ + public static function startSpan( + SpanData $span, + API\SpanContextInterface $context, + InstrumentationScopeInterface $instrumentationScope, + int $kind, + API\SpanInterface $parentSpan, + ContextInterface $parentContext, + SpanProcessorInterface $spanProcessor, + ResourceInfo $resource, + AttributesBuilderInterface $attributesBuilder, + array $links, + int $totalRecordedLinks, + bool $isRemapped = true // Answers the question "Was the span created using the OTel API?" + ): self { + $attributes = $attributesBuilder->build()->toArray(); + self::_setAttributes($span, $attributes); + + $resourceAttributes = $resource->getAttributes()->toArray(); + self::_setAttributes($span, $resourceAttributes); + + $OTelSpan = new self( + $span, + $context, + $instrumentationScope, + $kind, + $parentSpan->getContext(), + $spanProcessor, + $resource, + $isRemapped + ); + + ObjectKVStore::put($span, 'otel_span', $OTelSpan); + + // Call onStart here to ensure the span is fully initialized. + $spanProcessor->onStart($OTelSpan, $parentContext); + + return $OTelSpan; + } + + public function getName(): string + { + return $this->span->resource ?: $this->span->name; + } + + /** + * @inheritDoc + */ + public function getContext(): SpanContextInterface + { + return $this->context; + } + + public function getParentContext(): API\SpanContextInterface + { + return $this->parentSpanContext; + } + + public function getInstrumentationScope(): InstrumentationScopeInterface + { + return $this->instrumentationScope; + } + + public function hasEnded(): bool + { + return $this->span->getDuration() !== 0; + } + + /** + * @inheritDoc + */ + public function toSpanData(): SpanDataInterface + { + $hasEnded = $this->hasEnded(); + + return new ImmutableSpan( + $this, + $this->getName(), + [], // TODO: Handle Span Links + [], // TODO: Handle Span Events + Attributes::create(array_merge($this->span->meta, $this->span->metrics)), + 0, + StatusData::create($this->status->getCode(), $this->status->getDescription()), + $hasEnded ? $this->span->getStartTime() + $this->span->getDuration() : 0, + $this->hasEnded() + ); + } + + /** + * @inheritDoc + */ + public function getDuration(): int + { + return $this->span->getDuration() ?: ClockFactory::getDefault()->now() - $this->span->getStartTime(); + } + + /** + * @inheritDoc + */ + public function getKind(): int + { + return $this->kind; + } + + /** + * @inheritDoc + */ + public function getAttribute(string $key) + { + return $this->span->meta[$key] ?? ($this->span->metrics[$key] ?? null); + } + + public function getStartEpochNanos(): int + { + return $this->span->getStartTime(); + } + + public function getTotalRecordedLinks(): int + { + return 0; + } + + public function getTotalRecordedEvents(): int + { + return 0; + } + + /** + * @inheritDoc + */ + public function isRecording(): bool + { + return !$this->hasEnded(); + } + + private static function _setAttribute(SpanData $span, string $key, $value): void + { + if ($value === null && isset($span->meta[$key])) { + unset($span->meta[$key]); + } elseif ($value === null && isset($span->metrics[$key])) { + unset($span->metrics[$key]); + } elseif (strpos($key, '_dd.p.') === 0) { + $distributedKey = substr($key, 6); // strlen('_dd.p.') === 6 + \DDTrace\add_distributed_tag($distributedKey, $value); + } elseif (is_float($value) + || is_int($value) + || (is_array($value) && count($value) > 0 && is_numeric($value[0]))) { // Note: Assumes attribute with primitive, homogeneous array values + $span->metrics[$key] = $value; + } else { + $span->meta[$key] = $value; + } + } + + private static function _setAttributes(SpanData $span, iterable $attributes): void + { + foreach ($attributes as $key => $value) { + self::_setAttribute($span, $key, $value); + } + } + + private function updateConvention(): void + { + if ($this->span->name === $this->operationNameConvention) { + $this->span->name = $this->operationNameConvention = Convention::defaultOperationName($this->span); + } + } + + /** + * @inheritDoc + */ + public function setAttribute(string $key, $value): SpanInterface + { + if (!$this->hasEnded()) { + self::_setAttribute($this->span, $key, $value); + } + + $this->updateConvention(); + + return $this; + } + + /** + * @inheritDoc + */ + public function setAttributes(iterable $attributes): SpanInterface + { + if (!$this->hasEnded()) { + foreach ($attributes as $key => $value) { + $this->setAttribute($key, $value); + } + } + + $this->updateConvention(); + + return $this; + } + + /** + * @inheritDoc + */ + public function addEvent(string $name, iterable $attributes = [], int $timestamp = null): SpanInterface + { + // no-op + return $this; + } + + /** + * @inheritDoc + */ + public function recordException(Throwable $exception, iterable $attributes = []): SpanInterface + { + if (!$this->hasEnded()) { + $this->span->meta[Tag::ERROR_MSG] = $exception->getMessage(); + $this->span->meta[Tag::ERROR_TYPE] = get_class($exception); + $this->span->meta[Tag::ERROR_STACK] = $exception->getTraceAsString(); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function updateName(string $name): SpanInterface + { + // OTel.name => DD.resource + if (!$this->hasEnded()) { + $this->span->resource = $name; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function setStatus(string $code, string $description = null): SpanInterface + { + if ($this->hasEnded()) { + return $this; + } + + // An attempt to set value Unset SHOULD be ignored. + if ($code === API\StatusCode::STATUS_UNSET) { + return $this; + } + + // When span status is set to Ok it SHOULD be considered final and any further attempts to change it SHOULD be ignored. + if ($this->status->getCode() === API\StatusCode::STATUS_OK) { + return $this; + } + + if ($this->status->getCode() === API\StatusCode::STATUS_UNSET && $code === API\StatusCode::STATUS_ERROR) { + $this->span->meta[Tag::ERROR_MSG] = $description; + } elseif ($this->status->getCode() === API\StatusCode::STATUS_ERROR && $code === API\StatusCode::STATUS_OK) { + unset($this->span->meta[Tag::ERROR_MSG]); + unset($this->span->meta[Tag::ERROR_TYPE]); + unset($this->span->meta[Tag::ERROR_STACK]); + } + + $this->status = StatusData::create($code, $description); + + return $this; + } + + /** + * @inheritDoc + */ + public function end(int $endEpochNanos = null): void + { + if ($this->hasEnded()) { + return; + } + + $this->endOTelSpan($endEpochNanos); + + switch_stack($this->span); + close_span($endEpochNanos !== null ? $endEpochNanos / 1000000000 : 0); + $this->spanProcessor->onEnd($this); + } + + public function endOTelSpan(int $endEpochNanos = null): void + { + if ($this->hasEnded()) { + return; + } + + if (empty($this->span->name)) { // Honor set operation name + $this->span->name = Convention::defaultOperationName($this->span); + } + + // After closing the span, the context won't change, but would otherwise be lost + $this->context = API\SpanContext::create( + $this->context->getTraceId(), + $this->context->getSpanId(), + $this->context->getTraceFlags(), + $this->context->getTraceState() + ); + } + + public function getResource(): ResourceInfo + { + return $this->resource; + } + + /** + * @internal + * @return SpanData + */ + public function getDDSpan(): SpanData + { + return $this->span; + } +} diff --git a/src/DDTrace/OpenTelemetry/SpanBuilder.php b/src/DDTrace/OpenTelemetry/SpanBuilder.php new file mode 100644 index 00000000000..af96184adf4 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/SpanBuilder.php @@ -0,0 +1,215 @@ + */ + private array $links = []; + + private AttributesBuilderInterface $attributesBuilder; + private int $totalNumberOfLinksAdded = 0; + private float $startEpochNanos = 0; + + /** @param non-empty-string $spanName */ + public function __construct( + string $spanName, + InstrumentationScopeInterface $instrumentationScope, + TracerSharedState $tracerSharedState + ) { + $this->spanName = $spanName; + $this->instrumentationScope = $instrumentationScope; + $this->tracerSharedState = $tracerSharedState; + $this->attributesBuilder = $tracerSharedState->getSpanLimits()->getAttributesFactory()->builder(); + } + + /** + * @inheritDoc + */ + public function setParent($context): API\SpanBuilderInterface + { + $this->parentContext = $context; + + return $this; + } + + public function addLink(SpanContextInterface $context, iterable $attributes = []): SpanBuilderInterface + { + // TODO: Span Links are future works + + return $this; + } + + /** @inheritDoc */ + public function setAttribute(string $key, $value): API\SpanBuilderInterface + { + $this->attributesBuilder[$key] = $value; + + return $this; + } + + /** @inheritDoc */ + public function setAttributes(iterable $attributes): API\SpanBuilderInterface + { + foreach ($attributes as $key => $value) { + $this->attributesBuilder[$key] = $value; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function setStartTimestamp(int $timestampNanos): SpanBuilderInterface + { + if ($timestampNanos >= 0) { + $this->startEpochNanos = $timestampNanos / 1000000000; + } + + return $this; + } + + /** + * @inheritDoc + */ + public function setSpanKind(int $spanKind): SpanBuilderInterface + { + $this->spanKind = $spanKind; + + return $this; + } + + /** + * @inheritDoc + */ + public function startSpan(): SpanInterface + { + $this->applySpanKind(); + + $parentContext = Context::resolve($this->parentContext); + $parentSpan = Span::fromContext($parentContext); + $parentSpanContext = $parentSpan->getContext(); + + $span = $parentSpanContext->isValid() ? null : \DDTrace\start_trace_span($this->startEpochNanos); + $traceId = $parentSpanContext->isValid() ? $parentSpanContext->getTraceId() : \DDTrace\root_span()->traceId; + + $samplingResult = $this + ->tracerSharedState + ->getSampler() + ->shouldSample( + $parentContext, + $traceId, + $this->spanName, + $this->spanKind, + $this->attributesBuilder->build(), + $this->links, + ); + + $span = $span ?? \DDTrace\start_trace_span($this->startEpochNanos); + $samplingDecision = $samplingResult->getDecision(); + $sampled = SamplingResult::RECORD_AND_SAMPLE === $samplingDecision; + $samplingResultTraceState = $samplingResult->getTraceState(); + + if ($parentSpanContext->isValid()) { + // Traceparent: {2:version}-{32:hex trace id}-{16:hex parent id}-{2:trace_flags}, version always being '00' + // Since parentSpanContext is valid, the trace identifiers are guaranteed to be in hexadecimal format + $parentId = $parentSpanContext->getSpanId(); + $traceFlags = $sampled ? '01' : '00'; + $traceParent = "00-$traceId-$parentId-$traceFlags"; + \DDTrace\consume_distributed_tracing_headers([ + 'traceparent' => $traceParent, + 'tracestate' => (string) $samplingResultTraceState, // __toString() is implemented in TraceState + ]); + } elseif ($samplingResultTraceState) { + $samplingResultTraceState = $samplingResultTraceState->without('dd'); + \DDTrace\root_span()->tracestate = (string) $samplingResultTraceState; + } + + $hexSpanId = $span->hexId(); + $spanContext = DDTraceAPI\SpanContext::createFromLocalSpan($span, $sampled, $traceId, $hexSpanId); + + if (!in_array($samplingDecision, [SamplingResult::RECORD_AND_SAMPLE, SamplingResult::RECORD_ONLY], true)) { + return Span::wrap($spanContext); + } + + $span->resource = $this->spanName; // OTel.name => DD.resource + + $attributesBuilder = clone $this->attributesBuilder; // According to OTel's spec, attributes can't be changed after span creation... + $attributes = $samplingResult->getAttributes(); + foreach ($attributes as $key => $value) { + $attributesBuilder[$key] = $value; + } + + return Span::startSpan( + $span, + $spanContext, + $this->instrumentationScope, + $this->spanKind, + $parentSpan, + $parentContext, + $this->tracerSharedState->getSpanProcessor(), + $this->tracerSharedState->getResource(), + $attributesBuilder, + $this->links, + 0, + ); + } + + private function applySpanKind(): void + { + switch ($this->spanKind) { + case API\SpanKind::KIND_CLIENT: + $this->setAttribute(Tag::SPAN_KIND, Tag::SPAN_KIND_VALUE_CLIENT); + break; + case API\SpanKind::KIND_SERVER: + $this->setAttribute(Tag::SPAN_KIND, Tag::SPAN_KIND_VALUE_SERVER); + break; + case API\SpanKind::KIND_PRODUCER: + $this->setAttribute(Tag::SPAN_KIND, Tag::SPAN_KIND_VALUE_PRODUCER); + break; + case API\SpanKind::KIND_CONSUMER: + $this->setAttribute(Tag::SPAN_KIND, Tag::SPAN_KIND_VALUE_CONSUMER); + break; + case API\SpanKind::KIND_INTERNAL: + $this->setAttribute(Tag::SPAN_KIND, Tag::SPAN_KIND_VALUE_INTERNAL); + break; + default: + break; + } + } +} diff --git a/src/DDTrace/OpenTelemetry/SpanContext.php b/src/DDTrace/OpenTelemetry/SpanContext.php new file mode 100644 index 00000000000..2baef1e2156 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/SpanContext.php @@ -0,0 +1,129 @@ +span = $span; + $this->sampled = $sampled; + $this->remote = $remote; + $this->traceId = $traceId ?: \DDTrace\root_span()->traceId; + $this->spanId = $spanId ?: $this->span->hexId(); + + // TraceId must be exactly 16 bytes (32 chars) and at least one non-zero byte + // SpanId must be exactly 8 bytes (16 chars) and at least one non-zero byte + if (!SpanContextValidator::isValidTraceId($this->traceId) || !SpanContextValidator::isValidSpanId($this->spanId)) { + $this->traceId = SpanContextValidator::INVALID_TRACE; + $this->spanId = SpanContextValidator::INVALID_SPAN; + $this->isValid = false; + } + } + + /** + * @inheritDoc + */ + public function getTraceId(): string + { + return $this->traceId; + } + + public function getTraceIdBinary(): string + { + return hex2bin($this->getTraceId()); + } + + /** + * @inheritDoc + */ + public function getSpanId(): string + { + return $this->spanId; + } + + public function getSpanIdBinary(): string + { + return hex2bin($this->getSpanId()); + } + + public function getTraceState(): ?TraceStateInterface + { + $traceContext = generate_distributed_tracing_headers(['tracecontext']); + return new TraceState($traceContext['tracestate'] ?? null); + } + + public function isSampled(): bool + { + return $this->sampled; + } + + public function isValid(): bool + { + return $this->isValid; + } + + public function isRemote(): bool + { + return $this->remote; + } + + public function getTraceFlags(): int + { + return $this->sampled ? TraceFlags::SAMPLED : TraceFlags::DEFAULT; + } + + /** @inheritDoc */ + public static function createFromRemoteParent(string $traceId, string $spanId, int $traceFlags = TraceFlags::DEFAULT, ?TraceStateInterface $traceState = null): SpanContextInterface + { + return API\SpanContext::createFromRemoteParent($traceId, $spanId, $traceFlags, $traceState); + } + + /** @inheritDoc */ + public static function create(string $traceId, string $spanId, int $traceFlags = TraceFlags::DEFAULT, ?TraceStateInterface $traceState = null): SpanContextInterface + { + return API\SpanContext::create($traceId, $spanId, $traceFlags, $traceState); + } + + /** @inheritDoc */ + public static function getInvalid(): SpanContextInterface + { + return API\SpanContext::getInvalid(); + } + + public static function createFromLocalSpan(SpanData $span, bool $sampled, ?string $traceId = null, ?string $spanId = null) + { + return new self( + $span, + $sampled, + false, + $traceId, + $spanId + ); + } +} diff --git a/src/Integrations/Util/Convention.php b/src/Integrations/Util/Convention.php new file mode 100644 index 00000000000..a628c6275ec --- /dev/null +++ b/src/Integrations/Util/Convention.php @@ -0,0 +1,60 @@ +meta; + $spanKind = $meta[Tag::SPAN_KIND] ?? null; + + switch (true) { + case isset($meta['http.request.method']) && $spanKind === Tag::SPAN_KIND_VALUE_SERVER: // HTTP Server + return 'http.server.request'; + case isset($meta['http.request.method']) && $spanKind === Tag::SPAN_KIND_VALUE_CLIENT: // HTTP Client + return 'http.client.request'; + case isset($meta['db.system']) && $spanKind === Tag::SPAN_KIND_VALUE_CLIENT: // Database + return strtolower($meta['db.system']) . '.query'; + case isset($meta['messaging.system'], $meta['messaging.operation']) + && (in_array( + $spanKind, [ + Tag::SPAN_KIND_VALUE_CONSUMER, + Tag::SPAN_KIND_VALUE_PRODUCER, + Tag::SPAN_KIND_VALUE_SERVER, + Tag::SPAN_KIND_VALUE_CLIENT + ] + )): + return strtolower($meta['messaging.system']) . '.' . strtolower($meta['messaging.operation']); + case isset($meta['rpc.system']) && $meta['rpc.system'] === 'aws-api' + && $spanKind === Tag::SPAN_KIND_VALUE_CLIENT: // AWS Client + return isset($meta['rpc.service']) + ? 'aws.' . strtolower($meta['rpc.service']) . '.request' + : 'aws.client.request'; + case isset($meta['rpc.system']) && $spanKind === Tag::SPAN_KIND_VALUE_CLIENT: // RPC Client + return strtolower($meta['rpc.system']) . '.client.request'; + case isset($meta['rpc.system']) && $spanKind === Tag::SPAN_KIND_VALUE_SERVER: // RPC Server + return strtolower($meta['rpc.system']) . '.server.request'; + case isset($meta['faas.trigger']) && $spanKind === Tag::SPAN_KIND_VALUE_SERVER: // FaaS Server + return strtolower($meta['faas.trigger']) . '.invoke'; + case isset($meta['faas.invoked_provider'], $meta['faas.invoked_name']) + && $spanKind === Tag::SPAN_KIND_VALUE_CLIENT: // FaaS Client + return strtolower($meta['faas.invoked_provider']) . '.' . strtolower($meta['faas.invoked_name']) . '.invoke'; + case isset($meta['graphql.operation.type']): + return 'graphql.server.request'; + case $spanKind === Tag::SPAN_KIND_VALUE_SERVER: // Generic + case $spanKind === Tag::SPAN_KIND_VALUE_CLIENT: + return isset($meta['network.protocol.name']) + ? strtolower($meta['network.protocol.name']) . ".$spanKind.request" + : "$spanKind.request"; + case !empty($spanKind): + return $spanKind; + default: // If all else fails, we still shouldn't use the resource name + return ''; + } + } +} diff --git a/tests/Common/BaseTestCase.php b/tests/Common/BaseTestCase.php index 5b5c0406850..5e810892e2c 100644 --- a/tests/Common/BaseTestCase.php +++ b/tests/Common/BaseTestCase.php @@ -68,7 +68,7 @@ protected function withDebugLogger() return $logger; } - protected static function putEnv($putenv) + public static function putEnv($putenv) { // cleanup: properly replace this function by ini_set() in test code ... if (strpos($putenv, "DD_") === 0) { @@ -92,7 +92,7 @@ protected static function putEnv($putenv) * @param array $putenvs In the format ['ENV_1=value1', 'ENV_2=value2'] * @return void */ - protected function putEnvAndReloadConfig($putenvs = []) + public static function putEnvAndReloadConfig($putenvs = []) { foreach ($putenvs as $putenv) { self::putEnv($putenv); diff --git a/tests/OpenTelemetry/Integration/API/BaggageTest.php b/tests/OpenTelemetry/Integration/API/BaggageTest.php new file mode 100644 index 00000000000..c5a28b782b1 --- /dev/null +++ b/tests/OpenTelemetry/Integration/API/BaggageTest.php @@ -0,0 +1,108 @@ + 'key1=value1,key2=value2;property1', + ]; + $context = $propagator->extract($carrier); + $scopes[] = $context->activate(); + + // Get the baggage, and extract values from it + $baggage = Baggage::getCurrent(); + $key1 = $baggage->getValue('key1'); + $this->assertSame('value1', $key1); + $key2 = $baggage->getValue('key2'); + $this->assertSame('value2', $key2); + $key1Metadata = $baggage->getEntry('key1')->getMetadata()->getValue(); + $this->assertSame('', $key1Metadata); + $key2Metadata = $baggage->getEntry('key2')->getMetadata()->getValue(); + $this->assertSame('property1', $key2Metadata); + + // Remove a value from baggage and add a value + $scopes[] = $baggage->toBuilder()->remove('key1')->set('key3', 'value3')->build()->activate(); + + // Extract baggage from context, and store in a different carrier (e.g., outbound http request headers) + $out = []; + $propagator->inject($out); + $this->assertSame('key2=value2;property1,key3=value3', $out['baggage']); + + // Clear baggage (to avoid sending to an untrusted process) + $scopes[] = Baggage::getEmpty()->activate(); + $cleared = []; + $propagator->extract($cleared); + $this->assertEmpty($cleared); + + // Detach scopes + foreach (array_reverse($scopes) as $scope) { + $scope->detach(); + } + } + + public function testSpansAndBaggage() + { + $traces = $this->isolateTracer(function () { + $tracer = (new TracerProvider())->getTracer('OpenTelemetry.TestTracer'); + + $parentSpan = $tracer->spanBuilder('parent') + ->setSpanKind(SpanKind::KIND_SERVER) + ->startSpan(); + $parentSpanScope = $parentSpan->activate(); + + $baggage = Baggage::getBuilder() + ->set('user.id', '1') + ->set('user.name', 'name') + ->build(); + $baggageScope = $baggage->storeInContext(Context::getCurrent())->activate(); + + $childSpan = $tracer->spanBuilder('child')->startSpan(); + $childSpanScope = $childSpan->activate(); + + $childSpan->setAttribute('user.id', Baggage::getCurrent()->getValue('user.id')); + + $childSpanScope->detach(); + $childSpan->end(); + + $parentSpan->setAttribute('http.method', 'GET'); + $parentSpan->setAttribute('http.uri', '/parent'); + + $baggageScope->detach(); + $parentSpanScope->detach(); + $parentSpan->end(); + }); + + list($parent, $child) = $traces[0]; + $this->assertSame(Tag::SPAN_KIND_VALUE_SERVER, $parent['meta'][Tag::SPAN_KIND]); + $this->assertSame('GET', $parent['meta']['http.method']); + $this->assertSame('/parent', $parent['meta']['http.uri']); + $this->assertSame('1', $child['meta']['user.id']); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('server.request', 'parent', 'datadog/dd-trace-tests') + ->withChildren([ + SpanAssertion::exists('internal', 'child', 'datadog/dd-trace-tests') + ]) + ]); + } +} diff --git a/tests/OpenTelemetry/Integration/API/TracerTest.php b/tests/OpenTelemetry/Integration/API/TracerTest.php new file mode 100644 index 00000000000..b54c977475e --- /dev/null +++ b/tests/OpenTelemetry/Integration/API/TracerTest.php @@ -0,0 +1,873 @@ +getTracer("OpenTelemetry.TracerTest$uniqueKey"); + return $tracer; + } + + public function testOtelSetSpanStatusError() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $errorSpan = $tracer->spanBuilder('error_span')->startSpan(); + $errorSpanScope = $errorSpan->activate(); + $errorSpan->setStatus(StatusCode::STATUS_ERROR, "error_desc"); + $errorSpan->setStatus(StatusCode::STATUS_UNSET, "unset_desc"); + $errorSpanScope->detach(); + $errorSpan->end(); + }); + + $this->assertSame('error_desc', $traces[0][0]['meta']['error.message']); + } + + public function testUnorderedOtelSpanActivation() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span1 = $tracer->spanBuilder('test.span1')->startSpan(); + $span2 = $tracer->spanBuilder('test.span2')->startSpan(); + $span3 = $tracer->spanBuilder('test.span3')->startSpan(); + + $scope1 = $span1->activate(); + $scope3 = $span3->activate(); + $scope2 = $span2->activate(); + + $scope2->detach(); + $scope3->detach(); + $scope1->detach(); + + $span1->end(); + $span2->end(); + $span3->end(); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'test.span1', null, 'datadog/dd-trace-tests'), + SpanAssertion::exists('internal', 'test.span2', null, 'datadog/dd-trace-tests'), + SpanAssertion::exists('internal', 'test.span3', null, 'datadog/dd-trace-tests'), + ]); + } + + public function testManuallyCreatedSpanWithNoCustomTags() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + /** @var Span $span */ + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertNotEmpty($span['trace_id']); + $this->assertSame($span['trace_id'], $span['span_id']); + $this->assertNotEquals(0, $span['duration']); + $this->assertSame('internal', $span['name']); + $this->assertSame('test.span', $span['resource']); + $this->assertEquals(PrioritySampling::AUTO_KEEP, $span['metrics']["_sampling_priority_v1"]); + } + + public function testManuallyCreatedSpanWithCustomTags() + { + $traces = $this->isolateTracer(function () { + $tracerProvider = new TracerProvider(); + $tracer = $tracerProvider->getTracer('OpenTelemetry.TracerTest', 'dev', 'http://url', ['foo' => 'bar']); + /** @var Span $span */ + $span = $tracer->spanBuilder('test.span') + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 'baz') + ->startSpan(); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertNotEmpty($span['trace_id']); + $this->assertSame($span['trace_id'], $span['span_id']); + $this->assertNotEquals(0, $span['duration']); + $this->assertSame('internal', $span['name']); + $this->assertSame('test.span', $span['resource']); + $this->assertEquals(PrioritySampling::AUTO_KEEP, $span['metrics']["_sampling_priority_v1"]); + $this->assertSame('bar', $span['meta']['foo']); + $this->assertSame('baz', $span['meta']['bar']); + } + + public function testManuallyCreatedSpanWithNestedAttributes() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + /** @var Span $span */ + $span = $tracer->spanBuilder('test.span') + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 'baz') + ->setAttribute('nested', ['foo' => 'bar', 'bar' => 'baz', 'alone']) + ->startSpan(); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertNotEmpty($span['trace_id']); + $this->assertSame($span['trace_id'], $span['span_id']); + $this->assertNotEquals(0, $span['duration']); + $this->assertSame('internal', $span['name']); + $this->assertSame('test.span', $span['resource']); + $this->assertEquals(PrioritySampling::AUTO_KEEP, $span['metrics']["_sampling_priority_v1"]); + $this->assertSame('bar', $span['meta']['foo']); + $this->assertSame('baz', $span['meta']['bar']); + $this->assertSame('bar', $span['meta']['nested.foo']); + $this->assertSame('baz', $span['meta']['nested.bar']); + $this->assertSame('alone', $span['meta']['nested.0']); + } + + public function testManuallyCreatedNestedSpansBasic() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + + $parent = $tracer->spanBuilder("parent")->startSpan(); + $scope = $parent->activate(); + try { + $child = $tracer->spanBuilder("child")->startSpan(); + $child->end(); + } finally { + $parent->end(); + $scope->detach(); + } + }); + + $spans = $traces[0]; + list($parentSpan, $childSpan) = $spans; + $this->assertNotEmpty($parentSpan['trace_id']); + $this->assertSame($parentSpan['trace_id'], $parentSpan['span_id']); + $this->assertSame($parentSpan['trace_id'], $childSpan['trace_id']); + $this->assertSame($parentSpan['span_id'], $childSpan['trace_id']); + $this->assertNotSame($parentSpan['span_id'], $childSpan['span_id']); + $this->assertNotSame($childSpan['trace_id'], $childSpan['span_id']); + } + + public function testCreateSpanWithParentContext() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $parent = $tracer->spanBuilder("parent")->startSpan(); + $child = $tracer->spanBuilder("child") + ->setParent(Context::getCurrent()->withContextValue($parent)) + ->startSpan(); + + $child->end(); + $parent->end(); + }); + + $spans = $traces[0]; + list($parentSpan, $childSpan) = $spans; + $this->assertNotEmpty($parentSpan['trace_id']); + $this->assertSame($parentSpan['trace_id'], $parentSpan['span_id']); + $this->assertSame($parentSpan['trace_id'], $childSpan['trace_id']); + $this->assertSame($parentSpan['span_id'], $childSpan['trace_id']); + $this->assertNotSame($parentSpan['span_id'], $childSpan['span_id']); + $this->assertNotSame($childSpan['trace_id'], $childSpan['span_id']); + } + + public function testCreateSpanInvalidParent() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $child = $tracer->spanBuilder("child") + ->setParent(Context::getCurrent()->withContextValue(Span::getInvalid())) + ->startSpan(); + $child->end(); + }); + + $span = $traces[0][0]; + $this->assertNotEmpty($span['trace_id']); + $this->assertSame($span['trace_id'], $span['span_id']); + $this->assertArrayNotHasKey('parent_id', $span); + $this->assertEquals(PrioritySampling::AUTO_KEEP, $span['metrics']["_sampling_priority_v1"]); + } + + public function testCreateANewTraceInTheSameProcess() + { + // credits: https://github.com/open-telemetry/opentelemetry-php/blob/main/examples/traces/features/creating_a_new_trace_in_the_same_process.php + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + // This creates a span and sets it as the current parent (and root) span + $rootSpan = $tracer->spanBuilder('foo')->startSpan(); + $rootScope = $rootSpan->activate(); + + // This creates (and closes) a child span + $childSpan = $tracer->spanBuilder('bar')->startSpan(); + $childSpan->end(); + + // This closes the root/parent span and detaches its scope/context + $rootSpan->end(); + $rootScope->detach(); + + // This creates a new span as a parent/root, however regardless of calling "activate" on it, it will have a new TraceId + $span = $tracer->spanBuilder('baz')->startSpan(); + $scope = $span->activate(); + + $span->end(); + $scope->detach(); + }); + + $spans = $traces[0]; + list($span, $rootSpan, $childSpan) = $spans; + + $this->assertNotEmpty($rootSpan['trace_id']); + $this->assertSame($rootSpan['trace_id'], $rootSpan['span_id']); + $this->assertSame($rootSpan['trace_id'], $childSpan['trace_id']); + $this->assertSame($rootSpan['span_id'], $childSpan['parent_id']); + + $this->assertNotEmpty($span['trace_id']); + $this->assertSame($span['trace_id'], $span['span_id']); + $this->assertNotSame($rootSpan['trace_id'], $span['trace_id']); + } + + public function testSpanAttributes() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span') + ->setAttribute("string", "a") + ->setAttribute("null-string", null) + ->setAttributes([ + "empty_string" => "", + "number" => 1, + "boolean" => true, + "string-array" => ["a", "b", "c"], + "boolean-array" => [true, false], + "float-array" => [1.1, 2.2, 3.3], + "empty-array" => [], + "mixed-array" => [1, "a", true, 1.1, null, []], # Dropped because non-primitive, non-homogeneous array + ]) + ->startSpan(); + $span->end(); + }); + + $meta = $traces[0][0]['meta']; + $metrics = $traces[0][0]['metrics']; + $this->assertSame("a", $meta['string']); + $this->assertArrayNotHasKey("null-string", $meta); + $this->assertSame("", $meta['empty_string']); + $this->assertEquals(1, $metrics['number']); + $this->assertSame("true", $meta['boolean']); + $this->assertSame("a", $meta['string-array.0']); + $this->assertSame("b", $meta['string-array.1']); + $this->assertSame("c", $meta['string-array.2']); + $this->assertSame("true", $meta['boolean-array.0']); + $this->assertSame("false", $meta['boolean-array.1']); + $this->assertEquals("1.1", $metrics['float-array.0']); + $this->assertEquals("2.2", $metrics['float-array.1']); + $this->assertEquals("3.3", $metrics['float-array.2']); + $this->assertSame("", $meta['empty-array']); + } + + /** + * @dataProvider providerSpanKind + */ + public function testSpanKind($otelSpanKind, $tagSpanKind) + { + $traces = $this->isolateTracer(function () use ($otelSpanKind) { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span') + ->setSpanKind($otelSpanKind) + ->startSpan(); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertSame($tagSpanKind, $span['meta']['span.kind']); + } + + public function providerSpanKind() + { + return [ + [SpanKind::KIND_CLIENT, Tag::SPAN_KIND_VALUE_CLIENT], + [SpanKind::KIND_SERVER, Tag::SPAN_KIND_VALUE_SERVER], + [SpanKind::KIND_PRODUCER, Tag::SPAN_KIND_VALUE_PRODUCER], + [SpanKind::KIND_CONSUMER, Tag::SPAN_KIND_VALUE_CONSUMER], + [SpanKind::KIND_INTERNAL, Tag::SPAN_KIND_VALUE_INTERNAL], + ]; + } + + public function testSpanErrorStatus() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->setStatus(StatusCode::STATUS_ERROR, "error message"); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertSame('internal', $span['name']); + $this->assertSame('test.span', $span['resource']); + $this->assertSame('error message', $span['meta']['error.message']); + $this->assertEquals(1, $span['error']); + } + + public function testSpanStatusTransition() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->setStatus(StatusCode::STATUS_UNSET); + $this->assertArrayNotHasKey(Tag::ERROR_MSG, active_span()->meta); // Initial state + $span->setStatus(StatusCode::STATUS_ERROR, "error message"); + $this->assertSame("error message", active_span()->meta[Tag::ERROR_MSG]); // Error state + $span->setStatus(StatusCode::STATUS_UNSET); + $this->assertSame("error message", active_span()->meta[Tag::ERROR_MSG]); // Unchanged state + $span->setStatus(StatusCode::STATUS_OK); + $this->assertArrayNotHasKey(Tag::ERROR_MSG, active_span()->meta); // OK state + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertArrayNotHasKey("error", $span); + $this->assertArrayNotHasKey("error.message", $span['meta']); + $this->assertSame("internal", $span['name']); + $this->assertSame("test.span", $span['resource']); + } + + public function testRecordException() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->recordException(new \RuntimeException("exception message")); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertSame('internal', $span['name']); + $this->assertSame('test.span', $span['resource']); + $this->assertSame('exception message', $span['meta'][Tag::ERROR_MSG]); + $this->assertSame('RuntimeException', $span['meta'][Tag::ERROR_TYPE]); + $this->assertNotEmpty($span['meta'][Tag::ERROR_STACK]); + $this->assertEquals(1, $span['error']); + + $this->markTestIncomplete("Span Events aren't yet supported"); + } + + public function testSpanNameUpdate() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->updateName('new.name'); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertSame('new.name', $span['resource']); + } + + public function testSpanUpdateAfterEnd() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->setStatus(StatusCode::STATUS_ERROR, "error message"); + $span->setAttribute('foo', 'bar'); + $span->end(); + $span->setAttribute('foo', 'baz'); + $span->setStatus(StatusCode::STATUS_OK); + $span->updateName('new.name'); + $span->setAttributes([ + 'foo' => 'quz', + 'bar' => 'baz' + ]); + }); + + $span = $traces[0][0]; + $this->assertSame('internal', $span['name']); + $this->assertSame('test.span', $span['resource']); + $this->assertSame('error message', $span['meta']['error.message']); + $this->assertEquals(1, $span['error']); + $this->assertSame('bar', $span['meta']['foo']); + } + + public function testConcurrentSpans() + { + // credits: https://github.com/open-telemetry/opentelemetry-php/blob/main/examples/traces/features/concurrent_spans.php + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + + $rootSpan = $tracer->spanBuilder('root')->startSpan(); + $scope = $rootSpan->activate(); + + // Because the root span is active, each of the following spans will be parented to the root span + try { + $spans = []; + for ($i = 1; $i <= 3; $i++) { + $s = Span::fromContext(Context::getCurrent()); + $spans[] = $tracer->spanBuilder('http-' . $i) + //@see https://github.com/open-telemetry/opentelemetry-collector/blob/main/model/semconv/v1.6.1/trace.go#L834 + ->setAttribute('http.method', 'GET') + ->setAttribute('http.url', 'example.com/' . $i) + ->setAttribute('http.status_code', "200") + ->setAttribute('http.response_content_length', "1024") + ->startSpan(); + } + /** @psalm-suppress ArgumentTypeCoercion */ + foreach ($spans as $span) { + usleep((int) (0.3 * 1e6)); + $span->end(); + } + } finally { + $scope->detach(); + $rootSpan->end(); + } + }); + + $spans = $traces[0]; + $rootSpan = $spans[0]; + $httpSpans = [$spans[3], $spans[2], $spans[1]]; + $this->assertCount(4, $spans); + + $traceId = $rootSpan['trace_id']; + $this->assertNotEmpty($traceId); + + $this->assertSame($traceId, $rootSpan['span_id']); + for ($i = 1; $i <= 3; $i++) { + $httpSpan = $httpSpans[$i - 1]; + $this->assertSame($traceId, $httpSpan['trace_id']); + $this->assertSame($rootSpan['span_id'], $httpSpan['parent_id']); + + $this->assertSame("http-$i", $httpSpan['resource']); + $this->assertSame("GET", $httpSpan['meta']['http.method']); + $this->assertSame("example.com/$i", $httpSpan['meta']['http.url']); + $this->assertSame('200', $httpSpan['meta']['http.status_code']); + $this->assertSame('1024', $httpSpan['meta']['http.response_content_length']); + } + } + + public function testGetSpanContextWithMultipleTraceStates() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->setAttributes([ + '_dd.p.congo' => 't61rcWkgMzE', + '_dd.p.some_val' => 'tehehe' + ]); + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0;t.congo:t61rcWkgMzE;t.some_val:tehehe$/', (string)$span->getContext()->getTraceState()); + $span->end(); + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0;t.congo:t61rcWkgMzE;t.some_val:tehehe$/', (string)$span->getContext()->getTraceState()); + }); + + $span = $traces[0][0]; + $this->assertSame('t61rcWkgMzE', $span['meta']['_dd.p.congo']); + $this->assertSame('tehehe', $span['meta']['_dd.p.some_val']); + } + + /** + * @dataProvider providerRemoteParent + */ + public function testGetSpanContextWithRemoteParent(int $traceFlags, ?TraceState $traceState) + { + // 128-bit trace id = low-64 trace_id (as decimal) + high-64 _dd.p.tid (as hex) * 2^64 + $low = "11803532876627986230"; + $high = "4bf92f3577b34da6"; + $decSpanId = "67667974448284343"; + + $traces = $this->isolateTracer(function () use ($traceFlags, $traceState, $low, $decSpanId) { + $remoteContext = SpanContext::createFromRemoteParent( + '4bf92f3577b34da6a3ce929d0e0e4736', + '00f067aa0ba902b7', + $traceFlags, + $traceState + ); + $remoteSpan = new NonRecordingSpan($remoteContext); + + $tracer = self::getTracer(); + $spanBuilder = $tracer->spanBuilder('test.span'); + $this->assertInstanceOf(SpanBuilder::class, $spanBuilder); + $child = $spanBuilder + ->setParent(Context::getCurrent()->withContextValue($remoteSpan)) + ->startSpan(); + $scope = $child->activate(); + $this->assertTrue($child->isRecording()); + $this->assertInstanceOf(Span::class, $child); + $scope->detach(); + $child->end(); + + $childContext = $child->getContext(); + $this->assertSame($remoteContext->getTraceId(), $childContext->getTraceId()); + $this->assertSame($remoteContext->getSpanId(), $child->getParentContext()->getSpanId()); + $this->assertFalse($childContext->isRemote()); // "When creating children from remote spans, their IsRemote flag MUST be set to false." + $this->assertEquals(1, $childContext->getTraceFlags()); // RECORD_AND_SAMPLED ==> 01 (AlwaysOn sampler) + $this->assertSame("dd=t.tid:4bf92f3577b34da6;t.dm:-0" . ($traceState ? ",$traceState" : ""), (string)$childContext->getTraceState()); + }); + + $span = $traces[0][0]; + $this->assertSame($low, $span['trace_id']); + $this->assertSame($high, $span['meta']['_dd.p.tid']); + $this->assertSame($decSpanId, $span['parent_id']); + } + + public function providerRemoteParent() + { + return [ + [TraceFlags::SAMPLED, null], + [TraceFlags::SAMPLED, new TraceState("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE")], + [TraceFlags::DEFAULT, null], + [TraceFlags::DEFAULT, new TraceState("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE")], + ]; + } + + public function testMultipleTraceState() + { + $traces = $this->isolateTracer(function () { + $root = new class() implements SamplerInterface { + public function shouldSample( + ContextInterface $parentContext, + string $traceId, + string $spanName, + int $spanKind, + AttributesInterface $attributes, + array $links + ): SamplingResult { + return new SamplingResult( + SamplingResult::RECORD_AND_SAMPLE, + [], + new TraceState("root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE") + ); + } + + public function getDescription(): string + { + return "Custom Sampler"; + } + }; + + $localParentSampler = new class() implements SamplerInterface { + public function shouldSample( + ContextInterface $parentContext, + string $traceId, + string $spanName, + int $spanKind, + AttributesInterface $attributes, + array $links + ): SamplingResult { + return new SamplingResult( + SamplingResult::RECORD_AND_SAMPLE, + [], + new TraceState("localparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE") + ); + } + + public function getDescription(): string + { + return "Custom Sampler"; + } + }; + + // Create a new tracer with a parent based sampling + $tracer = (new TracerProvider([], new ParentBased( + $root, + null, + null, + $localParentSampler, + )))->getTracer('OpenTelemetry.TracerTest'); + $parent = $tracer->spanBuilder("parent")->startSpan(); // root sampler will be used + $scope = $parent->activate(); + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState()); + $parent->setAttributes([ + '_dd.p.some_val' => 'tehehe' + ]); + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState()); + try { + $child = $tracer->spanBuilder("child")->startSpan(); // local parent sampler will be used + + $childContext = $child->getContext(); + $this->assertSame($parent->getContext()->getTraceId(), $childContext->getTraceId()); + $this->assertSame($parent->getContext()->getSpanId(), $child->getParentContext()->getSpanId()); + $this->assertFalse($childContext->isRemote()); // "When creating children from remote spans, their IsRemote flag MUST be set to false." + $this->assertEquals(1, $childContext->getTraceFlags()); // RECORD_AND_SAMPLED ==> 01 (AlwaysOn sampler) + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0,localparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$childContext->getTraceState()); + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0,localparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState()); + + $grandChild = $tracer->spanBuilder("grandChild") + ->setParent(Context::getCurrent()->withContextValue($child)) + ->startSpan(); + $grandChildScope = $grandChild->activate(); + + $grandChildContext = $grandChild->getContext(); + $this->assertSame($parent->getContext()->getTraceId(), $grandChildContext->getTraceId()); + $this->assertSame($child->getContext()->getSpanId(), $grandChild->getParentContext()->getSpanId()); + $this->assertFalse($grandChildContext->isRemote()); // "When creating children from remote spans, their IsRemote flag MUST be set to false." + $this->assertEquals(1, $grandChildContext->getTraceFlags()); // RECORD_AND_SAMPLED ==> 01 (AlwaysOn sampler) + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0,localparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$grandChildContext->getTraceState()); + + $grandChildScope->detach(); + $grandChild->end(); + + $child->end(); + } finally { + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0;t.some_val:tehehe,root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE$/', (string)$parent->getContext()->getTraceState()); + $scope->detach(); + $parent->end(); + } + }); + + $spans = $traces[0]; + list($parentSpan, $childSpan) = $spans; + $this->assertNotEmpty($parentSpan['trace_id']); + $this->assertSame($parentSpan['trace_id'], $parentSpan['span_id']); + $this->assertSame($parentSpan['trace_id'], $childSpan['trace_id']); + $this->assertSame($parentSpan['span_id'], $childSpan['trace_id']); + $this->assertNotSame($parentSpan['span_id'], $childSpan['span_id']); + $this->assertNotSame($childSpan['trace_id'], $childSpan['span_id']); + } + + public function testMultipleTraceStateRemote() + { + $traces = $this->isolateTracer(function () { + $root = new class() implements SamplerInterface { + public function shouldSample( + ContextInterface $parentContext, + string $traceId, + string $spanName, + int $spanKind, + AttributesInterface $attributes, + array $links + ): SamplingResult { + return new SamplingResult( + SamplingResult::RECORD_AND_SAMPLE, + [], + new TraceState("root=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE") + ); + } + + public function getDescription(): string + { + return "Custom Sampler"; + } + }; + + $remoteParentSampler = new class() implements SamplerInterface { + public function shouldSample( + ContextInterface $parentContext, + string $traceId, + string $spanName, + int $spanKind, + AttributesInterface $attributes, + array $links + ): SamplingResult { + return new SamplingResult( + SamplingResult::RECORD_AND_SAMPLE, + [], + new TraceState("remoteparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE") + ); + } + + public function getDescription(): string + { + return "Custom Sampler"; + } + }; + + $remoteParentNotSampler = new class() implements SamplerInterface { + public function shouldSample( + ContextInterface $parentContext, + string $traceId, + string $spanName, + int $spanKind, + AttributesInterface $attributes, + array $links + ): SamplingResult { + return new SamplingResult( + SamplingResult::RECORD_AND_SAMPLE, + [], + new TraceState("remoteparentnot=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE") + ); + } + + public function getDescription(): string + { + return "Custom Sampler"; + } + }; + + // Create a new tracer with a parent based sampling + $tracer = (new TracerProvider([], new ParentBased( + $root, + $remoteParentSampler, + $remoteParentNotSampler + )))->getTracer('OpenTelemetry.TracerTest'); + $remoteContext = SpanContext::createFromRemoteParent( + '4bf92f3577b34da6a3ce929d0e0e4736', + '00f067aa0ba902b7', + TraceFlags::SAMPLED, + new TraceState("rojo=00f067aa0ba902b7,congo=t61rcWkgMzE") + ); + $remoteSpan = new NonRecordingSpan($remoteContext); + + $child = $tracer->spanBuilder("child") + ->setParent(Context::getCurrent()->withContextValue($remoteSpan)) + ->startSpan(); + $scope = $child->activate(); + + $childContext = $child->getContext(); + $this->assertSame($remoteContext->getTraceId(), $childContext->getTraceId()); + $this->assertSame($remoteContext->getSpanId(), $child->getParentContext()->getSpanId()); + $this->assertFalse($childContext->isRemote()); // "When creating children from remote spans, their IsRemote flag MUST be set to false." + $this->assertEquals(1, $childContext->getTraceFlags()); // RECORD_AND_SAMPLED ==> 01 (AlwaysOn sampler) + //$this->assertSame("dd=t.tid:4bf92f3577b34da6;t.dm:-0,remoteparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE", (string)$childContext->getTraceState()); + + $tracer = self::getTracer(); + $grandChild = $tracer->spanBuilder("grandChild") + ->setParent(Context::getCurrent()->withContextValue($child)) + ->startSpan(); + $this->assertSame("dd=t.tid:4bf92f3577b34da6;t.dm:-0,remoteparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE", (string)$child->getContext()->getTraceState()); + $grandChildScope = $grandChild->activate(); + + $grandChildContext = $grandChild->getContext(); + $this->assertSame($remoteContext->getTraceId(), $grandChildContext->getTraceId()); + $this->assertSame($child->getContext()->getSpanId(), $grandChild->getParentContext()->getSpanId()); + $this->assertFalse($grandChildContext->isRemote()); // "When creating children from remote spans, their IsRemote flag MUST be set to false." + $this->assertEquals(1, $grandChildContext->getTraceFlags()); // RECORD_AND_SAMPLED ==> 01 (AlwaysOn sampler) + $this->assertSame("dd=t.tid:4bf92f3577b34da6;t.dm:-0,remoteparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE", (string)$child->getContext()->getTraceState()); + $this->assertSame("dd=t.tid:4bf92f3577b34da6;t.dm:-0,remoteparent=yes,rojo=00f067aa0ba902b7,congo=t61rcWkgMzE", (string)$grandChildContext->getTraceState()); + + $grandChildScope->detach(); + $grandChild->end(); + + try { + $child->end(); + } finally { + $scope->detach(); + } + }); + + $spans = $traces[0]; + list($childSpan) = $spans; + $this->assertSame("11803532876627986230", $childSpan['trace_id']); + $this->assertSame("4bf92f3577b34da6", $childSpan['meta']['_dd.p.tid']); + $this->assertSame("67667974448284343", $childSpan['parent_id']); + } + + public function testAddItemToTracestate() + { + // See https://github.com/open-telemetry/opentelemetry-java/discussions/4008 + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test.span') + ->setSpanKind(SpanKind::KIND_INTERNAL) + ->startSpan(); + + $span->setAttributes([ + '_dd.p.congo' => 't61rcWkgMzE', + ]); + + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0;t.congo:t61rcWkgMzE$/', (string)$span->getContext()->getTraceState()); + + $traceState = $span->getContext()->getTraceState()->with('rojo', '00f067aa0ba902b7'); + $context = SpanContext::create( + $span->getContext()->getTraceId(), + $span->getContext()->getSpanId(), + $span->getContext()->getTraceFlags(), + $traceState + ); + + $child = $tracer->spanBuilder("child") + ->setParent(Context::getCurrent()->withContextValue(Span::wrap($context))) + ->startSpan(); + + $this->assertRegularExpression('/^dd=t.tid:[0-9a-f]{16};t.dm:-0;t.congo:t61rcWkgMzE,rojo=00f067aa0ba902b7$/', (string)$child->getContext()->getTraceState()); + + $child->end(); + $span->end(); + }); + + $span = $traces[0]; + list($childSpan, $span) = $span; + + $this->assertSame('t61rcWkgMzE', $span['meta']['_dd.p.congo']); + $this->assertSame('t61rcWkgMzE', $childSpan['meta']['_dd.p.congo']); + $this->assertSame('-0', $span['meta']['_dd.p.dm']); + $this->assertSame('-0', $childSpan['meta']['_dd.p.dm']); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'test.span') + ->withChildren([ + SpanAssertion::exists('internal', 'child') + ]) + ]); + } + + public function testUpdateOperationNameOnTheFly() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('operation') + ->setSpanKind(SpanKind::KIND_CLIENT) + ->startSpan(); + $scopeSpan = $span->activate(); + + $this->assertSame('client.request', active_span()->name); + + $span->setAttribute('messaging.system', 'Kafka'); + + $this->assertSame('client.request', active_span()->name); + + $span->setAttribute('messaging.operation', 'Receive'); + + $this->assertSame('kafka.receive', active_span()->name); + + $scopeSpan->detach(); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertSame('kafka.receive', $span['name']); + $this->assertSame('operation', $span['resource']); + } +} diff --git a/tests/OpenTelemetry/Integration/Context/FiberTest.php b/tests/OpenTelemetry/Integration/Context/FiberTest.php new file mode 100644 index 00000000000..55697a32fb1 --- /dev/null +++ b/tests/OpenTelemetry/Integration/Context/FiberTest.php @@ -0,0 +1,251 @@ +with($key, 'main') + ->activate(); + + $fiber = new Fiber(function () use ($key) { + $scope = Context::getCurrent() + ->with($key, 'fiber') + ->activate(); + + $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); + + Fiber::suspend(); + + $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); + + $scope->detach(); + }); + + $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); + + $fiber->start(); + + $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); + + $fiber->resume(); + + $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); + + $scope->detach(); + } + + public function test_context_switching_ffi_observer_registered_on_startup() + { + $key = Context::createKey('-'); + + $fiber = new Fiber(function () use ($key) { + $scope = Context::getCurrent() + ->with($key, 'fiber') + ->activate(); + + $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); + + Fiber::suspend(); + + $this->assertSame('fiber:fiber', 'fiber:' . Context::getCurrent()->get($key)); + + $scope->detach(); + }); + + + $fiber->start(); + + $this->assertSame('main:', 'main:' . Context::getCurrent()->get($key)); + + $scope = Context::getCurrent() + ->with($key, 'main') + ->activate(); + + $fiber->resume(); + + $this->assertSame('main:main', 'main:' . Context::getCurrent()->get($key)); + + $scope->detach(); + } + + public function testFiberInteroperabilityStackSwitch() + { + // See tests/ext/fiber_stack_switch.phpt + + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + $this->markTestSkipped('Fibers are only supported in PHP 8.1+'); + } + + $traces = $this->isolateTracer(function () { + $tracer = (new TracerProvider())->getTracer('OpenTelemetry.TestTracer'); + + $parentSpan = $tracer->spanBuilder('parent') + ->setSpanKind(SpanKind::KIND_SERVER) + ->startSpan(); + $parentSpanScope = $parentSpan->activate(); + + $otherFiberFn = function () use ($tracer) { + $currentSpan = Span::getCurrent(); + $this->assertSame('otherFiber', $currentSpan->getName()); + + $span = start_span(); + $span->name = 'dd.otherFiber'; + + $currentSpan = Span::getCurrent(); + $this->assertSame('dd.otherFiber', $currentSpan->getName()); + + Fiber::suspend(); + + $currentSpan = Span::getCurrent(); + $this->assertSame('dd.otherFiber', $currentSpan->getName()); + + close_span(); + + $currentSpan = Span::getCurrent(); + $this->assertSame('otherFiber', $currentSpan->getName()); + + throw new \Exception("ex"); + }; + + $otherFiber = null; + $inFiberFn = function () use ($parentSpan, $otherFiberFn, &$otherFiber, $tracer) { + $currentSpan = Span::getCurrent(); + $this->assertSame('inFiber', $currentSpan->getName()); + + $inFiberOTelSpan = $tracer->spanBuilder('otel.inFiber')->startSpan(); + $inFiberOTelScope = $inFiberOTelSpan->activate(); + + $otherFiber = new Fiber($otherFiberFn(...)); + $otherFiber->start(); + + $currentSpan = Span::getCurrent(); + $this->assertSame('otel.inFiber', $currentSpan->getName()); + + Fiber::suspend(123); + + $currentSpan = Span::getCurrent(); + $this->assertSame('otel.inFiber', $currentSpan->getName()); + + $inFiberOTelScope->detach(); + $inFiberOTelSpan->end(); + + $currentSpan = Span::getCurrent(); + $this->assertSame('inFiber', $currentSpan->getName()); + }; + + \DDTrace\trace_method('Fiber', 'start', function (SpanData $span) { + $span->name = 'fiber.start'; + }); + + \DDTrace\trace_method('Fiber', 'suspend', [ + 'posthook' => function (SpanData $span) { + $span->name = 'fiber.suspend'; + }, + 'recurse' => true + ]); + + \DDTrace\trace_method('Fiber', 'resume', function (SpanData $span) { + $span->name = 'fiber.resume'; + }); + + \DDTrace\install_hook($inFiberFn, function (HookData $hook) { + $span = $hook->span(); + $span->name = 'inFiber'; + }); + + \DDTrace\install_hook($otherFiberFn, function (HookData $hook) { + $span = $hook->span(); + $span->name = 'otherFiber'; + }); + + $currentSpan = Span::getCurrent(); + $this->assertSame('parent', $currentSpan->getName()); + + $fiber = new Fiber($inFiberFn(...)); + $fiber->start(); + + $currentSpan = Span::getCurrent(); + $this->assertSame('parent', $currentSpan->getName()); + + $parentSpan->setAttribute('http.method', 'GET'); + + $fiber->resume(); + + $currentSpan = Span::getCurrent(); + $this->assertSame('parent', $currentSpan->getName()); + + $parentSpan->setAttribute('http.uri', '/parent'); + + try { + $otherFiber->resume(); + } catch (\Exception) { + } + + $currentSpan = Span::getCurrent(); + $this->assertSame('parent', $currentSpan->getName()); + + $parentSpanScope->detach(); + $parentSpan->end(); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('server.request', 'parent')->withChildren([ + SpanAssertion::exists('fiber.start')->withChildren([ + SpanAssertion::exists('inFiber')->withChildren([ + SpanAssertion::exists('internal', 'otel.inFiber')->withChildren([ + SpanAssertion::exists('otherFiber')->withChildren([ + SpanAssertion::exists('dd.otherFiber')->withChildren([ + SpanAssertion::exists('fiber.suspend') + ]) + ])->withExistingTagsNames([ + Tag::ERROR_TYPE, Tag::ERROR_STACK, Tag::ERROR_MSG + ]), + SpanAssertion::exists('fiber.suspend') + ]) + ]) + ]), + SpanAssertion::exists('fiber.resume'), + SpanAssertion::exists('fiber.resume')->withExistingTagsNames([ + Tag::ERROR_TYPE, Tag::ERROR_STACK, Tag::ERROR_MSG + ]) + ]) + ]); + } +} diff --git a/tests/OpenTelemetry/Integration/InteroperabilityTest.php b/tests/OpenTelemetry/Integration/InteroperabilityTest.php new file mode 100644 index 00000000000..86dc3932bdf --- /dev/null +++ b/tests/OpenTelemetry/Integration/InteroperabilityTest.php @@ -0,0 +1,911 @@ +getTracer('OpenTelemetry.TracerTest'); + return $tracer; + } + + public function testActivateAnAlreadyActiveDatadogSpan() + { + $traces = $this->isolateTracer(function () { + $ddSpan = start_span(); + $ddSpan->name = "dd.span"; + $currentSpan = Span::getCurrent(); + + $this->assertNotNull($currentSpan); + $this->assertSame($ddSpan, $currentSpan->getDDSpan()); + $this->assertSame($ddSpan->hexId(), $currentSpan->getContext()->getSpanId()); + $this->assertSame(\DDTrace\root_span()->traceId, $currentSpan->getContext()->getTraceId()); + + // Get current scope + $currentScope = Context::storage()->scope(); + $this->assertNotNull($currentScope); + $currentScope->detach(); + $currentSpan = Span::getCurrent(); + + // Shouldn't have changed + $this->assertNotNull($currentSpan); + $this->assertSame($ddSpan, $currentSpan->getDDSpan()); + $this->assertSame($ddSpan->hexId(), $currentSpan->getContext()->getSpanId()); + $this->assertSame(\DDTrace\root_span()->traceId, $currentSpan->getContext()->getTraceId()); + + close_span(); + $currentSpan = Span::getCurrent(); + $this->assertSame(SpanContextValidator::INVALID_SPAN, $currentSpan->getContext()->getSpanId()); + $this->assertSame(SpanContextValidator::INVALID_TRACE, $currentSpan->getContext()->getTraceId()); + }); + + $span = $traces[0][0]; + $this->assertSame('dd.span', $span['name']); + $this->assertArrayNotHasKey('parent_id', $span['meta']); + } + + /** @noinspection PhpParamsInspection */ + public function testMixingOpenTelemetrylAndDatadogBasic() + { + //$this->markTestSkipped("d"); + self::putEnvAndReloadConfig(["DD_TRACE_DEBUG=1"]); + + $traces = $this->isolateTracer(function () { + $tracer = (new TracerProvider())->getTracer('test.tracer'); + $span = $tracer->spanBuilder("test.span")->startSpan(); + + $currentSpan = Span::getCurrent(); + + $this->assertNotNull($currentSpan); + $this->assertSame(SpanContextValidator::INVALID_TRACE, $currentSpan->getContext()->getTraceId()); + $this->assertSame(SpanContextValidator::INVALID_SPAN, $currentSpan->getContext()->getSpanId()); + $this->assertSame(SpanContext::getInvalid(), $currentSpan->getContext()); + + $scope = $span->activate(); + $currentSpan = Span::getCurrent(); + + $this->assertNotNull($currentSpan); + $this->assertSame($span, $currentSpan); + + $ddSpan = \DDTrace\start_span(); + $ddSpan->name = "other.span"; + $spanId = $ddSpan->hexId(); + $parentId = $ddSpan->parent->hexId(); + $this->assertSame($span->getContext()->getSpanId(), $parentId); + + $currentSpan = Span::getCurrent(); + $this->assertSame($span->getContext()->getSpanId(), $currentSpan->getParentContext()->getSpanId()); + $currentSpan->setAttributes([ + 'foo' => 'bar', + ]); + + $traceId = \DDTrace\root_span()->traceId; + $this->assertSame($traceId, $currentSpan->getContext()->getTraceId()); + $spanId = $ddSpan->hexId(); + $this->assertSame($spanId, $currentSpan->getContext()->getSpanId()); + + close_span(); // Note that we don't detach the scope + $scope->detach(); + $span->end(); + }); + + $spans = $traces[0]; + $this->assertCount(2, $spans); + + list($parent, $child) = $spans; + $this->assertSame('internal', $parent['name']); + $this->assertSame('test.span', $parent['resource']); + $this->assertSame('other.span', $child['name']); + $this->assertSame('other.span', $child['resource']); + $this->assertSame($parent['span_id'], $child['parent_id']); + $this->assertSame($parent['trace_id'], $child['trace_id']); + $this->assertSame('bar', $child['meta']['foo']); + $this->assertArrayNotHasKey('foo', $parent['meta']); + } + + public function testActivateSpanWithAnotherActiveNonActivatedDatadogSpan() + { + $traces = $this->isolateTracer(function () { + $ddSpan = start_span(); + $ddSpan->name = "dd.span"; + + $tracer = self::getTracer(); + $OTelSpan = $tracer->spanBuilder("otel.span")->startSpan(); + + /** @var \OpenTelemetry\SDK\Trace\Span $currentSpan */ + $currentSpan = Span::getCurrent(); + + $this->assertNotNull($currentSpan); + $this->assertSame($currentSpan->getDDSpan(), $ddSpan); // The OTel span wasn't activated, so the current span is the DDTrace span + + $ddOTelSpan = $currentSpan; + $OTelScope = $OTelSpan->activate(); + + /** @var \OpenTelemetry\SDK\Trace\Span $currentSpan */ + $currentSpan = Span::getCurrent(); + + $this->assertNotNull($currentSpan); + $this->assertSame($OTelSpan, $currentSpan); + $this->assertSame($ddOTelSpan->getContext()->getSpanId(), $currentSpan->getParentContext()->getSpanId()); + + $OTelScope->detach(); + $OTelSpan->end(); + $ddOTelSpan->end(); + }); + + $spans = $traces[0]; + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('dd.span', 'dd.span') + ->withChildren([ + SpanAssertion::exists('internal', 'otel.span') + ]) + ]); + } + + public function testCloseSpansUntilWithOnlyDatadogSpans() + { + $traces = $this->isolateTracer(function () { + $span1 = start_span(); + $span1->name = "dd.span1"; + $span2 = start_span(); + $span2->name = "dd.span2"; + $span3 = start_span(); + $span3->name = "dd.span3"; + + $currentSpan = Span::getCurrent(); // Should generate the OTel spans under the hood + $this->assertSame($span3, $currentSpan->getDDSpan()); + + close_spans_until($span1); // Closes And Flush span3 and span2 + // span1 is never flushed since never closed + $currentSpan = Span::getCurrent(); // span2 and span3 are closed, span3 is still open and should be the active span + $this->assertSame($span1, $currentSpan->getDDSpan()); + }); + + $spans = $traces[0]; + $this->assertCount(2, $spans); + + list($span2, $span3) = $spans; + $this->assertSame('dd.span2', $span2['name']); + $this->assertSame('dd.span3', $span3['name']); + $this->assertSame($span2['span_id'], $span3['parent_id']); + $this->assertSame($span2['trace_id'], $span3['trace_id']); + } + + public function testActivateOtelAfterDatadogSpan() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $OTelSpan = $tracer->spanBuilder("otel.span")->startSpan(); + + $ddSpan = start_span(); + $ddSpan->name = "dd.span"; + + $OTelScope = $OTelSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertSame($ddSpan, $currentSpan->getDDSpan()); + + $OTelScope->detach(); + $OTelSpan->end(); + Span::getCurrent()->end(); + }); + + $spans = $traces[0]; + $this->assertCount(2, $spans); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.span') + ->withChildren([ + SpanAssertion::exists('dd.span', 'dd.span') + ]) + ]); + } + + public function testMixingManualAndOtelInstrumentationBis() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $OTelParentSpan = $tracer->spanBuilder("otel.parent.span")->startSpan(); + $OTelParentScope = $OTelParentSpan->activate(); + + $activeSpan = active_span(); + $this->assertNotNull($activeSpan); + $this->assertSame('otel.parent.span', $activeSpan->resource); + $this->assertSame($activeSpan->hexId(), $OTelParentSpan->getContext()->getSpanId()); + + $ddChildSpan = start_span(); + $ddChildSpan->name = "dd.child.span"; + + $ddChildSpanAsOTel = Span::getCurrent(); + + $this->assertNotNull($ddChildSpanAsOTel); + $this->assertSame($ddChildSpan, $ddChildSpanAsOTel->getDDSpan()); + + $OTelGrandChildSpan = $tracer->spanBuilder("otel.grandchild.span")->startSpan(); + $OTelGrandChildScope = $OTelGrandChildSpan->activate(); + + $activeSpan = active_span(); + $this->assertNotNull($activeSpan); + $this->assertSame('otel.grandchild.span', $activeSpan->resource); + $this->assertSame($activeSpan->hexId(), $OTelGrandChildSpan->getContext()->getSpanId()); + + $OTelGrandChildScope->detach(); + $OTelGrandChildSpan->end(); + $ddChildSpanAsOTel->end(); + $OTelParentScope->detach(); + $OTelParentSpan->end(); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.parent.span') + ->withChildren([ + SpanAssertion::exists('dd.child.span', 'dd.child.span') + ->withChildren([ + SpanAssertion::exists('internal', 'otel.grandchild.span') + ]) + ]) + ]); + } + + public function testStartNewTraces() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $OTelRootSpan = $tracer->spanBuilder("otel.root.span")->startSpan(); + $OTelRootScope = $OTelRootSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($OTelRootSpan, $currentSpan); + + $OTelChildSpan = $tracer->spanBuilder("otel.child.span")->startSpan(); + $OTelChildScope = $OTelChildSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($OTelChildSpan, $currentSpan); + + $DDChildSpan = start_span(); + $DDChildSpan->name = "dd.child.span"; + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($DDChildSpan, $currentSpan->getDDSpan()); + + $DDRootSpan = start_trace_span(); + $DDRootSpan->name = "dd.root.span"; + + $DDRootOTelSpan = $tracer->spanBuilder("dd.root.otel.span")->startSpan(); + $DDRootOTelScope = $DDRootOTelSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($DDRootOTelSpan, $currentSpan); + + $DDRootChildSpan = start_span(); + $DDRootChildSpan->name = "dd.root.child.span"; + + close_span(); // Closes DDRootChildSpan + $DDRootOTelScope->detach(); + $DDRootOTelSpan->end(); + close_span(); // Closes and flushes DDRootSpan + + close_span(); // Closes DDChildSpan + + $OTelChildScope->detach(); + $OTelChildSpan->end(); + + $OTelRootScope->detach(); + $OTelRootSpan->end(); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.root.span') + ->withChildren([ + SpanAssertion::exists('internal', 'otel.child.span') + ->withChildren([ + SpanAssertion::exists('dd.child.span', 'dd.child.span') + ]) + ]), + SpanAssertion::exists('dd.root.span', 'dd.root.span') + ->withChildren([ + SpanAssertion::exists('internal', 'dd.root.otel.span') + ->withChildren([ + SpanAssertion::exists('dd.root.child.span', 'dd.root.child.span') + ]) + ]) + ]); + } + + public function testStartNewTracesWithCloseSpansUntil() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $OTelRootSpan = $tracer->spanBuilder("otel.root.span")->startSpan(); + $OTelRootScope = $OTelRootSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($OTelRootSpan, $currentSpan); + + $OTelChildSpan = $tracer->spanBuilder("otel.child.span")->startSpan(); + $OTelChildScope = $OTelChildSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($OTelChildSpan, $currentSpan); + + $DDChildSpan = start_span(); + $DDChildSpan->name = "dd.child.span"; + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($DDChildSpan, $currentSpan->getDDSpan()); + + $DDRootSpan = start_trace_span(); + $DDRootSpan->name = "dd.root.span"; + + $DDRootOTelSpan = $tracer->spanBuilder("dd.root.otel.span")->startSpan(); + $DDRootOTelScope = $DDRootOTelSpan->activate(); + + $currentSpan = Span::getCurrent(); + $this->assertNotNull($currentSpan); + $this->assertSame($DDRootOTelSpan, $currentSpan); + + $DDRootChildSpan = $tracer->spanBuilder("dd.root.child.span")->startSpan(); + $DDRootChildScope = $DDRootChildSpan->activate(); + + $DDRootChildScope->detach(); + $DDRootOTelScope->detach(); + close_spans_until(null); // Closes DDRootChildSpan, DDRootOTelSpan and DDRootSpan + + close_span(); // Closes DDChildSpan + $OTelChildScope->detach(); + $OTelRootScope->detach(); + close_spans_until(null); // Closes OTelChildSpan and OTelRootSpan + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.root.span') + ->withChildren([ + SpanAssertion::exists('internal', 'otel.child.span') + ->withChildren([ + SpanAssertion::exists('dd.child.span', 'dd.child.span') + ]) + ]), + SpanAssertion::exists('dd.root.span', 'dd.root.span') + ->withChildren([ + SpanAssertion::exists('internal', 'dd.root.otel.span') + ->withChildren([ + SpanAssertion::exists('internal', 'dd.root.child.span') + ]) + ]) + ]); + } + + public function testMixingSetParentContext() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $OTelRootSpan = $tracer->spanBuilder("otel.root.span")->startSpan(); + $OTelRootScope = $OTelRootSpan->activate(); + + $DDRootSpan = start_trace_span(); + $DDRootSpan->name = "dd.root.span"; + + $DDRootSpanContext = Context::getCurrent(); + + // Create a new OTel span with the OTel root span as parent + $OTelChildSpan = $tracer->spanBuilder("otel.child.span") + ->setParent(Context::getCurrent()->withContextValue($OTelRootSpan)) + ->startSpan(); + $OTelChildScope = $OTelChildSpan->activate(); + + $DDChildSpan = start_span(); + $DDChildSpan->name = "dd.child.span"; + + // Create a new OTel span with the DD root span as parent + $OTelGrandChildSpan = $tracer->spanBuilder("otel.grandchild.span") + ->setParent($DDRootSpanContext) + ->startSpan(); + $OTelGrandChildScope = $OTelGrandChildSpan->activate(); + + // Create a new DD span with the DD child span as parent + $DDGrandChildSpan = start_span(); + $DDGrandChildSpan->name = "dd.grandchild.span"; + + (Span::getCurrent())->end(); // Closes DDGrandChildSpan + $OTelGrandChildScope->detach(); + $OTelGrandChildSpan->end(); + + close_span(); // Closes DDChildSpan + $OTelChildScope->detach(); + $OTelChildSpan->end(); + + (Span::getCurrent())->end(); // Closes DDRootSpan + $OTelRootScope->detach(); + $OTelRootSpan->end(); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.root.span') + ->withChildren([ + SpanAssertion::exists('internal', 'otel.child.span') + ->withChildren([ + SpanAssertion::exists('dd.child.span', 'dd.child.span') + ]) + ]), + SpanAssertion::exists('dd.root.span', 'dd.root.span') + ->withChildren([ + SpanAssertion::exists('internal', 'otel.grandchild.span') + ->withChildren([ + SpanAssertion::exists('dd.grandchild.span', 'dd.grandchild.span') + ]) + ]) + ]); + } + + public function testMixingMultipleTraces() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $OTelTrace1 = $tracer->spanBuilder("otel.trace1")->startSpan(); + $OTelTrace1Scope = $OTelTrace1->activate(); + $OTelChild1 = $tracer->spanBuilder("otel.child1")->startSpan(); + $OTelChild1Scope = $OTelChild1->activate(); + + $OTelTrace2 = $tracer->spanBuilder("otel.trace2")->setParent(false)->startSpan(); + $OTelTrace2Scope = $OTelTrace2->activate(); + $DDChild2 = start_span(); + $DDChild2->name = "dd.child2"; + + $OTelTrace1->setAttribute('foo1', 'bar1'); + + $DDTrace1 = start_trace_span(); + $DDTrace1->name = "dd.trace1"; + + //$currentSpan = Span::getCurrent(); + //$this->assertNotNull($currentSpan); + //$this->assertSame($DDTrace1, $currentSpan->getDDSpan()); + + $DDChild1 = start_span(); + $DDChild1->name = "dd.child1"; + + $OTelChild1->setAttribute('foo2', 'bar2'); + $OTelChild1->setAttribute(Tag::SERVICE_NAME, 'my.service'); + + $DDTrace2 = start_trace_span(); + $DDTrace2->name = "dd.trace2"; + $OTelChild2 = $tracer->spanBuilder("otel.child2")->startSpan(); + $OTelChild2Scope = $OTelChild2->activate(); + + $DDTrace1->meta['foo1'] = 'bar1'; + + // Add an OTel span to OTelTrace1 + $OTelChild3 = $tracer->spanBuilder("otel.child3") + ->setParent(Context::getCurrent()->withContextValue($OTelChild1)) + ->startSpan(); + $OTelChild3Scope = $OTelChild3->activate(); + + $OTelChild3->setAttribute('foo3', 'bar3'); + $OTelChild3->setAttribute(Tag::RESOURCE_NAME, 'my.resource'); + + // Add an OTel span to OTelChild2 + $OTelChild4 = $tracer->spanBuilder("otel.child4") + ->setParent(Context::getCurrent()->withContextValue($OTelChild2)) + ->startSpan(); + $OTelChild4Scope = $OTelChild4->activate(); + + $OTelChild3->setAttribute('foo3', 'bar3'); + + $OTelChild4Scope->detach(); + $OTelChild2Scope->detach(); + close_spans_until(null); // Closes DDTrace2 + close_spans_until(null); // Closes DDTrace1 + $OTelTrace2Scope->detach(); + close_spans_until(null); // Closes OTelTrace2 + $OTelChild3Scope->detach(); + $OTelChild3->end(); + $OTelChild1Scope->detach(); + $OTelChild1->end(); + $OTelTrace1Scope->detach(); + $OTelTrace1->end(); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.trace1', null, 'datadog/dd-trace-tests') + ->withExistingTagsNames(['foo1']) + ->withChildren( + SpanAssertion::exists('internal', 'otel.child1', null, 'my.service') + ->withExistingTagsNames(['foo2']) + ->withChildren( + SpanAssertion::exists('internal', 'my.resource', null, 'datadog/dd-trace-tests') + ->withExistingTagsNames(['foo3']) + ) + ), + SpanAssertion::exists('internal', 'otel.trace2', null, 'datadog/dd-trace-tests') + ->withChildren( + SpanAssertion::exists('dd.child2', 'dd.child2', null, 'datadog/dd-trace-tests') + ), + SpanAssertion::build('dd.trace1', 'phpunit', 'cli', 'dd.trace1') + ->withExactTags([ + 'foo1' => 'bar1', + ]) + ->withChildren( + SpanAssertion::exists('dd.child1', 'dd.child1', null, 'datadog/dd-trace-tests') + ), + SpanAssertion::exists('dd.trace2', 'dd.trace2', null, 'datadog/dd-trace-tests') + ->withChildren( + SpanAssertion::exists('internal', 'otel.child2', null, 'datadog/dd-trace-tests') + ->withChildren( + SpanAssertion::exists('internal', 'otel.child4', null, 'datadog/dd-trace-tests') + ) + ), + ], true, false); + } + + public function testW3CInteroperability() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $propagator = TraceContextPropagator::getInstance(); + + $carrier = [ + TraceContextPropagator::TRACEPARENT => '00-ff0000000000051791e0000000000041-ff00051791e00041-01' + ]; + + $context = $propagator->extract($carrier); + + $OTelRootSpan = $tracer->spanBuilder("otel.root.span") + ->setParent($context) + ->startSpan(); + $OTelRootScope = $OTelRootSpan->activate(); + + $DDChildSpan = start_span(); + $DDChildSpan->name = "dd.child.span"; + + $DDChildSpanAsOtel = Span::getCurrent(); + $DDChildSpanId = $DDChildSpanAsOtel->getContext()->getSpanId(); + + $carrier = []; + $propagator->inject( + $carrier, + null, + Context::getCurrent()->withContextValue($DDChildSpanAsOtel) + ); + + $DDChildSpanAsOtel->end(); + $OTelRootScope->detach(); + $OTelRootSpan->end(); + + $this->assertSame("00-ff0000000000051791e0000000000041-$DDChildSpanId-01", $carrier[TraceContextPropagator::TRACEPARENT]); + $this->assertSame('dd=t.tid:ff00000000000517;t.dm:-0', $carrier[TraceContextPropagator::TRACESTATE]); // ff00000000000517 is the high 64-bit part of the 128-bit trace id + }); + + $this->assertSame('10511401530282737729', $traces[0][0]['trace_id']); + $this->assertSame('18374692078461386817', $traces[0][0]['parent_id']); + + $otelRootSpan = $traces[0][0]; + $this->assertSame('ff00000000000517', $otelRootSpan['meta']['_dd.p.tid']); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.root.span', 'datadog/dd-trace-tests') + ->withChildren( + SpanAssertion::exists('dd.child.span', 'dd.child.span', 'datadog/dd-trace-tests') + ) + ]); + } + + public function testB3SingleInteroperability() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $carrier = [ + 'b3' => 'ff0000000000051791e0000000000041-ff00051791e00041' + ]; + + $context = $propagator->extract($carrier); + + $OTelRootSpan = $tracer->spanBuilder("otel.root.span") + ->setParent($context) + ->startSpan(); + $OTelRootScope = $OTelRootSpan->activate(); + + $DDChildSpan = start_span(); + $DDChildSpan->name = "dd.child.span"; + + $DDChildSpanAsOtel = Span::getCurrent(); + $DDChildSpanId = $DDChildSpanAsOtel->getContext()->getSpanId(); + + // Inject + $carrier = []; + $propagator->inject( + $carrier, + null, + Context::getCurrent()->withContextValue($DDChildSpanAsOtel) + ); + + $DDChildSpanAsOtel->end(); + $OTelRootScope->detach(); + $OTelRootSpan->end(); + + $this->assertSame("ff0000000000051791e0000000000041-$DDChildSpanId-1", $carrier['b3']); + }); + + $this->assertSame('10511401530282737729', $traces[0][0]['trace_id']); + $this->assertSame('18374692078461386817', $traces[0][0]['parent_id']); + + $otelRootSpan = $traces[0][0]; + $this->assertSame('ff00000000000517', $otelRootSpan['meta']['_dd.p.tid']); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.root.span', 'datadog/dd-trace-tests') + ->withChildren( + SpanAssertion::exists('dd.child.span', 'dd.child.span', 'datadog/dd-trace-tests') + ) + ]); + } + + public function testB3MultiInteroperability() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $carrier = [ + 'X-B3-TraceId' => 'ff0000000000051791e0000000000041', + 'X-B3-SpanId' => 'ff00051791e00041', + 'X-B3-Sampled' => '1' + ]; + + $context = $propagator->extract($carrier); + + $OTelRootSpan = $tracer->spanBuilder("otel.root.span") + ->setParent($context) + ->startSpan(); + $OTelRootScope = $OTelRootSpan->activate(); + + $DDChildSpan = start_span(); + $DDChildSpan->name = "dd.child.span"; + + $DDChildSpanAsOtel = Span::getCurrent(); + $DDChildSpanId = $DDChildSpanAsOtel->getContext()->getSpanId(); + + // Inject + $carrier = []; + $propagator->inject( + $carrier, + null, + Context::getCurrent()->withContextValue($DDChildSpanAsOtel) + ); + + $DDChildSpanAsOtel->end(); + $OTelRootScope->detach(); + $OTelRootSpan->end(); + + $this->assertSame('ff0000000000051791e0000000000041', $carrier['X-B3-TraceId']); + $this->assertSame($DDChildSpanId, $carrier['X-B3-SpanId']); + $this->assertSame('1', $carrier['X-B3-Sampled']); + }); + + $this->assertSame('10511401530282737729', $traces[0][0]['trace_id']); + $this->assertSame('18374692078461386817', $traces[0][0]['parent_id']); + + $otelRootSpan = $traces[0][0]; + $this->assertSame('ff00000000000517', $otelRootSpan['meta']['_dd.p.tid']); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('internal', 'otel.root.span', 'datadog/dd-trace-tests') + ->withChildren( + SpanAssertion::exists('dd.child.span', 'dd.child.span', 'datadog/dd-trace-tests') + ) + ]); + } + + public function testBaggageInteroperability() + { + $traces = $this->isolateTracer(function () { + $tracer = (new TracerProvider())->getTracer('OpenTelemetry.TestTracer'); + + $parentSpan = $tracer->spanBuilder('parent') + ->setSpanKind(SpanKind::KIND_SERVER) + ->startSpan(); + $parentSpanScope = $parentSpan->activate(); + + $baggage = Baggage::getBuilder() + ->set('user.id', '1') + ->set('user.name', 'name') + ->build(); + $baggageScope = $baggage->storeInContext(Context::getCurrent())->activate(); + + $childSpan = start_span(); + $childSpan->name = 'child'; + $childSpan->meta['user.id'] = Baggage::getCurrent()->getValue('user.id'); + + close_span(); + + $parentSpan->setAttribute('http.method', 'GET'); + $parentSpan->setAttribute('http.uri', '/parent'); + + $baggageScope->detach(); + $parentSpanScope->detach(); + $parentSpan->end(); + }); + + list($parent, $child) = $traces[0]; + $this->assertSame(Tag::SPAN_KIND_VALUE_SERVER, $parent['meta'][Tag::SPAN_KIND]); + $this->assertSame('GET', $parent['meta']['http.method']); + $this->assertSame('/parent', $parent['meta']['http.uri']); + $this->assertSame('1', $child['meta']['user.id']); + + $this->assertFlameGraph($traces, [ + SpanAssertion::exists('server.request', 'parent', 'datadog/dd-trace-tests') + ->withChildren([ + SpanAssertion::exists('child', 'child', 'datadog/dd-trace-tests') + ]) + ]); + } + + public function testSpecialAttributes() + { + $traces = $this->isolateTracer(function () { + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('otel.span') + ->setSpanKind(SpanKind::KIND_SERVER) + ->startSpan(); + $span->setAttributes([ + 'http.request.method' => 'GET', + 'resource.name' => 'new.name', + 'operation.name' => 'Overriden.name', + 'service.name' => 'new.service.name', + 'span.type' => 'new.span.type', + 'analytics.event' => 'true', + ]); + + $span->end(); + }); + + $this->assertCount(1, $traces[0]); + $span = $traces[0][0]; + $this->assertSame('overriden.name', $span['name']); + $this->assertSame('new.name', $span['resource']); + $this->assertSame('new.service.name', $span['service']); + $this->assertSame('new.span.type', $span['type']); + $this->assertEquals(1.0, $span['metrics']['_dd1.sr.eausr']); + } + + public function testHasEnded() + { + $this->isolateTracer(function () { + start_span(); + $otelSpan = Span::getCurrent(); + close_span(); + $this->assertTrue($otelSpan->hasEnded()); + }); + } + + public function testAttributesInteroperability() + { + $traces = $this->isolateTracer(function () { + $span = start_span(); + $span->name = "dd.span"; + /** @var \OpenTelemetry\SDK\Trace\Span $otelSpan */ + $span->meta['arg1'] = 'value1'; + $span->meta['arg2'] = 'value2'; + $otelSpan = Span::getCurrent(); + $otelSpan->setAttribute('arg5', 'value5'); + $otelSpan->setAttribute('arg6', 'value6'); + $otelSpan->setAttribute('m1', 1); + $span->metrics['m2'] = 2; + unset($span->meta['arg1']); // Removes the arg1 -> value1 pair + $this->assertNull($otelSpan->getAttribute('arg1')); + $span->meta['arg3'] = 'value3'; + $otelSpan->setAttribute('key', 'value'); + $otelSpan->setAttribute('arg5', null); // Removes the arg5 -> value5 pair + close_span(); // Not flushed, yet + $span->meta['arg4'] = 'value4'; // Added + $otelSpan->setAttribute('post', 'val'); // Not Added (purposely) + + $this->assertTrue($otelSpan->hasEnded()); + $currentTime = (int) (microtime(true) * 1e9); + $this->assertNotEquals(0, $otelSpan->toSpanData()->getEndEpochNanos()); + $this->assertLessThanOrEqual($currentTime, $otelSpan->toSpanData()->getEndEpochNanos()); + + $attributes = $otelSpan->toSpanData()->getAttributes()->toArray(); + $this->assertArrayNotHasKey('arg1', $attributes); + $this->assertSame('value2', $attributes['arg2']); + $this->assertSame('value3', $attributes['arg3']); + $this->assertSame('value4', $attributes['arg4']); + $this->assertArrayNotHasKey('arg5', $attributes); + $this->assertSame('value6', $attributes['arg6']); + $this->assertSame('value', $attributes['key']); + $this->assertArrayNotHasKey('post', $attributes); + + $this->assertEquals(1, $attributes['m1']); + $this->assertEquals(2, $attributes['m2']); + }); + + $this->assertCount(1, $traces[0]); + + $meta = $traces[0][0]['meta']; + $this->assertArrayNotHasKey('arg1', $meta); + $this->assertSame('value2', $meta['arg2']); + $this->assertSame('value3', $meta['arg3']); + $this->assertSame('value4', $meta['arg4']); + $this->assertArrayNotHasKey('arg5', $meta); + $this->assertSame('value6', $meta['arg6']); + $this->assertSame('value', $meta['key']); + $this->assertArrayNotHasKey('post', $meta); + + $this->assertEquals(1, $traces[0][0]['metrics']['m1']); + $this->assertEquals(2, $traces[0][0]['metrics']['m2']); + } + + +} diff --git a/tests/OpenTelemetry/Integration/Logs/HandlerTest.php b/tests/OpenTelemetry/Integration/Logs/HandlerTest.php new file mode 100644 index 00000000000..1e1e6abc206 --- /dev/null +++ b/tests/OpenTelemetry/Integration/Logs/HandlerTest.php @@ -0,0 +1,164 @@ +storage = new ArrayObject(); + $exporter = new InMemoryExporter($this->storage); + $loggerProvider = new LoggerProvider( + new SimpleLogRecordProcessor($exporter), + new InstrumentationScopeFactory(Attributes::factory()), + ); + $handler = new Handler($loggerProvider, 200); + $this->logger = new Logger('test'); + $this->logger->pushHandler($handler); + } + + public function ddTearDown() + { + parent::ddTearDown(); + $this->putEnvAndReloadConfig([ + 'DD_TRACE_APPEND_TRACE_IDS_TO_LOGS=', + 'APP_NAME=', + 'DD_ENV=', + 'DD_SERVICE=', + 'DD_VERSION=', + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=', + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=' + ]); + \dd_trace_serialize_closed_spans(); + } + + public static function getTracer() + { + // Generate a unique key of length 10 + $uniqueKey = substr(md5(uniqid()), 0, 10); + $tracer = (new TracerProvider([], new AlwaysOnSampler()))->getTracer("OpenTelemetry.TracerTest$uniqueKey"); + return $tracer; + } + + public function test_logs_correlation_context(): void + { + $this->putEnvAndReloadConfig([ + 'DD_TRACE_APPEND_TRACE_IDS_TO_LOGS=0', + 'APP_NAME=logs_test', + 'DD_ENV=my-env', + 'DD_SERVICE=my-service', + 'DD_VERSION=4.2', + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=1', + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=1', + 'DD_LOGS_INJECTION=1' + ]); + + $this->assertCount(0, $this->storage); + + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test_span')->startSpan(); + $scope = $span->activate(); + + $traceId = $span->getContext()->getTraceId(); + $spanId = active_span()->id; + + /** @psalm-suppress UndefinedDocblockClass */ + $this->logger->warning('My Warning Message'); + + $scope->detach(); + $span->end(); + + $this->assertCount(1, $this->storage); + /** @var ReadWriteLogRecord $record */ + $record = $this->storage->offsetGet(0); + $context = $record->getAttributes()->toArray()['context']; + + $this->assertSame($spanId, $context['dd.span_id']); + $this->assertSame($traceId, $context['dd.trace_id']); + $this->assertSame('my-service', $context['dd.service']); + $this->assertSame('4.2', $context['dd.version']); + $this->assertSame('my-env', $context['dd.env']); + } + + public function test_logs_correlation_placeholders(): void + { + $this->putEnvAndReloadConfig([ + 'DD_TRACE_APPEND_TRACE_IDS_TO_LOGS=0', + 'APP_NAME=logs_test', + 'DD_ENV=my-env', + 'DD_SERVICE=my-service', + 'DD_VERSION=4.2', + 'DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED=1', + 'DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=1', + 'DD_LOGS_INJECTION=1' + ]); + + $this->assertCount(0, $this->storage); + + $tracer = self::getTracer(); + $span = $tracer->spanBuilder('test_span')->startSpan(); + $scope = $span->activate(); + + $traceId = $span->getContext()->getTraceId(); + $spanId = active_span()->id; + + /** @psalm-suppress UndefinedDocblockClass */ + $this->logger->info('My Warning Message [%dd.trace_id% %dd.span_id% %dd.service% %dd.version% %dd.env%]'); + + $scope->detach(); + $span->end(); + + $this->assertCount(1, $this->storage); + /** @var ReadWriteLogRecord $record */ + $record = $this->storage->offsetGet(0); + $this->assertSame( + "My Warning Message [dd.trace_id=\"$traceId\" dd.span_id=\"$spanId\" dd.service=\"my-service\" dd.version=\"4.2\" dd.env=\"my-env\"]", + $record->getBody() + ); + } + + public function test_log_info(): void + { + $this->assertCount(0, $this->storage); + /** @psalm-suppress UndefinedDocblockClass */ + $this->logger->info('foo'); + $this->assertCount(1, $this->storage); + /** @var ReadWriteLogRecord $record */ + $record = $this->storage->offsetGet(0); + $this->assertInstanceOf(LogRecord::class, $record); + $this->assertSame('INFO', $record->getSeverityText()); + $this->assertSame(9, $record->getSeverityNumber()); + $this->assertGreaterThan(0, $record->getTimestamp()); + $this->assertSame('test', $record->getInstrumentationScope()->getName(), 'scope name is set from logger name'); + } + + public function test_log_debug_is_not_handled(): void + { + //handler is configured with info level, so debug should be ignored + $this->assertCount(0, $this->storage); + /** @psalm-suppress UndefinedDocblockClass */ + $this->logger->debug('debug message'); + $this->assertCount(0, $this->storage); + } +} diff --git a/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php b/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php new file mode 100644 index 00000000000..d024e1a1faf --- /dev/null +++ b/tests/OpenTelemetry/Integration/SDK/SpanBuilderTest.php @@ -0,0 +1,666 @@ +spanProcessor = Mockery::spy(SpanProcessorInterface::class); + $this->tracerProvider = new TracerProvider($this->spanProcessor); + $this->tracer = $this->tracerProvider->getTracer('SpanBuilderTest'); + + $this->sampledSpanContext = SpanContext::create( + '12345678876543211234567887654321', + '8765432112345678', + API\TraceFlags::SAMPLED, + ); + } + + protected function tearDown() : void + { + Context::setStorage(new ContextStorage()); // Reset OpenTelemetry context + BaseTestCase::putEnv("DD_TRACE_GENERATE_ROOT_SPAN="); + \dd_trace_serialize_closed_spans(); + } + + /** @noinspection PhpParamsInspection */ + public function test_set_parent_invalid_context(): void + { + $parentSpan = Span::getInvalid(); + + $parentContext = Context::getCurrent()->withContextValue($parentSpan); + + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setParent($parentContext)->startSpan(); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($span, $parentContext) + ->once(); + + /** @noinspection PhpParamsInspection */ + $this->assertNotSame( + $span->getContext()->getTraceId(), + $parentSpan->getContext()->getTraceId() + ); + + $this->assertFalse(SpanContextValidator::isValidSpanId($span->toSpanData()->getParentSpanId())); + + $span->end(); + $parentSpan->end(); + } + + /** + * @group trace-compliance + */ + public function test_add_link(): void + { + $this->markTestSkipped("Span Links aren't yet supported"); + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->addLink($this->sampledSpanContext) + ->addLink($this->sampledSpanContext, []) + ->startSpan(); + + $this->assertCount(2, $span->toSpanData()->getLinks()); + } + + public function test_add_link_invalid(): void + { + $this->markTestIncomplete("Span Links aren't yet supported"); + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->addLink(Span::getInvalid()->getContext()) + ->addLink(Span::getInvalid()->getContext(), []) + ->startSpan(); + + $this->assertEmpty($span->toSpanData()->getLinks()); + $span->end(); + } + + public function test_add_link_dropping_links(): void + { + $this->markTestSkipped("Span Links aren't yet supported"); + $maxNumberOfLinks = 8; + $tracerProvider = new TracerProvider([], null, null, (new SpanLimitsBuilder())->setLinkCountLimit($maxNumberOfLinks)->build()); + $spanBuilder = $tracerProvider + ->getTracer('test') + ->spanBuilder(self::SPAN_NAME); + + for ($idx = 0; $idx < $maxNumberOfLinks * 2; $idx++) { + $spanBuilder->addLink($this->sampledSpanContext); + } + + /** @var Span $span */ + $span = $spanBuilder->startSpan(); + + $spanData = $span->toSpanData(); + $links = $spanData->getLinks(); + + $this->assertCount($maxNumberOfLinks, $links); + $this->assertSame(8, $spanData->getTotalDroppedLinks()); + + for ($idx = 0; $idx < $maxNumberOfLinks; $idx++) { + $this->assertEquals(new Link($this->sampledSpanContext, Attributes::create([])), $links[$idx]); + } + + $span->end(); + } + + public function test_add_link_truncate_link_attributes(): void + { + $this->markTestSkipped("Span Links aren't yet supported"); + $tracerProvider = new TracerProvider([], null, null, (new SpanLimitsBuilder())->setAttributePerLinkCountLimit(1)->build()); + /** @var Span $span */ + $span = $tracerProvider + ->getTracer('test') + ->spanBuilder(self::SPAN_NAME) + ->addLink( + $this->sampledSpanContext, + [ + 'key0' => 0, + 'key1' => 1, + 'key2' => 2, + ] + ) + ->startSpan(); + + $this->assertCount(1, $span->toSpanData()->getLinks()); + $this->assertCount(1, $span->toSpanData()->getLinks()[0]->getAttributes()); + } + + public function test_add_link_truncate_link_attribute_value(): void + { + $this->markTestSkipped("Spans Links aren't yet supported"); + $maxLength = 25; + + $strVal = str_repeat('a', $maxLength); + $tooLongStrVal = "{$strVal}{$strVal}"; + + $tracerProvider = new TracerProvider([], null, null, (new SpanLimitsBuilder())->setAttributeValueLengthLimit($maxLength)->build()); + /** @var Span $span */ + $span = $tracerProvider + ->getTracer('test') + ->spanBuilder(self::SPAN_NAME) + ->addLink( + $this->sampledSpanContext, + [ + 'string' => $tooLongStrVal, + 'bool' => true, + 'string_array' => [$strVal, $tooLongStrVal], + 'int_array' => [1, 2], + ] + ) + ->startSpan(); + + $attrs = $span->toSpanData()->getLinks()[0]->getAttributes(); + $this->assertSame($strVal, $attrs->get('string')); + $this->assertTrue($attrs->get('bool')); + $this->assertSame( + [$strVal, $strVal], + $attrs->get('string_array') + ); + $this->assertSame( + [1, 2], + $attrs->get('int_array') + ); + } + + /** + * @group trace-compliance + */ + public function test_add_link_no_effect_after_start_span(): void + { + $this->markTestSkipped("Span Links aren't yet supported"); + $spanBuilder = $this->tracer->spanBuilder(self::SPAN_NAME); + + /** @var Span $span */ + $span = $spanBuilder + ->addLink($this->sampledSpanContext) + ->startSpan(); + + $this->assertCount(1, $span->toSpanData()->getLinks()); + + $spanBuilder + ->addLink( + SpanContext::create( + '00000000000004d20000000000001a85', + '0000000000002694', + API\TraceFlags::SAMPLED + ) + ); + + $this->assertCount(1, $span->toSpanData()->getLinks()); + } + + /** + * @group trace-compliance + */ + public function test_set_attribute(): void + { + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 123) + ->setAttribute('empty-arr', []) + ->setAttribute('int-arr', [1, 2, 3]) + ->setAttribute('nil', null) + ->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + + $this->assertSame('bar', $attributes->get('foo')); + $this->assertSame(123, $attributes->get('bar')); + $this->assertSame([], $attributes->get('empty-arr')); + $this->assertSame([1, 2, 3], $attributes->get('int-arr')); + $this->assertNull($attributes->get('nil')); + } + + /** + * @group trace-compliance + */ + public function test_set_attribute_no_effect_after_end(): void + { + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 123) + ->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + $count = $attributes->count(); + $this->assertSame('bar', $attributes->get('foo')); + $this->assertSame(123, $attributes->get('bar')); + + $span->end(); + + $span->setAttribute('doo', 'baz'); + + $this->assertSame($count, $attributes->count()); + $this->assertFalse($attributes->has('doo')); + } + + /** + * @group trace-compliance + */ + // public function test_set_attribute_empty_string_value_is_set(): void + // { + // /** @var Span $span */ + // $span = $this + // ->tracer + // ->spanBuilder(self::SPAN_NAME) + // ->setAttribute('nil', null) + // ->setAttribute('empty-string', '') + // ->startSpan(); + + // $attributes = $span->toSpanData()->getAttributes(); + // $this->assertSame(1, $attributes->count()); + // $this->assertSame('', $attributes->get('empty-string')); + // $this->assertNull($attributes->get('nil')); + + // $span->end(); + // } + + /** + * @group trace-compliance + */ + public function test_set_attribute_only_null_string_value_should_not_be_set(): void + { + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setAttribute('nil', null) + ->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + // Check that only span kind is set + $this->assertNull($attributes->get('nil')); + $this->assertSame(API\SpanKind::KIND_INTERNAL, $span->getKind()); + $this->assertNull($attributes->get('nil')); + } + + /** + * @group trace-compliance + */ + public function test_set_attribute_no_effect_after_start_span(): void + { + $spanBuilder = $this->tracer->spanBuilder(self::SPAN_NAME); + + /** @var Span $span */ + $span = $spanBuilder + ->setAttribute('foo', 'bar') + ->setAttribute('bar', 123) + ->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + $count = $attributes->count(); + $this->assertSame($count, $attributes->count()); + + $spanBuilder + ->setAttribute('bar1', 77); + + $attributes = $span->toSpanData()->getAttributes(); + $this->assertSame($count, $attributes->count()); + $this->assertFalse($attributes->has('bar1')); + } + + public function test_set_attribute_dropping(): void + { + $this->markTestSkipped("Span Attributes Limits aren't yet supported"); + $maxNumberOfAttributes = 8; + $tracerProvider = new TracerProvider( + null, + null, + null, + (new SpanLimitsBuilder())->setAttributeCountLimit($maxNumberOfAttributes)->build() + ); + $spanBuilder = $tracerProvider + ->getTracer('test')->spanBuilder(self::SPAN_NAME); + + foreach (range(1, $maxNumberOfAttributes * 2) as $idx) { + $spanBuilder->setAttribute("str_attribute_{$idx}", $idx); + } + + /** @var Span $span */ + $span = $spanBuilder->startSpan(); + $attributes = $span->toSpanData()->getAttributes(); + + $this->assertCount($maxNumberOfAttributes, $attributes); + + foreach (range(1, $maxNumberOfAttributes) as $idx) { + $this->assertSame($idx, $attributes->get("str_attribute_{$idx}")); + } + } + + public function test_add_attributes_via_sampler(): void + { + $sampler = new class() implements SamplerInterface { + public function shouldSample( + ContextInterface $parentContext, + string $traceId, + string $spanName, + int $spanKind, + AttributesInterface $attributes, + array $links + ): SamplingResult { + return new SamplingResult(SamplingResult::RECORD_AND_SAMPLE, ['cat' => 'meow']); + } + + public function getDescription(): string + { + return 'test'; + } + }; + + $tracerProvider = new TracerProvider([], $sampler); + /** @var Span $span */ + $span = $tracerProvider->getTracer('test')->spanBuilder(self::SPAN_NAME)->startSpan(); + $span->end(); + + $attributes = $span->toSpanData()->getAttributes(); + + $this->assertSame('meow', $attributes->get('cat')); + } + + public function test_set_attributes(): void + { + $attributes = ['id' => 1, 'foo' => 'bar']; + + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setAttributes($attributes)->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + + $this->assertSame('bar', $attributes->get('foo')); + $this->assertSame(1, $attributes->get('id')); + } + + /** + * @group trace-compliance + */ + public function test_set_attributes_merges_attributes_correctly(): void + { + $attributes = ['id' => 2, 'foo' => 'bar', 'key' => 'val']; + + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setAttribute('key2', 'val2') + ->setAttribute('key1', 'val1') + ->setAttributes($attributes) + ->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + + $this->assertSame('bar', $attributes->get('foo')); + $this->assertSame(2, $attributes->get('id')); + $this->assertSame('val', $attributes->get('key')); + $this->assertSame('val2', $attributes->get('key2')); + $this->assertSame('val1', $attributes->get('key1')); + } + + public function test_set_attributes_overrides_values(): void + { + $attributes = ['id' => 1, 'foo' => 'bar']; + + /** @var Span $span */ + $span = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setAttribute('id', 0) + ->setAttribute('foo', 'baz') + ->setAttributes($attributes) + ->startSpan(); + + $attributes = $span->toSpanData()->getAttributes(); + + $this->assertSame('bar', $attributes->get('foo')); + $this->assertSame(1, $attributes->get('id')); + } + + public function test_is_recording_default(): void + { + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + $this->assertTrue($span->isRecording()); + $span->end(); + } + + public function test_is_recording_sampler(): void + { + /** @var Span $span */ + $span = (new TracerProvider([], new AlwaysOffSampler())) + ->getTracer('test') + ->spanBuilder(self::SPAN_NAME) + ->startSpan(); + + $this->assertFalse($span->isRecording()); + $span->end(); + } + + public function test_get_kind_default(): void + { + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + $this->assertSame(API\SpanKind::KIND_INTERNAL, $span->getKind()); + $span->end(); + } + + public function test_get_kind(): void + { + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setSpanKind(API\SpanKind::KIND_CONSUMER)->startSpan(); + $this->assertSame(API\SpanKind::KIND_CONSUMER, $span->getKind()); + $span->end(); + } + + public function test_start_timestamp(): void + { + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setStartTimestamp(123)->startSpan(); + $span->end(); + $this->assertSame(123, $span->toSpanData()->getStartEpochNanos()); + } + + public function test_set_no_parent(): void + { + $parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + $parentScope = $parentSpan->activate(); + + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setParent(false)->startSpan(); + + $this->assertNotSame( + $span->getContext()->getTraceId(), + $parentSpan->getContext()->getTraceId() + ); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($span, Context::getRoot()) + ->once(); + + /** @var Span $spanNoParent */ + $spanNoParent = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setParent(false) + ->setParent(Context::getCurrent()) + ->setParent(false) + ->startSpan(); + + $this->assertNotSame($span->getContext()->getTraceId(), $spanNoParent->getContext()->getTraceId()); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($spanNoParent, Context::getRoot()) + ->once(); + + $spanNoParent->end(); + $span->end(); + $parentScope->detach(); + $parentSpan->end(); + } + + public function test_set_no_parent_override(): void + { + $parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + $parentContext = Context::getCurrent()->withContextValue($parentSpan); + + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setParent(false)->setParent($parentContext)->startSpan(); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($span, $parentContext) + ->once(); + + $this->assertSame( + $span->getContext()->getTraceId(), + $parentSpan->getContext()->getTraceId() + ); + $this->assertSame( + $span->toSpanData()->getParentSpanId(), + $parentSpan->getContext()->getSpanId() + ); + + $parentContext2 = Context::getCurrent()->withContextValue($parentSpan); + + /** @var Span $span2 */ + $span2 = $this + ->tracer + ->spanBuilder(self::SPAN_NAME) + ->setParent(false) + ->setParent($parentContext2) + ->startSpan(); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($span2, $parentContext2) + ->once(); + + $this->assertSame( + $span2->getContext()->getTraceId(), + $parentSpan->getContext()->getTraceId() + ); + + $span2->end(); + $span->end(); + $parentSpan->end(); + } + + public function test_set_parent_empty_context(): void + { + $emptyContext = Context::getCurrent(); + $parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + $parentScope = $parentSpan->activate(); + + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->setParent($emptyContext)->startSpan(); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($span, $emptyContext) + ->once(); + + $this->assertNotSame( + $span->getContext()->getTraceId(), + $parentSpan->getContext()->getTraceId() + ); + $this->assertNotSame( + $span->toSpanData()->getParentSpanId(), + $parentSpan->getContext()->getSpanId() + ); + + $span->end(); + $parentScope->detach(); + $parentSpan->end(); + } + + public function test_set_parent_current_span(): void + { + $parentSpan = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + $parentScope = $parentSpan->activate(); + $implicitContext = Context::getCurrent(); + + /** @var Span $span */ + $span = $this->tracer->spanBuilder(self::SPAN_NAME)->startSpan(); + + $this + ->spanProcessor + ->shouldHaveReceived('onStart') + ->with($span, $implicitContext) + ->once(); + + $this->assertSame( + $span->getContext()->getTraceId(), + $parentSpan->getContext()->getTraceId() + ); + $this->assertSame( + $span->toSpanData()->getParentSpanId(), + $parentSpan->getContext()->getSpanId() + ); + + $span->end(); + $parentScope->detach(); + $parentSpan->end(); + } +} diff --git a/tests/OpenTelemetry/Integration/SDK/SpanContextTest.php b/tests/OpenTelemetry/Integration/SDK/SpanContextTest.php new file mode 100644 index 00000000000..6edf0f284e5 --- /dev/null +++ b/tests/OpenTelemetry/Integration/SDK/SpanContextTest.php @@ -0,0 +1,91 @@ +assertSame(SpanContextValidator::INVALID_TRACE, $spanContext->getTraceId()); + $this->assertSame(SpanContextValidator::INVALID_SPAN, $spanContext->getSpanId()); + } + + public function invalidSpanData(): array + { + return [ + // Too long TraceID + ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa36', 'bbbbbbbbbbbbbb16', '/^TraceID/'], + // Too long SpanID + ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa32', 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb32', '/^SpanID/'], + // Bad characters in TraceID + ['bad trace', 'bbbbbbbbbbbbbb16', '/^TraceID/'], + // Bad characters in SpanID + ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa32', 'bad span', '/^SpanID/'], + ]; + } + + /** + * @group trace-compliance + */ + public function test_valid_span(): void + { + $spanContext = SpanContext::create('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbb', API\TraceFlags::SAMPLED); + $this->assertTrue($spanContext->isValid()); + } + + /** + * @group trace-compliance + */ + public function test_context_is_remote_from_restore(): void + { + $spanContext = SpanContext::createFromRemoteParent('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbb', API\TraceFlags::SAMPLED); + $this->assertTrue($spanContext->isRemote()); + } + + public function test_context_is_not_remote_from_constructor(): void + { + $spanContext = SpanContext::create('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbb', API\TraceFlags::SAMPLED); + $this->assertFalse($spanContext->isRemote()); + } + + public function test_sampled_span(): void + { + $spanContext = SpanContext::create('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbb', API\TraceFlags::SAMPLED); + $this->assertTrue($spanContext->isSampled()); + } + + public function test_getters_work(): void + { + $trace = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; + $span = 'bbbbbbbbbbbbbbbb'; + $tracestate = new TraceState('a=b'); + $spanContext = SpanContext::create($trace, $span, API\TraceFlags::DEFAULT, $tracestate); + $this->assertSame($trace, $spanContext->getTraceId()); + $this->assertSame($span, $spanContext->getSpanId()); + $this->assertSame($tracestate, $spanContext->getTraceState()); + $this->assertFalse($spanContext->isSampled()); + } + + public function test_random_generated_ids_create_valid_context(): void + { + $idGenerator = new RandomIdGenerator(); + $context = SpanContext::create($idGenerator->generateTraceId(), $idGenerator->generateSpanId(), 0); + $this->assertTrue($context->isValid()); + } +} diff --git a/tests/OpenTelemetry/Integration/SDK/SpanProcessorTest.php b/tests/OpenTelemetry/Integration/SDK/SpanProcessorTest.php new file mode 100644 index 00000000000..a4ffb9becc1 --- /dev/null +++ b/tests/OpenTelemetry/Integration/SDK/SpanProcessorTest.php @@ -0,0 +1,152 @@ +createMock(SpanProcessorInterface::class); + $spanProcessor + ->expects($this->once()) + ->method('onStart') + ->with($this->isInstanceOf(SpanInterface::class), $this->equalTo($parentContext)) + ; + + $tracerProvider = new TracerProvider($spanProcessor); + $tracer = $tracerProvider->getTracer('OpenTelemetry.Test'); + $tracer->spanBuilder('test.span')->setParent($parentContext)->startSpan(); + } + + public function test_current_context_should_be_passed_to_span_processor_by_default(): void + { + $currentContext = Context::getCurrent(); + + $spanProcessor = $this->createMock(SpanProcessorInterface::class); + $spanProcessor + ->expects($this->once()) + ->method('onStart') + ->with($this->isInstanceOf(SpanInterface::class), $this->equalTo($currentContext)) + ; + + $tracerProvider = new TracerProvider($spanProcessor); + $tracer = $tracerProvider->getTracer('OpenTelemetry.Test'); + $tracer->spanBuilder('test.span')->startSpan(); + } + + public function test_overwrite_span_resource_in_span_processor(): void + { + $overwriteSpanProcessor = new class implements SpanProcessorInterface { + public function onStart(SpanInterface $span, ContextInterface $parentContext): void + { + $span->updateName('overwritten'); + } + + public function onEnd(ReadableSpanInterface $span): void + { + } + + public function shutdown(?CancellationInterface $cancellation = null): bool + { + } + + /** + * @inheritDoc + */ + public function forceFlush(?CancellationInterface $cancellation = null): bool + { + } + }; + + $traces = $this->isolateTracer(function () use ($overwriteSpanProcessor) { + $tracer = (new TracerProvider($overwriteSpanProcessor))->getTracer('OpenTelemetry.Test'); + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->end(); + }); + + $span = $traces[0][0]; + $this->assertSame('overwritten', $span['resource']); + } + + public function test_span_exporter_to_array(): void + { + $spanExporter = new class implements SpanExporterInterface + { + use SpanExporterTrait; + + private array $spans = []; + + public function __construct(?array $spans = null) + { + $this->spans = $spans ?? []; + } + + protected function doExport(iterable $spans): bool + { + foreach ($spans as $span) { + $this->spans[] = $span; + } + + return true; + } + + public function getSpans(): array + { + return $this->spans; + } + + public function getStorage(): array + { + return $this->spans; + } + }; + + $tracerProvider = new TracerProvider(new SimpleSpanProcessor($spanExporter)); + $tracer = $tracerProvider->getTracer('OpenTelemetry.Test'); + + $span = $tracer->spanBuilder('test.span')->startSpan(); + $span->end(); + + $this->assertCount(1, $spanExporter->getSpans()); + $this->assertSame($span->getContext(), $spanExporter->getSpans()[0]->getContext()); + } +} diff --git a/tests/OpenTelemetry/Integration/SDK/TracerTest.php b/tests/OpenTelemetry/Integration/SDK/TracerTest.php new file mode 100644 index 00000000000..50360dbda89 --- /dev/null +++ b/tests/OpenTelemetry/Integration/SDK/TracerTest.php @@ -0,0 +1,172 @@ +withContextValue( + new NonRecordingSpan( + SpanContext::create( + '4bf92f3577b34da6a3ce929d0e0e4736', + '00f067aa0ba902b7', + API\TraceFlags::SAMPLED + ) + ) + ); + + $newTraceState = new TraceState('new-key=new_value'); + + $sampler = $this->createMock(SamplerInterface::class); + $sampler->method('shouldSample') + ->willReturn(new SamplingResult( + SamplingResult::RECORD_AND_SAMPLE, + [], + $newTraceState + )); + + $this->isolateTracer(function () use ($sampler, $parentContext, $parentTraceState) { + $tracerProvider = new TracerProvider([], $sampler); + $tracer = $tracerProvider->getTracer('OpenTelemetry.TracerTest'); + $span = $tracer->spanBuilder('test.span')->setParent($parentContext)->startSpan(); + + $this->assertNotEquals($parentTraceState, $span->getContext()->getTraceState()); + $this->assertEquals('dd=t.tid:4bf92f3577b34da6;t.dm:-0,new-key=new_value', (string)$span->getContext()->getTraceState()); + }); + } + + /** + * @group trace-compliance + * @noinspection PhpParamsInspection + */ + public function test_span_should_receive_instrumentation_scope(): void + { + $this->isolateTracer(function () { + $tracerProvider = new TracerProvider(); + $tracer = $tracerProvider->getTracer('OpenTelemetry.TracerTest', 'dev', 'http://url', ['foo' => 'bar']); + /** @var Span $span */ + $span = $tracer->spanBuilder('test.span')->startSpan(); + $spanInstrumentationScope = $span->getInstrumentationScope(); + + $this->assertSame('OpenTelemetry.TracerTest', $spanInstrumentationScope->getName()); + $this->assertSame('dev', $spanInstrumentationScope->getVersion()); + $this->assertSame('http://url', $spanInstrumentationScope->getSchemaUrl()); + $this->assertSame(['foo' => 'bar'], $spanInstrumentationScope->getAttributes()->toArray()); + }); + } + + public function test_returns_noop_span_builder_after_shutdown(): void + { + $this->isolateTracer(function () { + $tracerProvider = new TracerProvider(); + $tracer = $tracerProvider->getTracer('foo'); + $this->assertInstanceOf(SpanBuilder::class, $tracer->spanBuilder('bar')); + $tracerProvider->shutdown(); + $this->assertInstanceOf(API\NoopSpanBuilder::class, $tracer->spanBuilder('baz')); + }); + } + + public function test_factory_returns_noop_tracer_when_sdk_disabled(): void + { + self::setEnvironmentVariable(Variables::OTEL_SDK_DISABLED, 'true'); + $tracerProvider = (new TracerProviderFactory())->create(); + $tracer = $tracerProvider->getTracer('foo'); + $this->assertInstanceOf(API\NoopTracer::class, $tracer); + } + + public function test_general_identity_attributes_are_dropped_by_default(): void + { + $this->markTestSkipped("Span Attributes Limits aren't yet supported"); + $this->isolateTracer(function () { + $exporter = new InMemoryExporter(); + $tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter)); + $tracer = $tracerProvider->getTracer('test'); + $tracer->spanBuilder('test') + ->setAttribute(TraceAttributes::ENDUSER_ID, 'username') + ->setAttribute(TraceAttributes::ENDUSER_ROLE, 'admin') + ->setAttribute(TraceAttributes::ENDUSER_SCOPE, 'read:message, write:files') + ->startSpan() + ->end(); + + $tracerProvider->shutdown(); + + $attributes = $exporter->getSpans()[0]->getAttributes(); + $this->assertCount(1, $attributes); + $this->assertSame('internal', $attributes->getIterator()->current()); + $this->assertSame(3, $attributes->getDroppedAttributesCount()); + }); + } + + public function test_general_identity_attributes_are_retained_if_enabled(): void + { + $this->markTestSkipped("Span Attributes Limits aren't yet supported"); + $this->isolateTracer(function () { + $exporter = new InMemoryExporter(); + $spanLimits = (new SpanLimitsBuilder()) + ->retainGeneralIdentityAttributes() + ->build(); + $tracerProvider = new TracerProvider(new SimpleSpanProcessor($exporter), null, null, $spanLimits); + $tracer = $tracerProvider->getTracer('test'); + $tracer->spanBuilder('test') + ->setAttribute(TraceAttributes::ENDUSER_ID, 'username') + ->setAttribute(TraceAttributes::ENDUSER_ROLE, 'admin') + ->setAttribute(TraceAttributes::ENDUSER_SCOPE, 'read:message, write:files') + ->startSpan() + ->end(); + + $tracerProvider->shutdown(); + + $attributes = $exporter->getSpans()[0]->getAttributes(); + $this->assertCount(4, $attributes); + $this->assertSame(0, $attributes->getDroppedAttributesCount()); + }); + } +} diff --git a/tests/OpenTelemetry/Unit/API/Baggage/BaggageTest.php b/tests/OpenTelemetry/Unit/API/Baggage/BaggageTest.php new file mode 100644 index 00000000000..a22fa0f71ff --- /dev/null +++ b/tests/OpenTelemetry/Unit/API/Baggage/BaggageTest.php @@ -0,0 +1,130 @@ +activate(); + $this->assertSame(Baggage::getCurrent(), Baggage::getEmpty()); + $scope->detach(); + } + + public function test_current(): void + { + $scope = Context::getRoot()->withContextValue( + Baggage::getBuilder()->set('foo', 'bar')->build(), + )->activate(); + $this->assertSame('bar', Baggage::getCurrent()->getValue('foo')); + $scope->detach(); + } + + public function test_get_current_baggage_default(): void + { + $scope = Context::getRoot()->activate(); + $baggage = Baggage::getCurrent(); + $this->assertSame($baggage, Baggage::getEmpty()); + $scope->detach(); + } + + public function test_get_current_baggage_sets_correct_context(): void + { + $baggage = Baggage::getEmpty(); + $scope = Context::getRoot()->withContextValue($baggage)->activate(); + $this->assertSame(Baggage::getCurrent(), $baggage); + $scope->detach(); + } + + public function test_baggage_from_context_default_context(): void + { + $baggage = Baggage::fromContext(Context::getRoot()); + $this->assertSame($baggage, Baggage::getEmpty()); + } + + public function test_get_baggage_explicit_context(): void + { + $baggage = Baggage::getEmpty(); + $context = Context::getRoot()->withContextValue($baggage); + $this->assertSame(Baggage::fromContext($context), $baggage); + } + + // endregion + + // region functionality + + public function test_get_value_present(): void + { + $this->assertSame(10, Baggage::getBuilder()->set('foo', 10)->build()->getValue('foo')); + } + + public function test_get_value_missing(): void + { + $this->assertNull(Baggage::getBuilder()->build()->getValue('foo')); + } + + public function test_get_entry_present(): void + { + /** @var Entry $entry */ + $entry = Baggage::getBuilder()->set('foo', 10, new Metadata('meta'))->build()->getEntry('foo'); + $this->assertSame(10, $entry->getValue()); + $this->assertSame('meta', $entry->getMetadata()->getValue()); + } + + public function test_get_entry_present_no_metadata(): void + { + /** @var Entry $entry */ + $entry = Baggage::getBuilder()->set('foo', 10)->build()->getEntry('foo'); + $this->assertSame(10, $entry->getValue()); + $this->assertEmpty($entry->getMetadata()->getValue()); + } + + public function test_get_entry_missing(): void + { + $this->assertNull(Baggage::getBuilder()->build()->getEntry('foo')); + } + + public function test_to_builder(): void + { + $baggage = Baggage::getBuilder()->set('foo', 10)->build(); + $baggage2 = $baggage->toBuilder()->build(); + + $this->assertSame(10, $baggage2->getValue('foo')); + } + + public function test_get_all(): void + { + $baggage = Baggage::getBuilder() + ->set('foo', 'bar') + ->set('bar', 'baz') + ->set('biz', 'fiz') + ->remove('biz') + ->build(); + + $arr = []; + + foreach ($baggage->getAll() as $key => $value) { + $arr[$key] = $value->getValue(); + } + + $this->assertEquals( + ['foo' => 'bar', 'bar' => 'baz'], + $arr + ); + } + + // endregion +} diff --git a/tests/OpenTelemetry/Unit/API/Baggage/Propagation/BaggagePropagatorTest.php b/tests/OpenTelemetry/Unit/API/Baggage/Propagation/BaggagePropagatorTest.php new file mode 100644 index 00000000000..637c0ebf628 --- /dev/null +++ b/tests/OpenTelemetry/Unit/API/Baggage/Propagation/BaggagePropagatorTest.php @@ -0,0 +1,183 @@ +assertSame( + ['baggage'], + BaggagePropagator::getInstance()->fields() + ); + } + + public function test_inject_empty_baggage(): void + { + $carrier = []; + + BaggagePropagator::getInstance()->inject($carrier); + + $this->assertEmpty($carrier); + } + + public function test_inject(): void + { + $carrier = []; + + BaggagePropagator::getInstance()->inject( + $carrier, + null, + Context::getRoot()->withContextValue( + Baggage::getBuilder() + ->set('nometa', 'nometa-value') + ->set('meta', 'meta-value', new Metadata('somemetadata; someother=foo')) + ->build() + ) + ); + + $this->assertSame( + ['baggage' => 'nometa=nometa-value,meta=meta-value;somemetadata; someother=foo'], + $carrier + ); + } + + public function test_inject_encodes_value(): void + { + $carrier = []; + + BaggagePropagator::getInstance()->inject( + $carrier, + null, + Context::getRoot()->withContextValue( + Baggage::getBuilder() + ->set('key1', 'val1') + ->set('key2', 'val2:val3') + ->set('key3', 'val4@#$val5') + ->set('key4', 'key=value', new Metadata('foo=bar=5')) + ->build() + ) + ); + + $this->assertSame( + ['baggage' => 'key1=val1,key2=val2%3Aval3,key3=val4%40%23%24val5,key4=key%3Dvalue;foo=bar=5'], + $carrier + ); + } + + public function test_extract_missing_header(): void + { + $this->assertEquals( + Context::getRoot(), + BaggagePropagator::getInstance()->extract([]) + ); + } + + /** @dataProvider headerProvider */ + public function test_extract(string $header, Baggage $expectedBaggage): void + { + $propagator = BaggagePropagator::getInstance(); + + $context = $propagator->extract(['baggage' => $header]); + + $this->assertEquals( + $expectedBaggage, + Baggage::fromContext($context) + ); + } + + public function test_parse_into_with_properties(): void + { + $builder = new BaggageBuilder(); + //@see https://www.w3.org/TR/baggage/#example + $header = 'key1=value1;property1;property2, key2 = value2, key3=value3; propertyKey=propertyValue'; + $parser = new Parser($header); + + $expected = [ + 'key1' => ['value1', 'property1;property2'], + 'key2' => ['value2', ''], + 'key3' => ['value3', 'propertyKey=propertyValue'], + ]; + $parser->parseInto($builder); + $baggage = $builder->build(); + foreach ($baggage->getAll() as $key => $entry) { + $this->assertSame($expected[$key][0], $entry->getValue()); + $this->assertSame($expected[$key][1], $entry->getMetadata()->getValue()); + } + } + + public static function headerProvider(): array + { + return [ + 'key - duplicate key' => ['key=value1,key=value2', Baggage::getBuilder()->set('key', 'value2')->build()], + 'key - leading spaces' => [' key=value1', Baggage::getBuilder()->set('key', 'value1')->build()], + 'key - trailing spaces' => ['key =value1', Baggage::getBuilder()->set('key', 'value1')->build()], + 'key - only spaces' => [' =value1', Baggage::getEmpty()], + 'key - inner spaces' => ['k ey=value1', Baggage::getEmpty()], + 'key - invalid character' => ['ke?y=value1', Baggage::getEmpty()], + 'key - invalid =' => ['ke%3Dy=value1', Baggage::getEmpty()], + 'key - multiple invalid' => ['keset('key', 'value', new Metadata('meta1=value1;meta2=value2'))->build()], + + 'value - leading spaces' => ['key= value1', Baggage::getBuilder()->set('key', 'value1')->build()], + 'value - trailing spaces' => ['key=value1 ', Baggage::getBuilder()->set('key', 'value1')->build()], + 'value - trailing spaces with metadata' => ['key=value1 ;meta1=meta2', Baggage::getBuilder()->set('key', 'value1', new Metadata('meta1=meta2'))->build()], + 'value - empty' => ['key=', Baggage::getEmpty()], + 'value - empty with metadata' => ['key1=;metakey=metaval', Baggage::getEmpty()], + 'value - only spaces' => ['key1= ', Baggage::getEmpty()], + 'value - inner spaces' => ['key1=va lue', Baggage::getEmpty()], + 'value - invalid character' => ['key1=va\\lue', Baggage::getEmpty()], + 'value - urlencoded' => ['key1=val1,key2=val2%3Aval3,key3=val4%40%23%24val5', Baggage::getBuilder()->set('key1', 'val1')->set('key2', 'val2:val3')->set('key3', 'val4@#$val5')->build()], + + 'multiple values - leadingSpaces' => ['key= value1,key1=val', Baggage::getBuilder()->set('key', 'value1')->set('key1', 'val')->build()], + 'multiple values - trailingSpaces' => ['key=value1 ,key1=val', Baggage::getBuilder()->set('key', 'value1')->set('key1', 'val')->build()], + 'multiple values - empty with metadata' => ['key1=;metakey=metaval,key1=val', Baggage::getBuilder()->set('key1', 'val')->build()], + 'multiple values - only spaces' => ['key1= ,key1=val', Baggage::getBuilder()->set('key1', 'val')->build()], + 'multiple values - inner spaces' => ['key=valu e1,key1=val', Baggage::getBuilder()->set('key1', 'val')->build()], + 'multiple values - invalid characters' => ['key=val\\ue1,key1=val', Baggage::getBuilder()->set('key1', 'val')->build()], + + 'equal sign in metadata' => ['key1=val1;prop=1;prop2;prop3=2', Baggage::getBuilder()->set('key1', 'val1', new Metadata('prop=1;prop2;prop3=2'))->build()], + 'empty header' => ['', Baggage::getEmpty()], + 'blank header' => [' ', Baggage::getEmpty()], + 'valid single value' => ['key=value', Baggage::getBuilder()->set('key', 'value')->build()], + 'valid single value with metadata' => ['key=value;metadata-key=value;othermetadata', Baggage::getBuilder()->set('key', 'value', new Metadata('metadata-key=value;othermetadata'))->build()], + 'valid multiple values' => ['key1=value1,key2=value2', Baggage::getBuilder()->set('key1', 'value1')->set('key2', 'value2')->build()], + 'valid complex value' => [ + "key1= value1; metadata-key = value; othermetadata, key2 =value2 , key3 =\tvalue3 ; ", + Baggage::getBuilder() + ->set('key1', 'value1', new Metadata('metadata-key = value; othermetadata')) + ->set('key2', 'value2') + ->set('key3', 'value3') + ->build(), + ], + 'valid complex value with some invalid' => [ + "key1= v;alsdf;-asdflkjasdf===asdlfkjadsf ,,a sdf9asdf-alue1; metadata-key = value; othermetadata, key2 =value2, key3 =\tvalue3 ; ", + Baggage::getBuilder() + ->set('key1', 'v', new Metadata('alsdf;-asdflkjasdf===asdlfkjadsf')) + ->set('key2', 'value2') + ->set('key3', 'value3') + ->build(), + ], + ]; + } +} diff --git a/tests/OpenTelemetry/Unit/API/Trace/NonRecordingSpanTest.php b/tests/OpenTelemetry/Unit/API/Trace/NonRecordingSpanTest.php new file mode 100644 index 00000000000..7c1a7344eac --- /dev/null +++ b/tests/OpenTelemetry/Unit/API/Trace/NonRecordingSpanTest.php @@ -0,0 +1,106 @@ +createNonRecordingSpan(); + + $this->assertSame( + $this->context, + $span->getContext() + ); + } + + public function test_is_recording(): void + { + $this->assertFalse( + $this->createNonRecordingSpan()->isRecording() + ); + } + + public function test_set_attribute(): void + { + $this->assertInstanceOf( + NonRecordingSpan::class, + $this->createNonRecordingSpan()->setAttribute('foo', 'bar') + ); + } + + public function test_set_attributes(): void + { + $this->assertInstanceOf( + NonRecordingSpan::class, + $this->createNonRecordingSpan()->setAttributes( + $this->createMock(AttributesInterface::class) + ) + ); + } + + public function test_add_event(): void + { + $this->assertInstanceOf( + NonRecordingSpan::class, + $this->createNonRecordingSpan()->addEvent('foo') + ); + } + + public function test_record_exception(): void + { + $this->assertInstanceOf( + NonRecordingSpan::class, + $this->createNonRecordingSpan()->recordException( + new Exception() + ) + ); + } + + public function test_update_name(): void + { + $this->assertInstanceOf( + NonRecordingSpan::class, + $this->createNonRecordingSpan()->updateName('foo') + ); + } + + public function test_set_status(): void + { + $this->assertInstanceOf( + NonRecordingSpan::class, + $this->createNonRecordingSpan()->setStatus('Ok') + ); + } + + public function test_end(): void + { + // @phpstan-ignore-next-line + $this->assertNull($this->createNonRecordingSpan()->end()); + } + + private function createNonRecordingSpan(): NonRecordingSpan + { + return new NonRecordingSpan( + $this->getSpanContextInterfaceMock() + ); + } + + private function getSpanContextInterfaceMock(): SpanContextInterface + { + return $this->context ?? $this->context = $this->createMock(SpanContextInterface::class); + } +} diff --git a/tests/OpenTelemetry/Unit/API/Trace/SpanContextTest.php b/tests/OpenTelemetry/Unit/API/Trace/SpanContextTest.php new file mode 100644 index 00000000000..8c9d6975712 --- /dev/null +++ b/tests/OpenTelemetry/Unit/API/Trace/SpanContextTest.php @@ -0,0 +1,100 @@ +first = SpanContext::create(self::FIRST_TRACE_ID, self::FIRST_SPAN_ID, API\TraceFlags::DEFAULT, new TraceState('foo=bar')); + $this->second = SpanContext::create(self::SECOND_TRACE_ID, self::SECOND_SPAN_ID, API\TraceFlags::SAMPLED, new TraceState('foo=baz')); + $this->remote = SpanContext::createFromRemoteParent(self::SECOND_TRACE_ID, self::SECOND_SPAN_ID, API\TraceFlags::SAMPLED, new TraceState()); + } + + // region API + + public function test_is_valid(): void + { + $this->assertFalse(SpanContext::getInvalid()->isValid()); + + $this->assertFalse( + SpanContext::createFromRemoteParent( + self::FIRST_TRACE_ID, + SpanContextValidator::INVALID_SPAN + )->isValid() + ); + + $this->assertFalse( + SpanContext::createFromRemoteParent( + SpanContextValidator::INVALID_TRACE, + self::SECOND_SPAN_ID + )->isValid() + ); + + $this->assertTrue($this->first->isValid()); + $this->assertTrue($this->second->isValid()); + } + + public function test_get_trace_id(): void + { + $this->assertSame(self::FIRST_TRACE_ID, $this->first->getTraceId()); + $this->assertSame(self::SECOND_TRACE_ID, $this->second->getTraceId()); + } + + public function test_get_trace_and_span_id_binary(): void + { + $traceId = '7d9df3359179cfd468fdf360f3e29f1a'; + $spanId = '7d9df3359179cfd4'; + + $spanContext = SpanContext::create($traceId, $spanId); + $this->assertSame(hex2bin($traceId), $spanContext->getTraceIdBinary()); + $this->assertSame(hex2bin($spanId), $spanContext->getSpanIdBinary()); + } + + public function test_get_span_id(): void + { + $this->assertSame(self::FIRST_SPAN_ID, $this->first->getSpanId()); + $this->assertSame(self::SECOND_SPAN_ID, $this->second->getSpanId()); + } + + public function test_get_trace_flags(): void + { + $this->assertSame(API\TraceFlags::DEFAULT, $this->first->getTraceFlags()); + $this->assertSame(API\TraceFlags::SAMPLED, $this->second->getTraceFlags()); + } + + public function test_get_trace_state(): void + { + $this->assertEquals(new TraceState('foo=bar'), $this->first->getTraceState()); + $this->assertEquals(new TraceState('foo=baz'), $this->second->getTraceState()); + } + + public function test_is_remote(): void + { + $this->assertFalse($this->first->isRemote()); + $this->assertFalse($this->second->isRemote()); + $this->assertTrue($this->remote->isRemote()); + } + + // endregion API +} diff --git a/tests/OpenTelemetry/Unit/API/Trace/TraceTest.php b/tests/OpenTelemetry/Unit/API/Trace/TraceTest.php new file mode 100644 index 00000000000..76eb27eb6b5 --- /dev/null +++ b/tests/OpenTelemetry/Unit/API/Trace/TraceTest.php @@ -0,0 +1,123 @@ + $this->assertSame($span, Span::getCurrent())); + } + + public function test_restores_previous_span(): void + { + $span = new NonRecordingSpan(SpanContext::getInvalid()); + $scope = $span->activate(); + + try { + trace(Span::getInvalid(), fn () => null); + + $this->assertSame($span, Span::getCurrent()); + } finally { + $scope->detach(); + } + } + + public function test_returns_closure_result(): void + { + $this->assertSame(42, trace(Span::getInvalid(), fn () => 42)); + } + + public function test_provides_args_to_closure(): void + { + trace(Span::getInvalid(), fn ($a) => $this->assertSame(42, $a), [42]); + } + + public function test_ends_span(): void + { + $span = $this->createMock(SpanInterface::class); + $span->expects($this->once())->method('end'); + + trace($span, fn () => null); + } + + public function test_rethrows_exception(): void + { + $this->expectException(RuntimeException::class); + + trace(Span::getInvalid(), function (): void { + throw new RuntimeException(); + }); + } + + public function test_records_exception(): void + { + $span = $this->createMock(SpanInterface::class); + $span->expects($this->once())->method('setStatus')->with(StatusCode::STATUS_ERROR); + $span->expects($this->once())->method('recordException'); + + try { + trace($span, function (): void { + throw new RuntimeException(); + }); + } catch (RuntimeException $e) { + } + } + + public function test_ends_span_on_exception(): void + { + $span = $this->createMock(SpanInterface::class); + $span->expects($this->once())->method('end'); + + try { + trace($span, function (): void { + throw new RuntimeException(); + }); + } catch (RuntimeException $e) { + } + } + + public function test_exception_does_not_leak_closure_reference(): void + { + $c = static function (): void { + throw new RuntimeException(); + }; + $r = WeakReference::create($c); + + try { + trace(Span::getInvalid(), $c); + } catch (Exception $e) { + $c = null; + $this->assertNull($r->get()); + } + } + + public function test_does_not_keep_argument_references(): void + { + trace(Span::getInvalid(), function (object $o): void { + $r = WeakReference::create($o); + $o = null; + + $this->assertNull($r->get()); + }, [new stdClass()]); + } +} diff --git a/tests/OpenTelemetry/Unit/Context/ContextStorageTest.php b/tests/OpenTelemetry/Unit/Context/ContextStorageTest.php new file mode 100644 index 00000000000..8d6843b9bbb --- /dev/null +++ b/tests/OpenTelemetry/Unit/Context/ContextStorageTest.php @@ -0,0 +1,100 @@ +assertNull($storage->scope()); + } + + public function test_scope_returns_non_null_after_attach(): void + { + $storage = new ContextStorage(); + $storage->attach($storage->current()); + $this->assertNotNull($storage->scope()); + } + + public function test_scope_returns_null_in_new_fork(): void + { + $storage = new ContextStorage(); + $storage->attach($storage->current()); + $storage->fork(1); + $storage->switch(1); + $this->assertNull($storage->scope()); + } + + public function test_storage_switch_treats_unknown_id_as_main(): void + { + $storage = new ContextStorage(); + + $storage->fork(1); + $storage->attach($storage->current()); + $storage->switch(1); + + $storage->switch(2); + $this->assertNotNull($storage->scope()); + } + + public function test_storage_switch_switches_context(): void + { + $storage = new ContextStorage(); + $main = Context::getRoot(); + $fork = Context::getRoot(); + + $scopeMain = $storage->attach($main); + + // Fiber start + $storage->fork(1); + $storage->switch(1); + $this->assertSame($main, $storage->current()); + + $scopeFork = $storage->attach($fork); + $this->assertSame($fork, $storage->current()); + + // Fiber suspend + $storage->switch(0); + $this->assertSame($main, $storage->current()); + + // Fiber resume + $storage->switch(1); + $this->assertSame($fork, $storage->current()); + + $scopeFork->detach(); + + // Fiber return + $storage->switch(0); + $storage->destroy(1); + + $scopeMain->detach(); + } + + public function test_storage_fork_keeps_forked_root(): void + { + $storage = new ContextStorage(); + $main = Context::getRoot(); + + $scopeMain = $storage->attach($main); + $storage->fork(1); + $scopeMain->detach(); + + $storage->switch(1); + $this->assertSame($main, $storage->current()); + + $storage->switch(0); + $storage->destroy(1); + } +} diff --git a/tests/OpenTelemetry/Unit/Context/ContextTest.php b/tests/OpenTelemetry/Unit/Context/ContextTest.php new file mode 100644 index 00000000000..609b9a74011 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Context/ContextTest.php @@ -0,0 +1,197 @@ +activate(); + + try { + $this->assertSame($context, Context::getCurrent()); + } finally { + $scope->detach(); + } + } + + public function test_ctx_can_store_values_by_key(): void + { + $key1 = Context::createKey('key1'); + $key2 = Context::createKey('key2'); + + $ctx = Context::getRoot()->with($key1, 'val1')->with($key2, 'val2'); + + $this->assertSame($ctx->get($key1), 'val1'); + $this->assertSame($ctx->get($key2), 'val2'); + } + + public function test_set_does_not_mutate_the_original(): void + { + $key1 = Context::createKey('-'); + $key2 = Context::createKey('-'); + + $parent = Context::getRoot()->with($key1, 'foo'); + $child = $parent->with($key2, 'bar'); + + $this->assertSame($child->get($key1), 'foo'); + $this->assertSame($child->get($key2), 'bar'); + $this->assertSame($parent->get($key1), 'foo'); + + $this->assertNull($parent->get($key2)); + } + + public function test_ctx_key_names_are_not_ids(): void + { + $key_name = 'foo'; + + $key1 = Context::createKey($key_name); + $key2 = Context::createKey($key_name); + + $ctx = Context::getRoot()->with($key1, 'val1')->with($key2, 'val2'); + + $this->assertSame($ctx->get($key1), 'val1'); + $this->assertSame($ctx->get($key2), 'val2'); + } + + public function test_empty_ctx_keys_are_valid(): void + { + $key1 = Context::createKey('-'); + $key2 = Context::createKey('-'); + + $ctx = Context::getRoot()->with($key1, 'val1')->with($key2, 'val2'); + + $this->assertSame($ctx->get($key1), 'val1'); + $this->assertSame($ctx->get($key2), 'val2'); + } + + public function test_ctx_can_store_scalar_array_null_and_obj(): void + { + $scalar_val = 42; + $array_val = ['foo', 'bar']; + $null_val = null; + $obj_val = new \stdClass(); + + $scalar_key = Context::createKey('-'); + $array_key = Context::createKey('-'); + $null_key = Context::createKey('-'); + $obj_key = Context::createKey('-'); + + $ctx = Context::getRoot() + ->with($scalar_key, $scalar_val) + ->with($array_key, $array_val) + ->with($null_key, $null_val) + ->with($obj_key, $obj_val); + + $this->assertSame($ctx->get($scalar_key), $scalar_val); + $this->assertSame($ctx->get($array_key), $array_val); + $this->assertSame($ctx->get($null_key), $null_val); + $this->assertSame($ctx->get($obj_key), $obj_val); + } + + public function test_storage_order_doesnt_matter(): void + { + $context = Context::getRoot(); + $arr = []; + foreach (range(0, 9) as $i) { + $r = rand(0, 100); + $key = Context::createKey((string) $r); + $context = $context->with($key, $r); + $arr[$r] = $key; + } + + ksort($arr); + + foreach ($arr as $v => $k) { + $this->assertSame($context->get($k), $v); + } + } + + public function test_reusing_key_overwrites_value(): void + { + $key = Context::createKey('-'); + $ctx = Context::getRoot()->with($key, 'val1'); + $this->assertSame($ctx->get($key), 'val1'); + + $ctx = $ctx->with($key, 'val2'); + $this->assertSame($ctx->get($key), 'val2'); + } + + public function test_ctx_value_not_found_returns_null(): void + { + $ctx = Context::getRoot()->with(Context::createKey('foo'), 'bar'); + $this->assertNull($ctx->get(Context::createKey('baz'))); + } + + public function test_ctx_value_overwrite_null_returns_null(): void + { + $key = Context::createKey('-'); + $ctx = Context::getRoot()->with($key, 'val')->with($key, null); + + $this->assertNull($ctx->get($key)); + } + + public function test_set_get_span_key(): void + { + $key = ContextKeys::span(); + $span = new stdClass(); + + $ctx = Context::getRoot()->with($key, $span); + $this->assertSame($span, $ctx->get($key)); + } + + public function test_with_context_value_calls_store_in_context(): void + { + $ctx = Context::getRoot(); + $value = $this->createMock(ImplicitContextKeyedInterface::class); + $value->expects($this->once())->method('storeInContext')->with($ctx)->willReturn($ctx); + + $ctx->withContextValue($value); + } + + public function test_attach_and_detach_set_current_ctx(): void + { + $key = Context::createKey('-'); + $scope = Context::getRoot()->with($key, '111')->activate(); + + try { + $token = Context::getRoot()->with($key, '222')->activate(); + $this->assertSame(Context::getCurrent()->get($key), '222'); + + $token->detach(); + $this->assertSame(Context::getCurrent()->get($key), '111'); + } finally { + $scope->detach(); + } + } +} diff --git a/tests/OpenTelemetry/Unit/Context/Propagation/MultiTextMapPropagatorTest.php b/tests/OpenTelemetry/Unit/Context/Propagation/MultiTextMapPropagatorTest.php new file mode 100644 index 00000000000..8736a6976e4 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Context/Propagation/MultiTextMapPropagatorTest.php @@ -0,0 +1,124 @@ +propagator1 = Mockery::mock(TextMapPropagatorInterface::class); + $this->propagator2 = Mockery::mock(TextMapPropagatorInterface::class); + $this->propagator3 = Mockery::mock(TextMapPropagatorInterface::class); + + $this->propagator1->shouldReceive('fields')->andReturn([])->byDefault(); + $this->propagator2->shouldReceive('fields')->andReturn([])->byDefault(); + $this->propagator3->shouldReceive('fields')->andReturn([])->byDefault(); + } + + public function test_fields(): void + { + $this->propagator1->allows(['fields' => ['foo', 'bar']]); + $this->propagator2->allows(['fields' => ['hello', 'world']]); + + $this->assertSame( + ['foo', 'bar', 'hello', 'world'], + (new MultiTextMapPropagator([$this->propagator1, $this->propagator2]))->fields() + ); + } + + public function test_fields_duplicates(): void + { + $this->propagator1->allows(['fields' => ['foo', 'bar', 'foo']]); + $this->propagator2->allows(['fields' => ['hello', 'world', 'world', 'bar']]); + + $this->assertSame( + ['foo', 'bar', 'hello', 'world'], + (new MultiTextMapPropagator([$this->propagator1, $this->propagator2]))->fields() + ); + } + + public function test_inject_delegates(): void + { + $carrier = []; + $context = Context::getRoot(); + + $this->propagator1->expects('inject')->with($carrier, null, $context); + $this->propagator2->expects('inject')->with($carrier, null, $context); + $this->propagator3->expects('inject')->with($carrier, null, $context); + + (new MultiTextMapPropagator([ + $this->propagator1, + $this->propagator2, + $this->propagator3, + ]))->inject($carrier, null, $context); + } + + public function test_extract_no_propagators(): void + { + $this->assertSame( + Context::getCurrent(), + (new MultiTextMapPropagator([]))->extract([]) + ); + } + + public function test_extract_found_all(): void + { + $carrier = []; + + $context1 = Context::getRoot(); + $context2 = Context::getRoot(); + $context3 = Context::getRoot(); + $expectedContext = Context::getRoot(); + + $this->propagator1->expects('extract')->with($carrier, null, $context1)->andReturn($context2); + $this->propagator2->expects('extract')->with($carrier, null, $context2)->andReturn($context3); + $this->propagator3->expects('extract')->with($carrier, null, $context3)->andReturn($expectedContext); + + $this->assertSame( + $expectedContext, + (new MultiTextMapPropagator([ + $this->propagator1, + $this->propagator2, + $this->propagator3, + ]))->extract($carrier, null, $context1) + ); + } + + public function test_extract_not_found(): void + { + $carrier = []; + + $context = Context::getRoot(); + + $this->propagator1->expects('extract')->with($carrier, null, $context)->andReturn($context); + $this->propagator2->expects('extract')->with($carrier, null, $context)->andReturn($context); + + $this->assertSame( + $context, + (new MultiTextMapPropagator([ + $this->propagator1, + $this->propagator2, + ]))->extract($carrier, null, $context) + ); + } +} diff --git a/tests/OpenTelemetry/Unit/Context/Propagation/NoopTextMapPropagatorTest.php b/tests/OpenTelemetry/Unit/Context/Propagation/NoopTextMapPropagatorTest.php new file mode 100644 index 00000000000..0cb92428512 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Context/Propagation/NoopTextMapPropagatorTest.php @@ -0,0 +1,35 @@ +assertEmpty(NoopTextMapPropagator::getInstance()->fields()); + } + + public function test_extract_context_is_unchanged(): void + { + $this->assertSame( + Context::getCurrent(), + NoopTextMapPropagator::getInstance()->extract([]) + ); + } + + public function test_inject_injects_nothing(): void + { + $carrier = []; + NoopTextMapPropagator::getInstance()->inject($carrier); + $this->assertEmpty($carrier); + } +} diff --git a/tests/OpenTelemetry/Unit/Context/ScopeTest.php b/tests/OpenTelemetry/Unit/Context/ScopeTest.php new file mode 100644 index 00000000000..08149ada518 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Context/ScopeTest.php @@ -0,0 +1,127 @@ +with($key, 'test'); + $scope = $ctx->activate(); + + $this->assertSame('test', Context::getCurrent()->get($key)); + + $scope->detach(); + + $this->assertNull(Context::getCurrent()->get($key)); + } + + public function test_nested_scope(): void + { + $key = Context::createKey('-'); + $ctx1 = Context::getRoot()->with($key, 'test1'); + $scope1 = $ctx1->activate(); + $this->assertSame('test1', Context::getCurrent()->get($key)); + + $ctx2 = Context::getRoot()->with($key, 'test2'); + $scope2 = $ctx2->activate(); + $this->assertSame('test2', Context::getCurrent()->get($key)); + + $scope2->detach(); + $this->assertSame('test1', Context::getCurrent()->get($key)); + + $scope1->detach(); + $this->assertNull(Context::getCurrent()->get($key)); + } + + public function test_detached_scope_detach(): void + { + $scope1 = Context::getCurrent()->activate(); + + $this->assertSame(0, $scope1->detach()); + /** @phpstan-ignore-next-line */ + $this->assertSame(ScopeInterface::DETACHED, @$scope1->detach() & ScopeInterface::DETACHED); + } + + public function test_order_mismatch_scope_detach(): void + { + $scope1 = Context::getCurrent()->activate(); + $scope2 = Context::getCurrent()->activate(); + + $this->assertSame(ScopeInterface::MISMATCH, @$scope1->detach() & ScopeInterface::MISMATCH); + $this->assertSame(0, $scope2->detach()); + } + + public function test_order_mismatch_scope_detach_depth(): void + { + $contextStorage = new ContextStorage(); + $context = $contextStorage->current(); + + $scope1 = $contextStorage->attach($context); + $scope2 = $contextStorage->attach($context); + $scope3 = $contextStorage->attach($context); + $scope4 = $contextStorage->attach($context); + + $this->assertSame(ScopeInterface::MISMATCH | 2, $scope2->detach()); + $this->assertSame(ScopeInterface::MISMATCH | 1, $scope3->detach()); + $this->assertSame(0, $scope4->detach()); + $this->assertSame(0, $scope1->detach()); + } + + public function test_inactive_scope_detach(): void + { + $scope1 = Context::getCurrent()->activate(); + + Context::storage()->fork(1); + Context::storage()->switch(1); + $this->assertSame(ScopeInterface::INACTIVE, @$scope1->detach() & ScopeInterface::INACTIVE); + + Context::storage()->switch(0); + Context::storage()->destroy(1); + } + + public function test_scope_context_returns_context_of_scope(): void + { + $storage = new ContextStorage(); + + $ctx1 = $storage->current()->with(Context::createKey('-'), 1); + $ctx2 = $storage->current()->with(Context::createKey('-'), 2); + + $scope1 = $storage->attach($ctx1); + $this->assertSame($ctx1, $scope1->context()); + + $scope2 = $storage->attach($ctx2); + $this->assertSame($ctx1, $scope1->context()); + $this->assertSame($ctx2, $scope2->context()); + + $scope2->detach(); + $this->assertSame($ctx2, $scope2->context()); + } + + public function test_scope_local_storage_is_preserved_between_attach_and_scope(): void + { + $storage = new ContextStorage(); + $scope = $storage->attach($storage->current()); + $scope['key'] = 'value'; + $scope = $storage->scope(); + $this->assertNotNull($scope); + $this->assertArrayHasKey('key', $scope); + $this->assertSame('value', $scope['key']); + + unset($scope['key']); + $scope = $storage->scope(); + $this->assertNotNull($scope); + $this->assertArrayNotHasKey('key', $scope); + } +} diff --git a/tests/OpenTelemetry/Unit/Propagation/B3/B3MultiPropagatorTest.php b/tests/OpenTelemetry/Unit/Propagation/B3/B3MultiPropagatorTest.php new file mode 100644 index 00000000000..5d3d0fc7a72 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Propagation/B3/B3MultiPropagatorTest.php @@ -0,0 +1,481 @@ +b3MultiPropagator = B3MultiPropagator::getInstance(); + $b3MultiFields = $this->b3MultiPropagator->fields(); + $this->TRACE_ID = $b3MultiFields[0]; + $this->SPAN_ID = $b3MultiFields[1]; + $this->SAMPLED = $b3MultiFields[3]; + $this->DEBUG_FLAG = $b3MultiFields[4]; + } + + public function test_fields(): void + { + $this->assertSame( + ['X-B3-TraceId', 'X-B3-SpanId', 'X-B3-ParentSpanId', 'X-B3-Sampled', 'X-B3-Flags'], + $this->b3MultiPropagator->fields() + ); + } + + public function test_inject_empty(): void + { + $carrier = []; + $this->b3MultiPropagator->inject($carrier); + $this->assertEmpty($carrier); + } + + public function test_inject_invalid_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create( + SpanContextValidator::INVALID_TRACE, + SpanContextValidator::INVALID_SPAN, + TraceFlags::SAMPLED + ), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_inject_non_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + Context::getCurrent() + ) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + ], + $carrier + ); + } + + public function test_inject_debug_with_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + Context::getCurrent() + )->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_inject_debug_with_non_sampled_context(): void + { + $carrier = []; + $this + ->b3MultiPropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::DEFAULT), + Context::getCurrent() + )->with(B3DebugFlagContextKey::instance(), self::IS_SAMPLED) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_extract_nothing(): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3MultiPropagator->extract([]) + ); + } + + public function test_extract_debug_context(): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertEquals( + self::IS_SAMPLED, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_debug_with_sampled_context(): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertEquals( + self::IS_SAMPLED, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_debug_with_non_sampled_context(): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->DEBUG_FLAG => self::IS_SAMPLED, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertEquals( + self::IS_SAMPLED, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider sampledValueProvider + */ + public function test_extract_sampled_context($sampledValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => $sampledValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) + ); + } + + public static function sampledValueProvider(): array + { + return [ + 'String sampled value' => ['1'], + 'Boolean(lower string) sampled value' => ['true'], + 'Boolean(upper string) sampled value' => ['TRUE'], + 'Boolean(camel string) sampled value' => ['True'], + ]; + } + + /** + * @dataProvider notSampledValueProvider + */ + public function test_extract_non_sampled_context($sampledValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => $sampledValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) + ); + } + + public static function notSampledValueProvider(): array + { + return [ + 'String sampled value' => ['0'], + 'Boolean(lower string) sampled value' => ['false'], + 'Boolean(upper string) sampled value' => ['FALSE'], + 'Boolean(camel string) sampled value' => ['False'], + ]; + } + + /** + * @dataProvider invalidDebugValueProvider + */ + public function test_extract_invalid_debug_with_sampled_context($debugValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + $this->DEBUG_FLAG => $debugValue, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider invalidDebugValueProvider + */ + public function test_extract_invalid_debug_with_non_sampled_context($debugValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->DEBUG_FLAG => $debugValue, + ]; + + $context = $this->b3MultiPropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::DEFAULT), + $this->getSpanContext($context) + ); + } + + public static function invalidDebugValueProvider(): array + { + return [ + 'Invalid debug value - wrong type' => [1], + 'Invalid debug value - wrong character' => ['x'], + 'Invalid debug value - false' => ['false'], + 'Invalid debug value - true' => ['true'], + ]; + } + + /** + * @dataProvider invalidSampledValueProvider + */ + public function test_extract_invalid_sampled_context($sampledValue): void + { + $carrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => $sampledValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3MultiPropagator->extract($carrier)) + ); + } + + public static function invalidSampledValueProvider(): array + { + return [ + 'wrong sampled value' => ['wrong'], + 'null sampled value' => [null], + 'empty sampled value' => [[]], + ]; + } + + public function test_extract_and_inject(): void + { + $extractCarrier = [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ]; + $context = $this->b3MultiPropagator->extract($extractCarrier); + $injectCarrier = []; + $this->b3MultiPropagator->inject($injectCarrier, null, $context); + $this->assertSame($injectCarrier, $extractCarrier); + } + + public function test_extract_empty_trace_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => '', + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_trace_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => 'abcdefghijklmnopabcdefghijklmnop', + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_trace_id_size(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16 . '00', + $this->SPAN_ID => self::SPAN_ID_BASE16, + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_extract_empty_span_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => '', + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_span_id(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => 'abcdefghijklmnop', + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + public function test_invalid_span_id_size(): void + { + $this->assertInvalid( + [ + $this->TRACE_ID => self::TRACE_ID_BASE16, + $this->SPAN_ID => self::SPAN_ID_BASE16 . '00', + $this->SAMPLED => self::IS_SAMPLED, + ] + ); + } + + private function assertInvalid(array $carrier): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3MultiPropagator->extract($carrier), + ); + } + + private function getSpanContext(ContextInterface $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, ContextInterface $context): ContextInterface + { + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/tests/OpenTelemetry/Unit/Propagation/B3/B3PropagatorTest.php b/tests/OpenTelemetry/Unit/Propagation/B3/B3PropagatorTest.php new file mode 100644 index 00000000000..64220741173 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Propagation/B3/B3PropagatorTest.php @@ -0,0 +1,326 @@ +B3] = B3SinglePropagator::getInstance()->fields(); + $b3MultiFields = B3MultiPropagator::getInstance()->fields(); + $this->TRACE_ID = $b3MultiFields[0]; + $this->SPAN_ID = $b3MultiFields[1]; + $this->SAMPLED = $b3MultiFields[3]; + } + + public function test_b3multi_fields(): void + { + $propagator = B3Propagator::getB3MultiHeaderInstance(); + $this->assertSame( + ['X-B3-TraceId', 'X-B3-SpanId', 'X-B3-ParentSpanId', 'X-B3-Sampled', 'X-B3-Flags'], + $propagator->fields() + ); + } + + public function test_b3single_fields(): void + { + $propagator = B3Propagator::getB3SingleHeaderInstance(); + $this->assertSame( + ['b3'], + $propagator->fields() + ); + } + + public function test_b3multi_inject(): void + { + $propagator = B3Propagator::getB3MultiHeaderInstance(); + $carrier = []; + $propagator->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [ + $this->TRACE_ID => self::B3_TRACE_ID, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_SAMPLED, + ], + $carrier + ); + } + + public function test_b3single_inject(): void + { + $propagator = B3Propagator::getB3SingleHeaderInstance(); + $carrier = []; + $propagator->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->B3 => self::B3_SINGLE_HEADER_SAMPLED], + $carrier + ); + } + + public function test_extract_only_b3single_sampled_context_with_b3single_instance(): void + { + $carrier = [ + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_b3single_sampled_context_with_b3multi_instance(): void + { + $carrier = [ + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_b3multi_sampled_context_with_b3single_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_TRACE_ID, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_SAMPLED, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider validTraceIdProvider + */ + public function test_extract_only_b3multi_sampled_context_with_b3multi_instance(string $traceId, string $expected): void + { + $carrier = [ + $this->TRACE_ID => $traceId, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_SAMPLED, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent($expected, self::B3_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider validTraceIdProvider + */ + public function test_extract_b3_single(string $traceId, string $expected): void + { + $carrier = [ + 'b3' => $traceId . '-' . self::B3_SPAN_ID, + ]; + $context = B3Propagator::getB3SingleHeaderInstance()->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent($expected, self::B3_SPAN_ID, TraceFlags::DEFAULT), + $this->getSpanContext($context) + ); + } + + public static function validTraceIdProvider(): array + { + return [ + '16 char trace id' => [ + self::B3_TRACE_ID_16_CHAR, + str_pad(self::B3_TRACE_ID_16_CHAR, 32, '0', STR_PAD_LEFT), + ], + '32 char trace id' => [ + self::B3_TRACE_ID, + self::B3_TRACE_ID, + ], + ]; + } + + public function test_extract_both_sampled_context_with_b3single_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_TRACE_ID, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_both_sampled_context_with_b3multi_instance(): void + { + $carrier = [ + $this->TRACE_ID => self::B3_TRACE_ID, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => self::B3_SINGLE_HEADER_SAMPLED, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider invalidB3SingleHeaderValueProvider + */ + public function test_extract_b3_single_invalid_and_b3_multi_valid_context_with_b3single_instance($headerValue): void + { + $carrier = [ + $this->TRACE_ID => self::B3_TRACE_ID, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => $headerValue, + ]; + + $propagator = B3Propagator::getB3SingleHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::DEFAULT), + $this->getSpanContext($context) + ); + } + + /** + * @dataProvider invalidB3SingleHeaderValueProvider + */ + public function test_extract_b3_single_invalid_and_b3_multi_valid_context_with_b3multi_instance($headerValue): void + { + $carrier = [ + $this->TRACE_ID => self::B3_TRACE_ID, + $this->SPAN_ID => self::B3_SPAN_ID, + $this->SAMPLED => self::IS_NOT_SAMPLED, + $this->B3 => $headerValue, + ]; + + $propagator = B3Propagator::getB3MultiHeaderInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::B3_TRACE_ID, self::B3_SPAN_ID, TraceFlags::DEFAULT), + $this->getSpanContext($context) + ); + } + + public static function invalidB3SingleHeaderValueProvider(): array + { + return [ + 'invalid traceid' => ['abcdefghijklmnopabcdefghijklmnop-' . self::B3_SPAN_ID . '-1'], + 'invalid spanid' => [self::B3_TRACE_ID . '-abcdefghijklmnop-1'], + ]; + } + + private function getSpanContext(ContextInterface $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, ContextInterface $context): ContextInterface + { + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/tests/OpenTelemetry/Unit/Propagation/B3/B3SinglePropagatorTest.php b/tests/OpenTelemetry/Unit/Propagation/B3/B3SinglePropagatorTest.php new file mode 100644 index 00000000000..47ccd7f9f21 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Propagation/B3/B3SinglePropagatorTest.php @@ -0,0 +1,380 @@ +b3SinglePropagator = B3SinglePropagator::getInstance(); + [$this->B3] = $this->b3SinglePropagator->fields(); + } + + public function test_fields(): void + { + $this->assertSame( + ['b3'], + $this->b3SinglePropagator->fields() + ); + } + + public function test_inject_empty(): void + { + + $carrier = []; + $this->b3SinglePropagator->inject($carrier); + $this->assertEmpty($carrier); + } + + public function test_inject_invalid_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create( + SpanContextValidator::INVALID_TRACE, + SpanContextValidator::INVALID_SPAN, + TraceFlags::SAMPLED + ), + Context::getCurrent() + ) + ); + $this->assertEmpty($carrier); + } + + public function test_inject_sampled_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->B3 => self::B3_HEADER_SAMPLED], + $carrier + ); + } + + public function test_inject_non_sampled_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->B3 => self::B3_HEADER_NOT_SAMPLED], + $carrier + ); + } + + public function test_inject_debug_context(): void + { + $carrier = []; + $this + ->b3SinglePropagator + ->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + Context::getCurrent() + )->with(B3DebugFlagContextKey::instance(), self::DEBUG_FLAG) + ); + + $this->assertSame( + [$this->B3 => self::B3_HEADER_DEBUG], + $carrier + ); + } + + public function test_extract_nothing(): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3SinglePropagator->extract([]) + ); + } + + /** + * @dataProvider debugValueProvider + */ + public function test_extract_debug_context($headerValue): void + { + $carrier = [ + $this->B3 => $headerValue, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertEquals( + self::DEBUG_FLAG, + $context->get(B3DebugFlagContextKey::instance()) + ); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public static function debugValueProvider(): array + { + return [ + 'String(lower string) debug value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-' . self::DEBUG_FLAG], + 'String(upper string) debug value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-' . strtoupper(self::DEBUG_FLAG)], + ]; + } + + public function test_extract_sampled_context(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_SAMPLED, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_non_sampled_context(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_NOT_SAMPLED, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($context) + ); + } + + public function test_extract_sampled_context_with_parent_span_id(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_SAMPLED . '-' . self::TRACE_ID_BASE16, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_non_sampled_context_with_parent_span_id(): void + { + $carrier = [ + $this->B3 => self::B3_HEADER_NOT_SAMPLED . '-' . self::TRACE_ID_BASE16, + ]; + + $context = $this->b3SinglePropagator->extract($carrier); + + $this->assertNull($context->get(B3DebugFlagContextKey::instance())); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($context) + ); + } + + public function test_extract_defer_sampling(): void + { + $carrier = [ + $this->B3 => self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16, + ]; + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3SinglePropagator->extract($carrier)) + ); + } + + /** + * @dataProvider invalidSampledValueProvider + */ + public function test_extract_invalid_sampled_context($headerValue): void + { + $carrier = [ + $this->B3 => $headerValue, + ]; + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::TRACE_ID_BASE16, self::SPAN_ID_BASE16), + $this->getSpanContext($this->b3SinglePropagator->extract($carrier)) + ); + } + + public static function invalidSampledValueProvider(): array + { + return [ + 'wrong sampled value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-wrong'], + 'empty sampled value' => [self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '-'], + ]; + } + + public function test_extract_and_inject(): void + { + $extractCarrier = [ + $this->B3 => self::B3_HEADER_SAMPLED, + ]; + $context = $this->b3SinglePropagator->extract($extractCarrier); + $injectCarrier = []; + $this->b3SinglePropagator->inject($injectCarrier, null, $context); + $this->assertSame($injectCarrier, $extractCarrier); + } + + public function test_extract_empty_header(): void + { + $this->assertInvalid([ + $this->B3 => '', + ]); + } + + public function test_extract_header_with_extra_flags(): void + { + $this->assertInvalid([ + $this->B3 => self::B3_HEADER_SAMPLED . '-extra-flags', + ]); + } + + public function test_extract_deny_sampling(): void + { + $this->assertInvalid([ + $this->B3 => self::B3_DENY_SAMPLING, + ]); + } + + public function test_empty_trace_id(): void + { + $this->assertInvalid([ + $this->B3 => '-' . self::SPAN_ID_BASE16 . '-1', + ]); + } + public function test_invalid_trace_id(): void + { + $this->assertInvalid([ + $this->B3 => 'abcdefghijklmnopabcdefghijklmnop-' . self::SPAN_ID_BASE16 . '-1', + ]); + } + + public function test_invalid_trace_id_size(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '00-' . self::SPAN_ID_BASE16 . '-1', + ]); + } + + public function test_empty_span_id(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '--1', + ]); + } + + public function test_invalid_span_id(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '-abcdefghijklmnop-1', + ]); + } + + public function test_invalid_span_id_size(): void + { + $this->assertInvalid([ + $this->B3 => self::TRACE_ID_BASE16 . '-' . self::SPAN_ID_BASE16 . '00-1', + ]); + } + + private function assertInvalid(array $carrier): void + { + $this->assertSame( + Context::getCurrent(), + $this->b3SinglePropagator->extract($carrier), + ); + } + + private function getSpanContext(ContextInterface $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, ContextInterface $context): ContextInterface + { + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/tests/OpenTelemetry/Unit/Propagation/W3C/W3CPropagatorTest.php b/tests/OpenTelemetry/Unit/Propagation/W3C/W3CPropagatorTest.php new file mode 100644 index 00000000000..51fd12894c2 --- /dev/null +++ b/tests/OpenTelemetry/Unit/Propagation/W3C/W3CPropagatorTest.php @@ -0,0 +1,149 @@ +W3C] = TraceContextPropagator::getInstance()->fields(); + } + + public function test_w3c_inject(): void + { + $propagator = TraceContextPropagator::getInstance(); + $carrier = []; + $propagator->inject( + $carrier, + null, + $this->withSpanContext( + SpanContext::create(self::W3C_TRACE_ID, self::W3C_SPAN_ID, TraceFlags::SAMPLED), + Context::getCurrent() + ) + ); + + $this->assertSame( + [$this->W3C => self::W3C_SINGLE_HEADER_SAMPLED], + $carrier + ); + } + + public function test_extract_only_w3c_sampled_context_with_w3c_instance(): void + { + $carrier = [ + $this->W3C => self::W3C_SINGLE_HEADER_SAMPLED + ]; + + $propagator = TraceContextPropagator::getInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::W3C_TRACE_ID, self::W3C_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_w3c_sampled_context_with_w3c_instance_and_tracestate(): void + { + $carrier = [ + $this->W3C => self::W3C_SINGLE_HEADER_SAMPLED, + 'tracestate' => 'foo=bar' + ]; + + $propagator = TraceContextPropagator::getInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::W3C_TRACE_ID, self::W3C_SPAN_ID, TraceFlags::SAMPLED, new TraceState('foo=bar')), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_w3c_sampled_context_with_traceparent(): void + { + $carrier = [ + TraceContextPropagator::TRACEPARENT => self::W3C_SINGLE_HEADER_SAMPLED + ]; + + $propagator = TraceContextPropagator::getInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::createFromRemoteParent(self::W3C_TRACE_ID, self::W3C_SPAN_ID, TraceFlags::SAMPLED), + $this->getSpanContext($context) + ); + } + + public function test_extract_only_w3c_sampled_context_with_invalid_traceparent(): void + { + $carrier = [ + TraceContextPropagator::TRACEPARENT => 'invalid' + ]; + + $propagator = TraceContextPropagator::getInstance(); + + $context = $propagator->extract($carrier); + + $this->assertEquals( + SpanContext::getInvalid(), + $this->getSpanContext($context) + ); + } + + private function getSpanContext(ContextInterface $context): SpanContextInterface + { + return Span::fromContext($context)->getContext(); + } + + private function withSpanContext(SpanContextInterface $spanContext, ContextInterface $context): ContextInterface + { + return $context->withContextValue(Span::wrap($spanContext)); + } +} diff --git a/tests/OpenTelemetry/Unit/SDK/Trace/NonRecordingSpanTest.php b/tests/OpenTelemetry/Unit/SDK/Trace/NonRecordingSpanTest.php new file mode 100644 index 00000000000..b7e1fd27170 --- /dev/null +++ b/tests/OpenTelemetry/Unit/SDK/Trace/NonRecordingSpanTest.php @@ -0,0 +1,26 @@ +assertFalse(API\NonRecordingSpan::getInvalid()->isRecording()); + } + + public function test_has_invalid_context_and_default_span_options(): void + { + $context = API\NonRecordingSpan::getInvalid()->getContext(); + $this->assertSame(API\TraceFlags::DEFAULT, $context->getTraceFlags()); + $this->assertNull($context->getTraceState()); + } +} diff --git a/tests/OpenTelemetry/Unit/SDK/Trace/SpanBuilderTest.php b/tests/OpenTelemetry/Unit/SDK/Trace/SpanBuilderTest.php new file mode 100644 index 00000000000..ea1cd16d031 --- /dev/null +++ b/tests/OpenTelemetry/Unit/SDK/Trace/SpanBuilderTest.php @@ -0,0 +1,88 @@ +createMock(AttributesInterface::class) + ); //final + $attributesFactory = $this->createMock(AttributesFactoryInterface::class); + $spanLimits = new SpanLimits( + $attributesFactory, + $attributesFactory, + $attributesFactory, + 10, + 10, + ); + $this->sampler = $this->createMock(SamplerInterface::class); + $this->idGenerator = $this->createMock(IdGeneratorInterface::class); + $this->spanProcessor = $this->createMock(SpanProcessorInterface::class); + $sharedState = new TracerSharedState( + $this->idGenerator, + $this->createMock(ResourceInfo::class), + $spanLimits, + $this->sampler, + [$this->spanProcessor] + ); + $this->builder = new SpanBuilder('foo', $instrumentationScope, $sharedState); + } + + public function test_start_span(): void + { + $this->sampler->expects($this->once())->method('shouldSample')->willReturn(new SamplingResult(SamplingResult::RECORD_AND_SAMPLE)); + $this->idGenerator->method('generateTraceId')->willReturn(self::TRACE_ID); + $this->idGenerator->method('generateSpanId')->willReturn(self::SPAN_ID); + $linkContext = $this->createMock(SpanContextInterface::class); + $linkContext->method('isValid')->willReturn(true); + + /** @var ReadableSpanInterface $span */ + $span = $this->builder + ->setSpanKind(SpanKind::KIND_CLIENT) + ->setAttributes(['foo' => 'bar']) + ->addLink($linkContext, ['link-attr' => 'link-val']) + ->setStartTimestamp(123456) + ->startSpan(); + + $this->assertSame(self::TRACE_ID, $span->toSpanData()->getTraceId()); + $this->assertSame(self::SPAN_ID, $span->toSpanData()->getSpanId()); + $this->assertSame(123456, $span->toSpanData()->getStartEpochNanos()); + $this->assertCount(1, $span->toSpanData()->getLinks()); + } +} diff --git a/tests/Unit/Util/ConventionTest.php b/tests/Unit/Util/ConventionTest.php new file mode 100644 index 00000000000..04f100d9757 --- /dev/null +++ b/tests/Unit/Util/ConventionTest.php @@ -0,0 +1,62 @@ + 'GET']], + ['http.client.request', Tag::SPAN_KIND_VALUE_CLIENT, ['http.request.method' => 'GET']], + ['redis.query', Tag::SPAN_KIND_VALUE_CLIENT, ['db.system' => 'Redis']], + ['kafka.receive', Tag::SPAN_KIND_VALUE_CLIENT, ['messaging.system' => 'Kafka', 'messaging.operation' => 'Receive']], + ['kafka.receive', Tag::SPAN_KIND_VALUE_SERVER, ['messaging.system' => 'Kafka', 'messaging.operation' => 'Receive']], + ['kafka.receive', Tag::SPAN_KIND_VALUE_PRODUCER, ['messaging.system' => 'Kafka', 'messaging.operation' => 'Receive']], + ['kafka.receive', Tag::SPAN_KIND_VALUE_CONSUMER, ['messaging.system' => 'Kafka', 'messaging.operation' => 'Receive']], + ['aws.s3.request', Tag::SPAN_KIND_VALUE_CLIENT, ['rpc.system' => 'aws-api', 'rpc.service' => 'S3']], + ['aws.client.request', Tag::SPAN_KIND_VALUE_CLIENT, ['rpc.system' => 'aws-api']], + ['grpc.client.request', Tag::SPAN_KIND_VALUE_CLIENT, ['rpc.system' => 'GRPC']], + ['grpc.server.request', Tag::SPAN_KIND_VALUE_SERVER, ['rpc.system' => 'GRPC']], + ['aws.my-function.invoke', Tag::SPAN_KIND_VALUE_CLIENT, ['faas.invoked_provider' => 'aws', 'faas.invoked_name' => 'My-Function']], + ['datasource.invoke', Tag::SPAN_KIND_VALUE_SERVER, ['faas.trigger' => 'Datasource']], + ['graphql.server.request', Tag::SPAN_KIND_VALUE_SERVER, ['graphql.operation.type' => 'query']], + ['amqp.server.request', Tag::SPAN_KIND_VALUE_SERVER, ['network.protocol.name' => 'Amqp']], + ['server.request', Tag::SPAN_KIND_VALUE_SERVER, []], + ['amqp.client.request', Tag::SPAN_KIND_VALUE_CLIENT, ['network.protocol.name' => 'Amqp']], + ['client.request', Tag::SPAN_KIND_VALUE_CLIENT, []], + ['internal', Tag::SPAN_KIND_VALUE_INTERNAL, []], + ['consumer', Tag::SPAN_KIND_VALUE_CONSUMER, []], + ['producer', Tag::SPAN_KIND_VALUE_PRODUCER, []] + ]; + } + + /** + * @dataProvider providerConventionData + */ + public function testConvention($expectedOperationName, $spanKind, $attributes) + { + $span = start_span(); + $span->meta = $attributes; + if ($spanKind !== null) { + $span->meta[Tag::SPAN_KIND] = $spanKind; + } + + $this->assertSame($expectedOperationName, Convention::defaultOperationName($span)); + + close_span(); + } +} diff --git a/tests/composer.json b/tests/composer.json index 17b68aedbdb..0bcbcb78e68 100644 --- a/tests/composer.json +++ b/tests/composer.json @@ -10,6 +10,7 @@ ] }, "require-dev": { + "assertwell/phpunit-global-state": "^0.2.2", "mockery/mockery": "*", "phpunit/phpunit": "<10", "phpspec/prophecy": "*", @@ -115,6 +116,16 @@ "create-lockfile": false } }, + "opentelemetry1": { + "require": { + "open-telemetry/sdk": "@stable", + "open-telemetry/extension-propagator-b3": "@stable", + "open-telemetry/opentelemetry-logger-monolog": "@stable" + }, + "scenario-options": { + "create-lockfile": false + } + }, "opentracing_beta5": { "require": { "opentracing/opentracing": "1.0.0-beta5" diff --git a/tests/ext/dd_trace_span_link_with_exception.phpt b/tests/ext/dd_trace_span_link_with_exception.phpt index 563fe6325ac..295d471e296 100644 --- a/tests/ext/dd_trace_span_link_with_exception.phpt +++ b/tests/ext/dd_trace_span_link_with_exception.phpt @@ -50,7 +50,7 @@ try { $rr->waitForFlush(); $root = json_decode($rr->replayRequest()["body"], true); -$span = $root[0]['0']; +$span = $root[0][0]; var_dump($span['meta']['error.message']); var_dump($span['meta']['error.type']); var_dump($span['meta']['error.stack']); diff --git a/tests/ext/distributed_tracestate_consumption.phpt b/tests/ext/distributed_tracestate_consumption.phpt new file mode 100644 index 00000000000..a38167c23b8 --- /dev/null +++ b/tests/ext/distributed_tracestate_consumption.phpt @@ -0,0 +1,32 @@ +--TEST-- +Distributed tracestate consumption should produce valid tracestate header +--ENV-- +DD_TRACE_DEBUG_PRNG_SEED=42 +DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED=1 +--FILE-- +hexId(); +$traceId = \DDTrace\root_span()->traceId; +$traceFlags = '01'; +$traceParent = "00-$traceId-$parentId-$traceFlags"; + +\DDTrace\consume_distributed_tracing_headers([ + 'traceparent' => $traceParent, + 'tracestate' => $rawTracestate, +]); + +var_dump(\DDTrace\generate_distributed_tracing_headers(['tracecontext'])); + +?> +--EXPECTF-- +array(2) { + ["traceparent"]=> + string(55) "00-%sc151df7d6ee5e2d6-a3978fb9b92502a8-01" + ["tracestate"]=> + string(75) "dd=t.tid:%s;t.dm:-1;t.congo:t61rcWkgMzE,rojo=00f067aa0ba902b7" +} diff --git a/tests/ext/integrations/curl/distributed_tracing_curl_drop_dm.phpt b/tests/ext/integrations/curl/distributed_tracing_curl_drop_dm.phpt index 49b01d32965..0702a910e42 100644 --- a/tests/ext/integrations/curl/distributed_tracing_curl_drop_dm.phpt +++ b/tests/ext/integrations/curl/distributed_tracing_curl_drop_dm.phpt @@ -44,7 +44,7 @@ echo 'Done.' . PHP_EOL; ?> --EXPECTF-- traceparent: 00-12345678901234567890123456789012-1234567890123456-00 -tracestate: dd=t.tid:1234567890123456;t.usr.id:baz64~~;t.url:http://localhost,foo=1, +tracestate: dd=t.tid:1234567890123456;t.usr.id:baz64~~;t.url:http://localhost,foo=1 x-datadog-parent-id: 1311768467284833366 x-datadog-tags: _dd.p.tid=1234567890123456,_dd.p.usr.id=baz64==,_dd.p.url=http://localhost x-datadog-trace-id: 8687463697196027922 diff --git a/tests/ext/test_special_attributes.phpt b/tests/ext/test_special_attributes.phpt new file mode 100644 index 00000000000..7fbd099a8e1 --- /dev/null +++ b/tests/ext/test_special_attributes.phpt @@ -0,0 +1,63 @@ +--TEST-- +Reserved OTel attributes that have special meaning +--FILE-- +meta['operation.name'] = 'New.name'; + $span->meta['resource.name'] = 'new.resource'; + $span->meta['span.type'] = 'new.type'; + $span->meta['service.name'] = 'new.service'; + $span->meta['analytics.event'] = 'true'; + return "Hello $name!"; +} + +\DDTrace\trace_function('greet', function (\DDTrace\SpanData $span) { + $span->name = 'old.name'; + $span->resource = 'old.resource'; + $span->type = 'old.type'; + $span->service = 'old.service'; + $span->metrics['_dd1.sr.eausr'] = 0; +}); + +greet('World'); + +var_dump(dd_trace_serialize_closed_spans()); + +?> +--EXPECTF-- +array(1) { + [0]=> + array(11) { + ["trace_id"]=> + string(%d) "%d" + ["span_id"]=> + string(%d) "%d" + ["parent_id"]=> + string(%d) "%d" + ["start"]=> + int(%d) + ["duration"]=> + int(%d) + ["name"]=> + string(8) "new.name" + ["resource"]=> + string(12) "new.resource" + ["service"]=> + string(11) "new.service" + ["type"]=> + string(8) "new.type" + ["meta"]=> + array(1) { + ["_dd.base_service"]=> + string(27) "test_special_attributes.php" + } + ["metrics"]=> + array(1) { + ["_dd1.sr.eausr"]=> + float(1) + } + } +} diff --git a/tests/ext/test_special_attributes_bis.phpt b/tests/ext/test_special_attributes_bis.phpt new file mode 100644 index 00000000000..c33676a5224 --- /dev/null +++ b/tests/ext/test_special_attributes_bis.phpt @@ -0,0 +1,65 @@ +--TEST-- +Reserved OTel attributes that have special meaning +--ENV-- +DD_SERVICE_MAPPING=new.service:mapped.service +--FILE-- +meta['operation.name'] = 'New.name'; + $span->meta['resource.name'] = 'new.resource'; + $span->meta['span.type'] = 'new.type'; + $span->meta['service.name'] = 'new.service'; + $span->meta['analytics.event'] = true; + return "Hello $name!"; +} + +\DDTrace\trace_function('greet', function (\DDTrace\SpanData $span) { + $span->name = 'old.name'; + $span->resource = 'old.resource'; + $span->type = 'old.type'; + $span->service = 'old.service'; + $span->metrics['_dd1.sr.eausr'] = 0; +}); + +greet('World'); + +var_dump(dd_trace_serialize_closed_spans()); + +?> +--EXPECTF-- +array(1) { + [0]=> + array(11) { + ["trace_id"]=> + string(%d) "%d" + ["span_id"]=> + string(%d) "%d" + ["parent_id"]=> + string(%d) "%d" + ["start"]=> + int(%d) + ["duration"]=> + int(%d) + ["name"]=> + string(8) "new.name" + ["resource"]=> + string(12) "new.resource" + ["service"]=> + string(14) "mapped.service" + ["type"]=> + string(8) "new.type" + ["meta"]=> + array(1) { + ["_dd.base_service"]=> + string(31) "test_special_attributes_bis.php" + } + ["metrics"]=> + array(1) { + ["_dd1.sr.eausr"]=> + float(1) + } + } +} diff --git a/tests/phpunit.xml b/tests/phpunit.xml index b7207b2bd33..3269ac4a815 100644 --- a/tests/phpunit.xml +++ b/tests/phpunit.xml @@ -51,6 +51,16 @@ ./Integrations/Laravel/V10_x ./Integrations/CLI/Laravel/V10_X + + ./OpenTelemetry/Unit/API + ./OpenTelemetry/Unit/Context + ./OpenTelemetry/Unit/Propagation + ./OpenTelemetry/Integration/API + ./OpenTelemetry/Integration/Context + ./OpenTelemetry/Integration/Logs + ./OpenTelemetry/Integration/SDK + ./OpenTelemetry/Integration/InteroperabilityTest.php + ./Integrations/Slim/V3_12 diff --git a/tooling/bin/generate-final-artifact.sh b/tooling/bin/generate-final-artifact.sh index 250c2a03dac..61fb07e0e7e 100755 --- a/tooling/bin/generate-final-artifact.sh +++ b/tooling/bin/generate-final-artifact.sh @@ -48,7 +48,9 @@ for architecture in "${architectures[@]}"; do cp ./extensions_${architecture}/ddtrace-$php_api-debug-zts.so ${tmp_folder_final_gnu_trace}/ext/$php_api/ddtrace-debug-zts.so; fi done; + cp -r ./src ${tmp_folder_final_gnu_trace}; cp -r ./bridge ${tmp_folder_final_gnu_trace}; + cp -r ./src ${tmp_folder_final_musl_trace}; cp -r ./bridge ${tmp_folder_final_musl_trace}; ######################## From d80306e25a7b5909a3e1d8b9d810bba59e1c94f1 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Sun, 19 Nov 2023 21:18:52 +0100 Subject: [PATCH 10/16] Completely avoid instrumenting the PSR NullLogger (#2367) This is implemented by adding the ability to remove a hook from selected classes directly. --- ext/hook/uhook.c | 27 +++- ext/hook/uhook.stub.php | 4 +- ext/hook/uhook_arginfo.h | 3 +- .../Integrations/Logs/LogsIntegration.php | 8 +- .../install_hook/exclude_inherited_hook.phpt | 50 ++++++++ zend_abstract_interface/hook/hook.c | 121 ++++++++++++++++-- zend_abstract_interface/hook/hook.h | 6 +- 7 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 tests/ext/sandbox/install_hook/exclude_inherited_hook.phpt diff --git a/ext/hook/uhook.c b/ext/hook/uhook.c index 0de11b5ce28..d53266688c9 100644 --- a/ext/hook/uhook.c +++ b/ext/hook/uhook.c @@ -549,23 +549,38 @@ PHP_FUNCTION(DDTrace_install_hook) { RETURN_LONG(id); } /* }}} */ -/* {{{ proto void DDTrace\remove_hook(int $id) */ +/* {{{ proto void DDTrace\remove_hook(int $id, string $location = "") */ PHP_FUNCTION(DDTrace_remove_hook) { (void)return_value; zend_long id; - ZEND_PARSE_PARAMETERS_START(1, 1) + zend_string *location = NULL; + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_LONG(id) + Z_PARAM_OPTIONAL + Z_PARAM_STR(location) ZEND_PARSE_PARAMETERS_END(); dd_uhook_def *def; if ((def = zend_hash_index_find_ptr(&DDTRACE_G(uhook_active_hooks), (zend_ulong)id))) { - if (def->function) { + if (def->function || def->file) { zai_str scope = zai_str_from_zstr(def->scope); - zai_str function = ZAI_STR_FROM_ZSTR(def->function); - zai_hook_remove(scope, function, id); + zai_str function = zai_str_from_zstr(def->function); + if (location && ZSTR_LEN(location)) { + zend_string *lower = zend_string_tolower(location); + zai_hook_exclude_class(scope, function, id, lower); + zend_string_release(lower); + } else { + zai_hook_remove(scope, function, id); + } } else { - zai_hook_remove_resolved(def->install_address, id); + if (location && ZSTR_LEN(location)) { + zend_string *lower = zend_string_tolower(location); + zai_hook_exclude_class_resolved(def->install_address, id, lower); + zend_string_release(lower); + } else { + zai_hook_remove_resolved(def->install_address, id); + } } } } diff --git a/ext/hook/uhook.stub.php b/ext/hook/uhook.stub.php index a6887b1b538..2a06bf1192f 100644 --- a/ext/hook/uhook.stub.php +++ b/ext/hook/uhook.stub.php @@ -122,5 +122,7 @@ function install_hook( * Removes an installed hook by its id, as returned by install_hook or HookData->id. * * @param int $id The id to remove. + * @param string $location A class name (which inherits this hook through inheritance), which to specifically remove + * this hook from. */ -function remove_hook(int $id): void {} +function remove_hook(int $id, string $location = ""): void {} diff --git a/ext/hook/uhook_arginfo.h b/ext/hook/uhook_arginfo.h index fe9aef79526..6516be132ed 100644 --- a/ext/hook/uhook_arginfo.h +++ b/ext/hook/uhook_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 01f2bb558e7a4e81a309bd02af464ca618ea62ff */ + * Stub hash: e25a961e65f8aa086ed4f7d83b86b141e13e718c */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_install_hook, 0, 1, IS_LONG, 0) ZEND_ARG_OBJ_TYPE_MASK(0, target, Closure|Generator, MAY_BE_STRING|MAY_BE_CALLABLE, NULL) @@ -10,6 +10,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_remove_hook, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, id, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, location, IS_STRING, 0, "\"\"") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_DDTrace_HookData_span, 0, 0, DDTrace\\SpanData, 0) diff --git a/src/Integrations/Integrations/Logs/LogsIntegration.php b/src/Integrations/Integrations/Logs/LogsIntegration.php index 35ec669de30..43a129c9703 100644 --- a/src/Integrations/Integrations/Logs/LogsIntegration.php +++ b/src/Integrations/Integrations/Logs/LogsIntegration.php @@ -4,8 +4,8 @@ use DDTrace\HookData; use DDTrace\Integrations\Integration; -use DDTrace\SpanData; use DDTrace\Util\ObjectKVStore; +use Psr\Log\NullLogger; use function DDTrace\logs_correlation_trace_id; @@ -234,16 +234,18 @@ public function init() ]; foreach ($levelNames as $levelName) { - \DDTrace\install_hook( + $hook = \DDTrace\install_hook( "Psr\Log\LoggerInterface::$levelName", self::getHookFn($levelName, 0, 1) ); + \DDTrace\remove_hook($hook, NullLogger::class); } - \DDTrace\install_hook( + $hook = \DDTrace\install_hook( "Psr\Log\LoggerInterface::log", self::getHookFn('log', 1, 2, 0) ); + \DDTrace\remove_hook($hook, NullLogger::class); return Integration::LOADED; } diff --git a/tests/ext/sandbox/install_hook/exclude_inherited_hook.phpt b/tests/ext/sandbox/install_hook/exclude_inherited_hook.phpt new file mode 100644 index 00000000000..e1c1f181ad0 --- /dev/null +++ b/tests/ext/sandbox/install_hook/exclude_inherited_hook.phpt @@ -0,0 +1,50 @@ +--TEST-- +remove_hook() with class argument +--FILE-- +foo(); + +if (time()) { + abstract class Child implements Elder {} + + class GrandChild extends Child { + function foo() { + print static::class . "\n"; + } + } + + class GreatGrandChild extends GrandChild { + } +} + +(new GrandChild)->foo(); // no hook +(new GreatGrandChild)->foo(); // no hook + +DDTrace\remove_hook($id, "Other"); +(new Other)->foo(); // also removed now +(new OtherChild)->foo(); // also removed now + +?> +--EXPECT-- +HOOKED: Other +Other +GrandChild +GreatGrandChild +Other +OtherChild diff --git a/zend_abstract_interface/hook/hook.c b/zend_abstract_interface/hook/hook.c index 34c34bc8d7a..c5b9adc114e 100644 --- a/zend_abstract_interface/hook/hook.c +++ b/zend_abstract_interface/hook/hook.c @@ -17,6 +17,7 @@ typedef struct { bool is_abstract; zend_long id; int refcount; // one ref held by the existence in the array, one ref held by each frame + HashTable exclusions; } zai_hook_t; /* }}} */ typedef struct _zai_hooks_entry { @@ -107,6 +108,10 @@ static void zai_hook_data_dtor(zai_hook_t *hook) { if (hook->function) { zend_string_release(hook->function); } + + if (hook->exclusions.arData) { + zend_hash_destroy(&hook->exclusions); + } } static void zai_hook_static_destroy(zval *zv) { @@ -226,6 +231,30 @@ static void zai_hook_hash_destroy(zval *zv) { efree(hooks); } +static inline bool zai_hook_is_excluded(zai_hook_t *hook, zend_class_entry *ce) { + if (hook->exclusions.arData && ce) { + do { + zend_string *lower_class = zend_string_tolower(ce->name); + if (zend_hash_exists(&hook->exclusions, lower_class)) { + zend_string_release(lower_class); + return true; + } + zend_string_release(lower_class); + + for (uint32_t i = 0; i < ce->num_interfaces; ++i) { + zend_string *interface = ce->interfaces[i]->name; + zend_string *lower_interface = zend_string_tolower(interface); + if (zend_hash_exists(&hook->exclusions, lower_interface)) { + zend_string_release(lower_interface); + return true; + } + zend_string_release(lower_interface); + } + } while ((ce = ce->parent)); + } + return false; +} + /* {{{ */ static inline zend_function *zai_hook_lookup_function(zai_str scope, zai_str func, zend_class_entry **ce) { zend_function *function = NULL; @@ -417,6 +446,10 @@ static inline zai_hooks_entry *zai_hook_resolved_ensure_hooks_entry(zend_functio } static inline void zai_hook_resolved_install_shared_hook(zai_hook_t *hook, zend_ulong index, zend_function *func, zend_class_entry *ce) { + if (zai_hook_is_excluded(hook, ce)) { + return; + } + zai_hooks_entry *hooks = zai_hook_resolved_ensure_hooks_entry(func, ce); zval hook_zv; @@ -549,20 +582,24 @@ static inline void zai_hook_merge_inherited_hooks(zai_hooks_entry **hooks_entry, zai_hooks_entry *protoHooks; if ((protoHooks = zend_hash_index_find_ptr(&zai_hook_resolved, addr))) { zai_hooks_entry *hooks = *hooks_entry; - if (!hooks && !(hooks = zend_hash_index_find_ptr(&zai_hook_resolved, zai_hook_install_address(function)))) { - *hooks_entry = hooks = zend_hash_index_add_ptr(&zai_hook_resolved, zai_hook_install_address(function), zai_hook_alloc_hooks_entry()); - zai_hook_resolve_hooks_entry(hooks, function); + zval *hook_zv; + zend_ulong index; + ZEND_HASH_FOREACH_NUM_KEY_VAL(&protoHooks->hooks, index, hook_zv) { + if (zai_hook_is_excluded(Z_PTR_P(hook_zv), ce)) { + continue; + } + + if (!hooks && !(hooks = zend_hash_index_find_ptr(&zai_hook_resolved, zai_hook_install_address(function)))) { + *hooks_entry = hooks = zend_hash_index_add_ptr(&zai_hook_resolved, zai_hook_install_address(function), zai_hook_alloc_hooks_entry()); + zai_hook_resolve_hooks_entry(hooks, function); #if PHP_VERSION_ID >= 80200 - // Internal functions duplicated onto userland classes share their run_time_cache with their parent function - zai_hook_handle_internal_duplicate_function(hooks, ce, function); + // Internal functions duplicated onto userland classes share their run_time_cache with their parent function + zai_hook_handle_internal_duplicate_function(hooks, ce, function); #else - (void)ce; + (void)ce; #endif - } + } - zval *hook_zv; - zend_ulong index; - ZEND_HASH_FOREACH_NUM_KEY_VAL(&protoHooks->hooks, index, hook_zv) { if ((hook_zv = zend_hash_index_add(&hooks->hooks, index, hook_zv))) { zai_hook_t *hook = Z_PTR_P(hook_zv); hooks->dynamic += hook->dynamic; @@ -1144,6 +1181,7 @@ zend_long zai_hook_install_resolved_generator(zend_function *function, .id = 0, .dynamic = dynamic, .refcount = 1, + .exclusions = {{ 0 }}, }; return hook->id = zai_hook_resolved_install(hook, function, function->common.scope); @@ -1185,6 +1223,7 @@ zend_long zai_hook_install_generator(zai_str scope, zai_str function, .id = 0, .dynamic = dynamic, .refcount = 1, + .exclusions = {{ 0 }}, }; if (persistent) { @@ -1201,6 +1240,68 @@ zend_long zai_hook_install(zai_str scope, zai_str function, return zai_hook_install_generator(scope, function, begin, NULL, NULL, end, aux, dynamic); } /* }}} */ +static inline void zai_hook_add_exclusion(zai_hooks_entry *hooks, zend_long index, zend_string *lc_classname) { + if (hooks) { + zai_hook_t *hook = zend_hash_index_find_ptr(&hooks->hooks, index); + + if (!hook || hook->id < 0) { + return; + } + + if (!hook->exclusions.arData) { + zend_hash_init(&hook->exclusions, 8, NULL, NULL, 0); + } + + zend_hash_add_empty_element(&hook->exclusions, lc_classname); + } +} + +void zai_hook_exclude_class_resolved(zai_install_address function_address, zend_long index, zend_string *lc_classname) { + zai_hooks_entry *hooks = zend_hash_index_find_ptr(&zai_hook_resolved, function_address); + if (!hooks) { + return; + } + zai_hook_add_exclusion(hooks, index, lc_classname); + + zend_class_entry *ce = NULL; + zend_string *function_name = hooks->resolved->common.function_name; + zend_function *resolved = zai_hook_lookup_function(zai_str_from_zstr(lc_classname), zai_str_from_zstr(function_name), &ce); + if (!ce || !resolved) { + return; + } + zai_hooks_entry *excluded_hooks = zend_hash_index_find_ptr(&zai_hook_resolved, zai_hook_install_address(resolved)); + if (!excluded_hooks) { + return; + } + + zval *hook_zv = zend_hash_index_find(&excluded_hooks->hooks, index); + if (!hook_zv || Z_TYPE_INFO_P(hook_zv) != ZAI_IS_SHARED_HOOK_PTR) { + return; + } + + zai_hook_remove_abstract_recursive(hooks, ce, function_name, (zend_ulong) index); +} + +void zai_hook_exclude_class(zai_str scope, zai_str function, zend_long index, zend_string *lc_classname) { + if (!function.len || !scope.len) { + return; + } + + zend_class_entry *ce; + zend_function *resolved = zai_hook_lookup_function(scope, function, &ce); + if (resolved) { + zai_hook_exclude_class_resolved(zai_hook_install_address(resolved), index, lc_classname); + return; + } + + HashTable *base_ht = zend_hash_str_find_ptr_lc(&zai_hook_tls->request_classes, scope.ptr, scope.len); + if (!base_ht) { + return; + } + zai_hooks_entry *hooks = zend_hash_str_find_ptr_lc(base_ht, function.ptr, function.len); + zai_hook_add_exclusion(hooks, index, lc_classname); +} + bool zai_hook_remove_resolved(zai_install_address function_address, zend_long index) { zai_hooks_entry *hooks = zend_hash_index_find_ptr(&zai_hook_resolved, function_address); if (hooks) { diff --git a/zend_abstract_interface/hook/hook.h b/zend_abstract_interface/hook/hook.h index b23da28cd11..1f4ffcb0ed7 100644 --- a/zend_abstract_interface/hook/hook.h +++ b/zend_abstract_interface/hook/hook.h @@ -1,6 +1,6 @@ #ifndef ZAI_HOOK_H #define ZAI_HOOK_H -#include +#include "../symbols/symbols.h" /* The Hook interface intends to abstract away the storage and resolution of hook targets */ @@ -62,6 +62,10 @@ zend_long zai_hook_install_resolved_generator(zend_function *function, bool zai_hook_remove(zai_str scope, zai_str function, zend_long index); bool zai_hook_remove_resolved(zai_install_address function_address, zend_long index); /* }}} */ +/* {{{ zai_hook_exclude_class prevents a hook from being installed on a specific class through inheritance. */ +void zai_hook_exclude_class(zai_str scope, zai_str function, zend_long index, zend_string *lc_classname); +void zai_hook_exclude_class_resolved(zai_install_address function_address, zend_long index, zend_string *lc_classname); /* }}} */ + /* {{{ zai_hook_memory_t structure is passed between continue and finish and managed by the hook interface */ typedef struct { From 8b7df34b8a7a657ed4e674f44897bc09f1c14361 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Mon, 20 Nov 2023 00:12:03 +0100 Subject: [PATCH 11/16] Add tracing for curl_multi_exec() (#2347) * Add tracing for curl_multi_exec() Fixes #1156. Signed-off-by: Bob Weinand * Also track curl_multi_info_read for curl_multi_exec errors Signed-off-by: Bob Weinand * Fix CI * Fix memory leak Signed-off-by: Bob Weinand * Try fixing test flakiness Signed-off-by: Bob Weinand * Reflection doesn't like calling functions with references on PHP 7.0 --------- Signed-off-by: Bob Weinand --- ext/compatibility.h | 10 + ext/ddtrace.c | 98 +++++++- ext/ddtrace.h | 1 + ext/ddtrace.stub.php | 22 +- ext/ddtrace_arginfo.h | 16 +- ext/handlers_curl.c | 28 ++- ext/handlers_curl_php7.c | 29 ++- ext/hook/uhook.c | 9 +- ext/integrations/integrations.c | 2 + .../Integrations/Curl/CurlIntegration.php | 226 ++++++++++++++---- .../Integrations/Curl/CurlIntegrationTest.php | 59 +++++ .../Guzzle/V5/GuzzleIntegrationTest.php | 4 +- .../Guzzle/V6/GuzzleIntegrationTest.php | 17 +- tests/internal-api-stress-test.php | 3 +- .../exceptions/exceptions.c | 18 +- .../exceptions/exceptions.h | 2 + 16 files changed, 470 insertions(+), 74 deletions(-) diff --git a/ext/compatibility.h b/ext/compatibility.h index 2b8669f7ac3..af93acb357c 100644 --- a/ext/compatibility.h +++ b/ext/compatibility.h @@ -116,6 +116,8 @@ static inline zend_string *php_base64_encode_str(const zend_string *str) { #define GC_ADD_FLAGS(c, flag) GC_FLAGS(c) |= flag #define GC_DEL_FLAGS(c, flag) GC_FLAGS(c) &= ~(flag) +#define rc_dtor_func zval_dtor_func + static inline HashTable *zend_new_array(uint32_t nSize) { HashTable *ht = (HashTable *)emalloc(sizeof(HashTable)); zend_hash_init(ht, nSize, dummy, ZVAL_PTR_DTOR, 0); @@ -292,6 +294,14 @@ static zend_always_inline void zend_array_release(zend_array *array) #define ZEND_ATOL(s) atol((s)) #endif #define ZEND_ACC_READONLY 0 + +static zend_always_inline zend_result add_next_index_object(zval *arg, zend_object *obj) { + zval tmp; + + ZVAL_OBJ(&tmp, obj); + return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE; +} + #endif #if PHP_VERSION_ID < 80200 diff --git a/ext/ddtrace.c b/ext/ddtrace.c index a855615ba79..4f983679748 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1017,6 +1017,13 @@ static void dd_clean_globals(void) { zend_hash_destroy(&DDTRACE_G(tracestate_unknown_dd_keys)); zend_hash_destroy(&DDTRACE_G(propagated_root_span_tags)); + if (DDTRACE_G(curl_multi_injecting_spans)) { + if (GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) { + rc_dtor_func((zend_refcounted *) DDTRACE_G(curl_multi_injecting_spans)); + } + DDTRACE_G(curl_multi_injecting_spans) = NULL; + } + if (DDTRACE_G(dd_origin)) { zend_string_release(DDTRACE_G(dd_origin)); DDTRACE_G(dd_origin) = NULL; @@ -1792,15 +1799,15 @@ PHP_FUNCTION(dd_trace_internal_fn) { /* {{{ proto int DDTrace\close_spans_until(DDTrace\SpanData) */ PHP_FUNCTION(DDTrace_close_spans_until) { - zval *untilzv = NULL; + zval *spanzv = NULL; - if (zend_parse_parameters_ex(ddtrace_quiet_zpp(), ZEND_NUM_ARGS(), "O!", &untilzv, ddtrace_ce_span_data) == + if (zend_parse_parameters_ex(ddtrace_quiet_zpp(), ZEND_NUM_ARGS(), "O!", &spanzv, ddtrace_ce_span_data) == FAILURE) { LOG_LINE_ONCE(Error, "DDTrace\\close_spans_until() expects null or a SpanData object"); RETURN_FALSE; } - int closed_spans = ddtrace_close_userland_spans_until(untilzv ? OBJ_SPANDATA(Z_OBJ_P(untilzv)) : NULL); + int closed_spans = ddtrace_close_userland_spans_until(spanzv ? OBJ_SPANDATA(Z_OBJ_P(spanzv)) : NULL); if (closed_spans == -1) { RETURN_FALSE; @@ -1961,6 +1968,17 @@ PHP_FUNCTION(DDTrace_start_trace_span) { dd_start_span(INTERNAL_FUNCTION_PARAM_PASSTHRU); } +static void dd_set_span_finish_time(ddtrace_span_data *span, double finish_time_seconds) { + // we do not expose the monotonic time here, so do not use it as reference time to calculate difference + uint64_t start_time = span->start; + uint64_t finish_time = (uint64_t)(finish_time_seconds * 1000000000); + if (finish_time < start_time) { + dd_trace_stop_span_time(span); + } else { + span->duration = finish_time - start_time; + } +} + /* {{{ proto string DDTrace\close_span() */ PHP_FUNCTION(DDTrace_close_span) { double finish_time_seconds = 0; @@ -1976,19 +1994,36 @@ PHP_FUNCTION(DDTrace_close_span) { RETURN_NULL(); } - // we do not expose the monotonic time here, so do not use it as reference time to calculate difference - uint64_t start_time = top_span->start; - uint64_t finish_time = (uint64_t)(finish_time_seconds * 1000000000); - if (finish_time < start_time) { - dd_trace_stop_span_time(top_span); - } else { - top_span->duration = finish_time - start_time; - } + dd_set_span_finish_time(top_span, finish_time_seconds); ddtrace_close_span(top_span); RETURN_NULL(); } +/* {{{ proto string DDTrace\update_span_duration() */ +PHP_FUNCTION(DDTrace_update_span_duration) { + double finish_time_seconds = 0; + zval *spanzv = NULL; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|d", &spanzv, ddtrace_ce_span_data, &finish_time_seconds) != SUCCESS) { + RETURN_FALSE; + } + + ddtrace_span_data *span = OBJ_SPANDATA(Z_OBJ_P(spanzv)); + + if (span->duration == 0) { + LOG(Error, "Cannot update the span duration of an unfinished span."); + RETURN_NULL(); + } + + if (span->duration == DDTRACE_DROPPED_SPAN || span->duration == DDTRACE_SILENTLY_DROPPED_SPAN) { + RETURN_NULL(); + } + + dd_set_span_finish_time(span, finish_time_seconds); + + RETURN_NULL(); +} + /* {{{ proto string DDTrace\active_stack() */ PHP_FUNCTION(DDTrace_active_stack) { if (zend_parse_parameters_ex(ddtrace_quiet_zpp(), ZEND_NUM_ARGS(), "")) { @@ -2428,15 +2463,18 @@ PHP_FUNCTION(DDTrace_get_priority_sampling) { PHP_FUNCTION(DDTrace_get_sanitized_exception_trace) { zend_object *ex; + zend_long skip = 0; - ZEND_PARSE_PARAMETERS_START_EX(ddtrace_quiet_zpp(), 1, 1) + ZEND_PARSE_PARAMETERS_START_EX(ddtrace_quiet_zpp(), 1, 2) Z_PARAM_OBJ_OF_CLASS(ex, zend_ce_throwable) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(skip) ZEND_PARSE_PARAMETERS_END_EX({ LOG_LINE_ONCE(Error, "unexpected parameter for DDTrace\\get_sanitized_exception_trace, the first argument must be a Throwable"); RETURN_FALSE; }); - RETURN_STR(zai_get_trace_without_args_from_exception(ex)); + RETURN_STR(zai_get_trace_without_args_from_exception_skip_frames(ex, skip)); } PHP_FUNCTION(DDTrace_startup_logs) { @@ -2473,6 +2511,40 @@ PHP_FUNCTION(DDTrace_extract_ip_from_headers) { RETURN_ARR(Z_ARR(meta)); } +PHP_FUNCTION(DDTrace_curl_multi_exec_get_request_spans) { + zval *array; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(array) + ZEND_PARSE_PARAMETERS_END(); + + if (Z_TYPE_P(array) == IS_REFERENCE) { + zend_reference *ref = Z_REF_P(array); + +#if PHP_VERSION_ID < 70400 + array = &ref->val; + zval_ptr_dtor(array); + array_init(array); +#else + array = zend_try_array_init(array); + if (!array) { + RETURN_THROWS(); + } +#endif + + if (get_DD_TRACE_ENABLED()) { + if (DDTRACE_G(curl_multi_injecting_spans) && GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) { + rc_dtor_func((zend_refcounted *) DDTRACE_G(curl_multi_injecting_spans)); + } + + GC_ADDREF(ref); + DDTRACE_G(curl_multi_injecting_spans) = ref; + } + } + + RETURN_NULL(); +} + static const zend_module_dep ddtrace_module_deps[] = {ZEND_MOD_REQUIRED("json") ZEND_MOD_REQUIRED("standard") ZEND_MOD_END}; diff --git a/ext/ddtrace.h b/ext/ddtrace.h index a41366eed10..48150a23d1e 100644 --- a/ext/ddtrace.h +++ b/ext/ddtrace.h @@ -92,6 +92,7 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace) ddtrace_trace_id distributed_trace_id; uint64_t distributed_parent_trace_id; zend_string *dd_origin; + zend_reference *curl_multi_injecting_spans; char *cgroup_file; ddog_QueueId telemetry_queue_id; diff --git a/ext/ddtrace.stub.php b/ext/ddtrace.stub.php index 9b88a09fda3..01b33fd366e 100644 --- a/ext/ddtrace.stub.php +++ b/ext/ddtrace.stub.php @@ -403,11 +403,19 @@ function start_span(float $startTime = 0): SpanData|false {} /** * Close the currently active user-span on the top of the stack * - * @param float $finishTime Finish time in seconds. + * @param float $finishTime Finish time in seconds. Defaults to now if zero. * @return false|null 'false' if unexpected parameters were given, else 'null' */ function close_span(float $finishTime = 0): false|null {} + /** + * Update the duration of an already closed span + * + * @param SpanData $span The span to update. + * @param float $finishTime Finish time in seconds. Defaults to now if zero. + */ + function update_span_duration(SpanData $span, float $finishTime = 0): null {} + /** * Start a new trace * @@ -467,9 +475,11 @@ function get_priority_sampling(bool $global = false): int|null {} * Sanitize an exception * * @param \Exception|\Throwable $exception + * @param int $skipFrames The number of frames to be dropped from the start. E.g. to hide the fact that we're + * in a hook function. * @return string */ - function get_sanitized_exception_trace(\Exception|\Throwable $exception): string {} + function get_sanitized_exception_trace(\Exception|\Throwable $exception, int $skipFrames = 0): string {} /** * Update datadog headers for distributed tracing for new spans. Also applies this information to the current trace, @@ -569,6 +579,14 @@ function set_distributed_tracing_context( * Closes all spans and force-send finished traces to the agent */ function flush(): void {} + + /** + * Registers an array to be populated with spans for each request during the next curl_multi_exec() call. + * + * @internal + * @param list{\CurlHandle, SpanData}[] $array An array which will be populated with curl handles and spans. + */ + function curl_multi_exec_get_request_spans(&$array): void {} } namespace DDTrace\System { diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index 6563d45b5db..0d0ed49079d 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 85f2b3a4b45cb7685e5ec115eefa59ae7f20cd79 */ + * Stub hash: 1d07ca443c39ea7a0a831b062bb0efe4baf69b0f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_trace_method, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, className, IS_STRING, 0) @@ -56,6 +56,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_close_span, 0, 0, IS_FAL ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, finishTime, IS_DOUBLE, 0, "0") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_update_span_duration, 0, 1, IS_NULL, 1) + ZEND_ARG_OBJ_INFO(0, span, DDTrace\\SpanData, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, finishTime, IS_DOUBLE, 0, "0") +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_DDTrace_start_trace_span, 0, 0, DDTrace\\SpanData, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, startTime, IS_DOUBLE, 0, "0") ZEND_END_ARG_INFO() @@ -81,6 +86,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_get_sanitized_exception_trace, 0, 1, IS_STRING, 0) ZEND_ARG_OBJ_TYPE_MASK(0, exception, Exception|Throwable, 0, NULL) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, skipFrames, IS_LONG, 0, "0") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_consume_distributed_tracing_headers, 0, 1, IS_VOID, 0) @@ -118,6 +124,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_flush, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_curl_multi_exec_get_request_spans, 0, 1, IS_VOID, 0) + ZEND_ARG_INFO(1, array) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_System_container_id, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -269,6 +279,7 @@ ZEND_FUNCTION(DDTrace_active_span); ZEND_FUNCTION(DDTrace_root_span); ZEND_FUNCTION(DDTrace_start_span); ZEND_FUNCTION(DDTrace_close_span); +ZEND_FUNCTION(DDTrace_update_span_duration); ZEND_FUNCTION(DDTrace_start_trace_span); ZEND_FUNCTION(DDTrace_active_stack); ZEND_FUNCTION(DDTrace_create_stack); @@ -286,6 +297,7 @@ ZEND_FUNCTION(DDTrace_logs_correlation_trace_id); ZEND_FUNCTION(DDTrace_current_context); ZEND_FUNCTION(DDTrace_set_distributed_tracing_context); ZEND_FUNCTION(DDTrace_flush); +ZEND_FUNCTION(DDTrace_curl_multi_exec_get_request_spans); ZEND_FUNCTION(DDTrace_System_container_id); ZEND_FUNCTION(DDTrace_Config_integration_analytics_enabled); ZEND_FUNCTION(DDTrace_Config_integration_analytics_sample_rate); @@ -346,6 +358,7 @@ static const zend_function_entry ext_functions[] = { ZEND_NS_FALIAS("DDTrace", root_span, DDTrace_root_span, arginfo_DDTrace_root_span) ZEND_NS_FALIAS("DDTrace", start_span, DDTrace_start_span, arginfo_DDTrace_start_span) ZEND_NS_FALIAS("DDTrace", close_span, DDTrace_close_span, arginfo_DDTrace_close_span) + ZEND_NS_FALIAS("DDTrace", update_span_duration, DDTrace_update_span_duration, arginfo_DDTrace_update_span_duration) ZEND_NS_FALIAS("DDTrace", start_trace_span, DDTrace_start_trace_span, arginfo_DDTrace_start_trace_span) ZEND_NS_FALIAS("DDTrace", active_stack, DDTrace_active_stack, arginfo_DDTrace_active_stack) ZEND_NS_FALIAS("DDTrace", create_stack, DDTrace_create_stack, arginfo_DDTrace_create_stack) @@ -363,6 +376,7 @@ static const zend_function_entry ext_functions[] = { ZEND_NS_FALIAS("DDTrace", current_context, DDTrace_current_context, arginfo_DDTrace_current_context) ZEND_NS_FALIAS("DDTrace", set_distributed_tracing_context, DDTrace_set_distributed_tracing_context, arginfo_DDTrace_set_distributed_tracing_context) ZEND_NS_FALIAS("DDTrace", flush, DDTrace_flush, arginfo_DDTrace_flush) + ZEND_NS_FALIAS("DDTrace", curl_multi_exec_get_request_spans, DDTrace_curl_multi_exec_get_request_spans, arginfo_DDTrace_curl_multi_exec_get_request_spans) ZEND_NS_FALIAS("DDTrace\\System", container_id, DDTrace_System_container_id, arginfo_DDTrace_System_container_id) ZEND_NS_FALIAS("DDTrace\\Config", integration_analytics_enabled, DDTrace_Config_integration_analytics_enabled, arginfo_DDTrace_Config_integration_analytics_enabled) ZEND_NS_FALIAS("DDTrace\\Config", integration_analytics_sample_rate, DDTrace_Config_integration_analytics_sample_rate, arginfo_DDTrace_Config_integration_analytics_sample_rate) diff --git a/ext/handlers_curl.c b/ext/handlers_curl.c index 9426117aab6..d0b640ac9e6 100644 --- a/ext/handlers_curl.c +++ b/ext/handlers_curl.c @@ -125,8 +125,32 @@ static void dd_multi_inject_headers(zend_object *mh) { if (handles && zend_hash_num_elements(handles) > 0) { zend_object *ch; - ZEND_HASH_FOREACH_PTR(handles, ch) { dd_inject_distributed_tracing_headers(ch); } - ZEND_HASH_FOREACH_END(); + ZEND_HASH_FOREACH_PTR(handles, ch) { + if (DDTRACE_G(curl_multi_injecting_spans) && Z_TYPE(DDTRACE_G(curl_multi_injecting_spans)->val) == IS_ARRAY) { + ddtrace_span_data *span = ddtrace_open_span(DDTRACE_INTERNAL_SPAN); + dd_inject_distributed_tracing_headers(ch); + ddtrace_close_span(span); + span->duration = 1; + + SEPARATE_ARRAY(&DDTRACE_G(curl_multi_injecting_spans)->val); + + zval data; + array_init(&data); + GC_ADDREF(ch); + add_next_index_object(&data, ch); + add_next_index_object(&data, &span->std); + zend_hash_next_index_insert(Z_ARR(DDTRACE_G(curl_multi_injecting_spans)->val), &data); + } else { + dd_inject_distributed_tracing_headers(ch); + } + } ZEND_HASH_FOREACH_END(); + + if (DDTRACE_G(curl_multi_injecting_spans)) { + if (GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) { + rc_dtor_func((zend_refcounted *) DDTRACE_G(curl_multi_injecting_spans)); + } + DDTRACE_G(curl_multi_injecting_spans) = NULL; + } zend_weakrefs_hash_del(&dd_multi_handles, mh); } diff --git a/ext/handlers_curl_php7.c b/ext/handlers_curl_php7.c index b1a1ee99dd5..fb29f376b93 100644 --- a/ext/handlers_curl_php7.c +++ b/ext/handlers_curl_php7.c @@ -157,6 +157,26 @@ static int dd_inject_distributed_tracing_headers(zval *ch) { return ZEND_HASH_APPLY_REMOVE; } +static int dd_inject_distributed_tracing_headers_multi(zval *ch) { + if (DDTRACE_G(curl_multi_injecting_spans) && Z_TYPE(DDTRACE_G(curl_multi_injecting_spans)->val) == IS_ARRAY) { + ddtrace_span_data *span = ddtrace_open_span(DDTRACE_INTERNAL_SPAN); + int ret = dd_inject_distributed_tracing_headers(ch); + ddtrace_close_span(span); + span->duration = 1; + + SEPARATE_ARRAY(&DDTRACE_G(curl_multi_injecting_spans)->val); + + zval data; + array_init(&data); + Z_ADDREF_P(ch); + add_next_index_zval(&data, ch); + add_next_index_object(&data, &span->std); + zend_hash_next_index_insert(Z_ARR(DDTRACE_G(curl_multi_injecting_spans)->val), &data); + return ret; + } + return dd_inject_distributed_tracing_headers(ch); +} + static bool dd_is_valid_curl_resource(zval *ch) { if (le_curl) { void *resource = zend_fetch_resource(Z_RES_P(ch), NULL, le_curl); @@ -268,9 +288,16 @@ static void dd_multi_inject_headers(zval *mh) { } if (handles && zend_hash_num_elements(handles) > 0) { - zend_hash_apply(handles, dd_inject_distributed_tracing_headers); + zend_hash_apply(handles, dd_inject_distributed_tracing_headers_multi); dd_multi_reset(mh); } + + if (DDTRACE_G(curl_multi_injecting_spans)) { + if (GC_DELREF(DDTRACE_G(curl_multi_injecting_spans)) == 0) { + rc_dtor_func((zend_refcounted *) DDTRACE_G(curl_multi_injecting_spans)); + } + DDTRACE_G(curl_multi_injecting_spans) = NULL; + } } ZEND_FUNCTION(ddtrace_curl_close) { diff --git a/ext/hook/uhook.c b/ext/hook/uhook.c index d53266688c9..c9f9556d5c2 100644 --- a/ext/hook/uhook.c +++ b/ext/hook/uhook.c @@ -166,7 +166,7 @@ void dd_uhook_report_sandbox_error(zend_execute_data *execute_data, zend_object }) } -static void dd_uhook_call_hook(zend_execute_data *execute_data, zend_object *closure, dd_hook_data *hook_data) { +static bool dd_uhook_call_hook(zend_execute_data *execute_data, zend_object *closure, dd_hook_data *hook_data) { zval closure_zv, hook_data_zv; ZVAL_OBJ(&closure_zv, closure); ZVAL_OBJ(&hook_data_zv, &hook_data->std); @@ -182,6 +182,7 @@ static void dd_uhook_call_hook(zend_execute_data *execute_data, zend_object *clo } zai_sandbox_close(&sandbox); zval_ptr_dtor(&rv); + return Z_TYPE(rv) != IS_FALSE; } static bool dd_uhook_match_filepath(zend_string *file, zend_string *source) { @@ -268,6 +269,8 @@ static void dd_uhook_end(zend_ulong invocation, zend_execute_data *execute_data, dd_trace_stop_span_time(span); } + bool keep_span = true; + if (def->end && !def->running && get_DD_TRACE_ENABLED()) { zval tmp; @@ -307,7 +310,7 @@ static void dd_uhook_end(zend_ulong invocation, zend_execute_data *execute_data, def->running = true; dyn->hook_data->retval_ptr = retval; - dd_uhook_call_hook(execute_data, def->end, dyn->hook_data); + keep_span = dd_uhook_call_hook(execute_data, def->end, dyn->hook_data); dyn->hook_data->retval_ptr = NULL; def->running = false; } @@ -316,7 +319,7 @@ static void dd_uhook_end(zend_ulong invocation, zend_execute_data *execute_data, dyn->hook_data->span = NULL; // e.g. spans started in limited mode are never properly started if (span->start) { - ddtrace_clear_execute_data_span(invocation, true); + ddtrace_clear_execute_data_span(invocation, keep_span); if (dyn->hook_data->prior_stack) { ddtrace_switch_span_stack(dyn->hook_data->prior_stack); OBJ_RELEASE(&dyn->hook_data->prior_stack->std); diff --git a/ext/integrations/integrations.c b/ext/integrations/integrations.c index ed56c570344..d282a730fd5 100644 --- a/ext/integrations/integrations.c +++ b/ext/integrations/integrations.c @@ -160,6 +160,8 @@ void ddtrace_integrations_minit(void) { DD_SET_UP_DEFERRED_LOADING_BY_FUNCTION(DDTRACE_INTEGRATION_CURL, "curl_exec", "DDTrace\\Integrations\\Curl\\CurlIntegration"); + DD_SET_UP_DEFERRED_LOADING_BY_FUNCTION(DDTRACE_INTEGRATION_CURL, "curl_multi_exec", + "DDTrace\\Integrations\\Curl\\CurlIntegration"); DD_SET_UP_DEFERRED_LOADING_BY_METHOD(DDTRACE_INTEGRATION_DRUPAL, "Drupal\\Core\\DrupalKernel", "__construct", "DDTrace\\Integrations\\Drupal\\DrupalIntegration"); diff --git a/src/Integrations/Integrations/Curl/CurlIntegration.php b/src/Integrations/Integrations/Curl/CurlIntegration.php index 2cc5959581e..4d2f3ca6550 100644 --- a/src/Integrations/Integrations/Curl/CurlIntegration.php +++ b/src/Integrations/Integrations/Curl/CurlIntegration.php @@ -2,12 +2,14 @@ namespace DDTrace\Integrations\Curl; +use DDTrace\HookData; use DDTrace\Http\Urls; use DDTrace\Integrations\HttpClientIntegrationHelper; use DDTrace\Integrations\Integration; use DDTrace\SpanData; use DDTrace\Tag; use DDTrace\Type; +use DDTrace\Util\ObjectKVStore; /** * @param \DDTrace\SpanData $span @@ -48,13 +50,7 @@ public function init() // the ddtrace extension will handle distributed headers 'instrument_when_limited' => 0, 'posthook' => function (SpanData $span, $args, $retval) use ($integration) { - $span->name = $span->resource = 'curl_exec'; - $span->type = Type::HTTP_CLIENT; - $span->service = 'curl'; - Integration::handleInternalSpanServiceName($span, CurlIntegration::NAME); - $integration->addTraceAnalyticsIfEnabled($span); - $span->meta[Tag::COMPONENT] = CurlIntegration::NAME; - $span->meta[Tag::SPAN_KIND] = Tag::SPAN_KIND_VALUE_CLIENT; + $integration->setup_curl_span($span); if (!isset($args[0])) { return; @@ -65,59 +61,205 @@ public function init() $span->meta[Tag::ERROR_MSG] = \curl_error($ch); $span->meta[Tag::ERROR_TYPE] = 'curl error'; if (PHP_VERSION_ID >= 70000) { - $span->meta[Tag::ERROR_STACK] = \DDTrace\get_sanitized_exception_trace(new \Exception()); + $span->meta[Tag::ERROR_STACK] = \DDTrace\get_sanitized_exception_trace(new \Exception(), 1); } } - $info = \curl_getinfo($ch); - $sanitizedUrl = \DDTrace\Util\Normalizer::urlSanitize($info['url']); - $normalizedPath = \DDTrace\Util\Normalizer::uriNormalizeOutgoingPath($info['url']); - $host = Urls::hostname($sanitizedUrl); - $span->meta[Tag::NETWORK_DESTINATION_NAME] = $host; - unset($info['url']); + CurlIntegration::set_curl_attributes($span, \curl_getinfo($ch)); + }, + ]); - if (\DDTrace\Util\Runtime::getBoolIni("datadog.trace.http_client_split_by_domain")) { - $span->service = Urls::hostnameForTag($sanitizedUrl); + if (\PHP_MAJOR_VERSION > 5) { + $lastMh = [0, null]; + \DDTrace\install_hook('curl_multi_exec', function (HookData $hook) use ($integration, &$lastMh) { + if (\count($hook->args) >= 2) { + $data = null; + if (\PHP_MAJOR_VERSION > 7) { + $data = ObjectKVStore::get($hook->args[0], "span"); + } elseif ($lastMh[0] == (int)$hook->args[0]) { + $data = $lastMh[1]; + $lastMh = [0, null]; + } + if ($data) { + $hook->data = $data; + return; + } } - $span->resource = $normalizedPath; - - /* Special case the Datadog Standard Attributes - * See https://docs.datadoghq.com/logs/processing/attributes_naming_convention/ - */ - if (!array_key_exists(Tag::HTTP_URL, $span->meta)) { - $span->meta[Tag::HTTP_URL] = $sanitizedUrl; + $span = $hook->span(); + if (\count($hook->args) >= 2) { + \DDTrace\curl_multi_exec_get_request_spans($spans); + $hook->data = [$span, &$spans, true]; + if (\PHP_MAJOR_VERSION > 7) { + ObjectKVStore::put($hook->args[0], "span", [$span, &$spans]); + } } - if (\PHP_MAJOR_VERSION > 5) { - $span->peerServiceSources = HttpClientIntegrationHelper::PEER_SERVICE_SOURCES; + $span->name = 'curl_multi_exec'; + $span->resource = 'curl_multi_exec'; + $span->service = "curl"; + $span->type = Type::HTTP_CLIENT; + Integration::handleInternalSpanServiceName($span, CurlIntegration::NAME); + $span->meta[Tag::COMPONENT] = CurlIntegration::NAME; + $span->peerServiceSources = HttpClientIntegrationHelper::PEER_SERVICE_SOURCES; + }, function (HookData $hook) use ($integration, &$lastMh) { + if (empty($hook->data) || $hook->exception) { + return; } - addSpanDataTagFromCurlInfo($span, $info, Tag::HTTP_STATUS_CODE, 'http_code'); + $span = $hook->data[0]; + $spans = &$hook->data[1]; - addSpanDataTagFromCurlInfo($span, $info, 'network.client.ip', 'local_ip'); - addSpanDataTagFromCurlInfo($span, $info, 'network.client.port', 'local_port'); + if (!$spans) { + // Drop the span if nothing was handled here + if (\PHP_MAJOR_VERSION == 8) { + ObjectKVStore::put($hook->args[0], "span", null); + } + return false; + } - addSpanDataTagFromCurlInfo($span, $info, 'network.destination.ip', 'primary_ip'); - addSpanDataTagFromCurlInfo($span, $info, 'network.destination.port', 'primary_port'); + if ($spans && $spans[0][1]->name != "curl_exec") { + foreach ($spans as $requestSpan) { + list(, $requestSpan) = $requestSpan; + $integration->setup_curl_span($requestSpan); + } + } - addSpanDataTagFromCurlInfo($span, $info, 'network.bytes_read', 'size_download'); - addSpanDataTagFromCurlInfo($span, $info, 'network.bytes_written', 'size_upload'); + $saveSpans = $hook->args[1]; - // Add the rest to a 'curl.' object - foreach ($info as $key => $val) { - // Datadog doesn't support arrays in tags - if (\is_scalar($val) && $val !== '') { - // Datadog sets durations in nanoseconds - convert from seconds - if (\substr_compare($key, '_time', -5) === 0) { - $val *= 1000000000; + if (!$hook->args[1]) { + // finished + foreach ($spans as $requestSpan) { + list($ch, $requestSpan) = $requestSpan; + $info = curl_getinfo($ch); + if ($info["connect_time"] <= 0) { + $saveSpans = true; + if (!isset($error_trace)) { + $error_trace = \DDTrace\get_sanitized_exception_trace(new \Exception(), 1); + } + if (!isset($requestSpan->meta[Tag::ERROR_MSG])) { + $requestSpan->meta[Tag::ERROR_MSG] = "CURL request failure"; + } + $requestSpan->meta[Tag::ERROR_TYPE] = 'curl error'; + $requestSpan->meta[Tag::ERROR_STACK] = $error_trace; + } + CurlIntegration::set_curl_attributes($requestSpan, $info); + if (isset($info["total_time"])) { + $endTime = $info["total_time"] + $requestSpan->getStartTime() / 1e9; + \DDTrace\update_span_duration($requestSpan, $endTime); } - $span->meta["curl.{$key}"] = $val; } } - }, - ]); + + // If there's an error we retain it for a possible future curl_multi_info_read + if ($saveSpans) { + if (\PHP_MAJOR_VERSION == 7) { + $lastMh = [(int)$hook->args[0], [$span, &$spans]]; + } + } elseif (\PHP_MAJOR_VERSION == 8) { + ObjectKVStore::put($hook->args[0], "span", null); + } + + if (!isset($hook->data[2])) { + \DDTrace\update_span_duration($span); + } + + if ($hook->returned != CURLM_OK) { + if (!isset($error_trace)) { + $error_trace = \DDTrace\get_sanitized_exception_trace(new \Exception(), 1); + } + $requestSpan->meta[Tag::ERROR_MSG] = curl_multi_strerror($hook->returned); + $requestSpan->meta[Tag::ERROR_TYPE] = 'curl_multi error'; + $requestSpan->meta[Tag::ERROR_STACK] = $error_trace; + } + }); + + \DDTrace\install_hook('curl_multi_info_read', null, function (HookData $hook) use (&$lastMh) { + if (count($hook->args) < 1 || !isset($hook->returned["handle"])) { + return; + } + if (!isset($hook->returned["result"]) || $hook->returned["result"] == CURLE_OK) { + return; + } + + $handle = $hook->returned["handle"]; + + if (\PHP_MAJOR_VERSION > 7) { + $data = ObjectKVStore::get($hook->args[0], "span"); + } elseif ($lastMh[0] == (int)$hook->args[0]) { + $data = $lastMh[1]; + } + + list(, $spans) = $data; + foreach ($spans as $requestSpan) { + list($ch, $requestSpan) = $requestSpan; + if ($ch === $handle) { + $requestSpan->meta[Tag::ERROR_MSG] = curl_strerror($hook->returned["result"]); + } + } + }); + } return Integration::LOADED; } + + public function setup_curl_span($span) { + $span->name = $span->resource = 'curl_exec'; + $span->type = Type::HTTP_CLIENT; + $span->service = 'curl'; + Integration::handleInternalSpanServiceName($span, CurlIntegration::NAME); + $this->addTraceAnalyticsIfEnabled($span); + $span->meta[Tag::COMPONENT] = CurlIntegration::NAME; + $span->meta[Tag::SPAN_KIND] = Tag::SPAN_KIND_VALUE_CLIENT; + } + + public static function set_curl_attributes($span, $info) { + $sanitizedUrl = \DDTrace\Util\Normalizer::urlSanitize($info['url']); + $normalizedPath = \DDTrace\Util\Normalizer::uriNormalizeOutgoingPath($info['url']); + $host = Urls::hostname($sanitizedUrl); + $span->meta[Tag::NETWORK_DESTINATION_NAME] = $host; + unset($info['url']); + + if (\DDTrace\Util\Runtime::getBoolIni("datadog.trace.http_client_split_by_domain")) { + $span->service = Urls::hostnameForTag($sanitizedUrl); + } + + $span->resource = $normalizedPath; + + /* Special case the Datadog Standard Attributes + * See https://docs.datadoghq.com/logs/processing/attributes_naming_convention/ + */ + if (!array_key_exists(Tag::HTTP_URL, $span->meta)) { + $span->meta[Tag::HTTP_URL] = $sanitizedUrl; + } + + if (\PHP_MAJOR_VERSION > 5) { + $span->peerServiceSources = HttpClientIntegrationHelper::PEER_SERVICE_SOURCES; + } + + addSpanDataTagFromCurlInfo($span, $info, Tag::HTTP_STATUS_CODE, 'http_code'); + + addSpanDataTagFromCurlInfo($span, $info, 'network.client.ip', 'local_ip'); + addSpanDataTagFromCurlInfo($span, $info, 'network.client.port', 'local_port'); + + addSpanDataTagFromCurlInfo($span, $info, 'network.destination.ip', 'primary_ip'); + addSpanDataTagFromCurlInfo($span, $info, 'network.destination.port', 'primary_port'); + + addSpanDataTagFromCurlInfo($span, $info, 'network.bytes_read', 'size_download'); + addSpanDataTagFromCurlInfo($span, $info, 'network.bytes_written', 'size_upload'); + + // Add the rest to a 'curl.' object + foreach ($info as $key => $val) { + // Datadog doesn't support arrays in tags + if (\is_scalar($val) && $val !== '') { + // Datadog sets durations in nanoseconds - convert from seconds + if (\substr_compare($key, '_time', -5) === 0) { + $val *= 1000000000; + } + $span->meta["curl.{$key}"] = $val; + } + } + + return $info; + } } diff --git a/tests/Integrations/Curl/CurlIntegrationTest.php b/tests/Integrations/Curl/CurlIntegrationTest.php index aa8ca0ac2c1..f9b65d0d6a5 100644 --- a/tests/Integrations/Curl/CurlIntegrationTest.php +++ b/tests/Integrations/Curl/CurlIntegrationTest.php @@ -561,6 +561,65 @@ public function testHttpHeadersIsCorrectlySetAgain() }); } + public function testMulti() + { + $traces = $this->isolateTracer(function () { + $ch1 = curl_init(self::URL . '/status/200'); + curl_setopt($ch1, CURLOPT_RETURNTRANSFER, true); + $ch2 = curl_init(self::URL_NOT_EXISTS); + curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); + + $mh = curl_multi_init(); + curl_multi_add_handle($mh, $ch1); + curl_multi_add_handle($mh, $ch2); + + do { + $status = curl_multi_exec($mh, $active); + if ($active) { + curl_multi_select($mh); + } + while (false !== curl_multi_info_read($mh)); + } while ($active && $status == CURLM_OK); + + $response = curl_multi_getcontent($ch1); + $this->assertSame('', $response); + $response = curl_multi_getcontent($ch2); + $this->assertSame('', $response); + curl_multi_close($mh); + }); + + $this->assertFlameGraph($traces, [ + SpanAssertion::build('curl_multi_exec', 'curl', 'http', 'curl_multi_exec') + ->withExactTags([ + Tag::COMPONENT => 'curl', + ])->withChildren([ + SpanAssertion::build('curl_exec', 'curl', 'http', 'http://httpbin_integration/status/?') + ->setTraceAnalyticsCandidate() + ->withExactTags([ + 'http.url' => self::URL . '/status/200', + 'http.status_code' => '200', + 'span.kind' => 'client', + 'network.destination.name' => 'httpbin_integration', + Tag::COMPONENT => 'curl', + ]) + ->withExistingTagsNames(self::commonCurlInfoTags()) + ->skipTagsLike('/^curl\..*/'), + SpanAssertion::build('curl_exec', 'curl', 'http', 'http://__i_am_not_real__.invalid/') + ->setTraceAnalyticsCandidate() + ->withExactTags([ + 'http.url' => 'http://__i_am_not_real__.invalid/', + 'http.status_code' => '0', + 'span.kind' => 'client', + 'network.destination.name' => '__i_am_not_real__.invalid', + Tag::COMPONENT => 'curl', + ]) + ->withExistingTagsNames(self::commonCurlInfoTags()) + ->skipTagsLike('/^curl\..*/') + ->setError('curl error', "Couldn't resolve host name", true), + ]), + ]); + } + /** * @dataProvider dataProviderTestTraceAnalytics */ diff --git a/tests/Integrations/Guzzle/V5/GuzzleIntegrationTest.php b/tests/Integrations/Guzzle/V5/GuzzleIntegrationTest.php index 4ba7afab05a..fc9d8ff9275 100644 --- a/tests/Integrations/Guzzle/V5/GuzzleIntegrationTest.php +++ b/tests/Integrations/Guzzle/V5/GuzzleIntegrationTest.php @@ -256,8 +256,8 @@ function ($execute) use (&$headers1, &$headers2) { * without an event loop. * @see https://github.com/guzzle/guzzle/issues/1439 */ - self::assertDistributedTracingSpan($traces[0][2], $headers1['headers']); - self::assertDistributedTracingSpan($traces[0][1], $headers2['headers']); + self::assertDistributedTracingSpan($traces[0][6], $headers1['headers']); + self::assertDistributedTracingSpan($traces[0][3], $headers2['headers']); } private static function assertDistributedTracingSpan($span, $headers) diff --git a/tests/Integrations/Guzzle/V6/GuzzleIntegrationTest.php b/tests/Integrations/Guzzle/V6/GuzzleIntegrationTest.php index dd4e7f81e94..dfbabade758 100644 --- a/tests/Integrations/Guzzle/V6/GuzzleIntegrationTest.php +++ b/tests/Integrations/Guzzle/V6/GuzzleIntegrationTest.php @@ -252,10 +252,19 @@ function ($execute) use (&$found) { * $curl->tick() is called. */ $rootSpan = $traces[0][0]; - self::assertSame( - $rootSpan['span_id'], - $data['headers']['X-Datadog-Parent-Id'] - ); + try { + $parentSpan = $traces[0][3]; + self::assertSame( + $parentSpan['span_id'], + $data['headers']['X-Datadog-Parent-Id'] + ); + } catch (\Throwable $t) { + $parentSpan = $traces[0][2]; + self::assertSame( + $parentSpan['span_id'], + $data['headers']['X-Datadog-Parent-Id'] + ); + } self::assertSame( $rootSpan['trace_id'], $data['headers']['X-Datadog-Trace-Id'] diff --git a/tests/internal-api-stress-test.php b/tests/internal-api-stress-test.php index 38639039cb5..3dbf459d98c 100644 --- a/tests/internal-api-stress-test.php +++ b/tests/internal-api-stress-test.php @@ -147,7 +147,8 @@ function runOneIteration() $functions = array_filter($ext->getFunctions(), function ($f) { return $f->name != "dd_trace_internal_fn" && !strpos($f->name, "Testing") - && $f->name != "dd_trace_disable_in_request"; + && $f->name != "dd_trace_disable_in_request" + && (PHP_VERSION_ID >= 70100 || $f->name != 'DDTrace\curl_multi_exec_get_request_spans'); }); $props = array_filter( diff --git a/zend_abstract_interface/exceptions/exceptions.c b/zend_abstract_interface/exceptions/exceptions.c index 548b37aab80..bc6d437d05d 100644 --- a/zend_abstract_interface/exceptions/exceptions.c +++ b/zend_abstract_interface/exceptions/exceptions.c @@ -41,7 +41,7 @@ zend_string *zai_exception_message(zend_object *ex) { /* Modeled after Exception::getTraceAsString: * @see https://heap.space/xref/PHP-8.0/Zend/zend_exceptions.c#getTraceAsString */ -zend_string *zai_get_trace_without_args(zend_array *trace) { +zend_string *zai_get_trace_without_args_skip_frames(zend_array *trace, int skip) { if (!trace) { // should never happen; TODO: fail in CI return zend_string_init_interned(ZEND_STRL("[broken trace]"), 1); @@ -51,6 +51,10 @@ zend_string *zai_get_trace_without_args(zend_array *trace) { smart_str str = {0}; uint32_t num = 0; ZEND_HASH_FOREACH_VAL(trace, frame) { + if (skip-- > 0) { + continue; + } + smart_str_appendc(&str, '#'); smart_str_append_long(&str, num++); smart_str_appendc(&str, ' '); @@ -118,7 +122,11 @@ zend_string *zai_get_trace_without_args(zend_array *trace) { return str.s; } -zend_string *zai_get_trace_without_args_from_exception(zend_object *ex) { +zend_string *zai_get_trace_without_args(zend_array *trace) { + return zai_get_trace_without_args_skip_frames(trace, 0); +} + +zend_string *zai_get_trace_without_args_from_exception_skip_frames(zend_object *ex, int skip) { if (!ex) { return ZSTR_EMPTY_ALLOC(); // should never happen; TODO: fail in CI } @@ -129,5 +137,9 @@ zend_string *zai_get_trace_without_args_from_exception(zend_object *ex) { return ZSTR_EMPTY_ALLOC(); // should never happen in PHP 8 as the property is typed and always initialized } - return zai_get_trace_without_args(Z_ARR_P(trace)); + return zai_get_trace_without_args_skip_frames(Z_ARR_P(trace), skip); } + +zend_string *zai_get_trace_without_args_from_exception(zend_object *ex) { + return zai_get_trace_without_args_from_exception_skip_frames(ex, 0); +} \ No newline at end of file diff --git a/zend_abstract_interface/exceptions/exceptions.h b/zend_abstract_interface/exceptions/exceptions.h index 24c472ed07e..bcbe6d75207 100644 --- a/zend_abstract_interface/exceptions/exceptions.h +++ b/zend_abstract_interface/exceptions/exceptions.h @@ -53,5 +53,7 @@ static inline zval *zai_exception_read_property(zend_object *object, zend_string zend_string *zai_exception_message(zend_object *ex); // fallback string if message invalid zend_string *zai_get_trace_without_args(zend_array *trace); zend_string *zai_get_trace_without_args_from_exception(zend_object *ex); +zend_string *zai_get_trace_without_args_skip_frames(zend_array *trace, int skip); +zend_string *zai_get_trace_without_args_from_exception_skip_frames(zend_object *ex, int skip); #endif // ZAI_EXCEPTIONS_H From ab347f86a5d0dd5c793d26882008649261758dec Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Mon, 20 Nov 2023 16:13:31 +0100 Subject: [PATCH 12/16] Fix possible double-free with hooks on internal methods inherited by user classes (#2372) Doing the due diligence whether I can reuse nNumOfElements in the HashTable, to increase and decrease it, so that all checks for zend_hash_num_elements() == 0 only are true when neither internal duplicates exist, nor actual hooks exist, I missed the tiny detail, that, while all direct usages of nNumOfElements were safe, there was an usage of nNumOfElements in the HT_IS_WITHOUT_HOLES() macro, checking whether nNumOfElements == nNumUsed. Thus, when there were two hooks, one later removed, and one class inheriting the internal method, nNumOfElements happened to be equal to nNumUsed in shutdown. During hash destruction the smart Zend code would check this and unconditionally iterate over all elements of the array, skipping IS_UNDEF checks. Eventually leading to a double free. As a saving grace, this double free generally happened very late in the shutdown sequence, so the memory corruption would usually not surface... Signed-off-by: Bob Weinand --- zend_abstract_interface/hook/hook.c | 31 +++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/zend_abstract_interface/hook/hook.c b/zend_abstract_interface/hook/hook.c index c5b9adc114e..a3d5ddf82a3 100644 --- a/zend_abstract_interface/hook/hook.c +++ b/zend_abstract_interface/hook/hook.c @@ -28,6 +28,7 @@ typedef struct _zai_hooks_entry { // But to ensure consistency between existence of hooks and Closure actually being hooked, we need to keep track of the run_time_cache, so that we eventually may remove the hook again. #if PHP_VERSION_ID >= 80200 struct _zai_hooks_entry *internal_duplicate; + int internal_duplicate_count; void **run_time_cache; // used only if Closure #else void ***run_time_cache; // used only if Closure @@ -208,7 +209,7 @@ static void zai_hook_entries_remove_resolved(zend_ulong install_address) { #if PHP_VERSION_ID >= 80200 if (hooks->internal_duplicate) { // We refcount parents of internal function duplicates, remove here again if necessary - if (--hooks->internal_duplicate->hooks.nNumOfElements == 0) { + if (--hooks->internal_duplicate->internal_duplicate_count == 0 && zend_hash_num_elements(&hooks->internal_duplicate->hooks) == 0) { zai_hook_entries_remove_resolved(zai_hook_install_address(hooks->internal_duplicate->resolved)); } } @@ -371,6 +372,9 @@ static zai_hooks_entry *zai_hook_alloc_hooks_entry(void) { hooks->resolved = NULL; #if PHP_VERSION_ID >= 80000 hooks->run_time_cache = NULL; +#endif +#if PHP_VERSION_ID >= 80200 + hooks->internal_duplicate_count = 0; #endif zend_hash_init(&hooks->hooks, 8, NULL, zai_hook_destroy, 0); hooks->hooks.nNextFreeElement = ZEND_LONG_MAX >> 1; @@ -405,12 +409,12 @@ static inline void zai_hook_handle_internal_duplicate_function(zai_hooks_entry * bool is_internal_duplicate = !ZEND_USER_CODE(function->type) && (function->common.fn_flags & ZEND_ACC_ARENA_ALLOCATED) && function->common.scope != ce; if (is_internal_duplicate) { // Hence we need to ensure that each top-level internal function is responsible for its run-time cache. - // We achieve that by refcounting on top of the anyway checked nNumOfElements. + // We achieve that by refcounting it. zend_string *lcname = zend_string_tolower(function->common.function_name); zend_function *original_function = zend_hash_find_ptr(&ce->parent->function_table, lcname); zend_string_release(lcname); hooks->internal_duplicate = zai_hook_resolved_ensure_hooks_entry(original_function, ce->parent); - ++hooks->internal_duplicate->hooks.nNumOfElements; + ++hooks->internal_duplicate->internal_duplicate_count; } else { hooks->internal_duplicate = NULL; } @@ -824,7 +828,12 @@ static inline void zai_hook_remove_shared_hook(zend_function *func, zend_ulong h if (hooks && hooks != base_hooks) { zend_hash_index_del(&hooks->hooks, hook_id); if (zend_hash_num_elements(&hooks->hooks) == 0) { - zai_hook_entries_remove_resolved(addr); +#if PHP_VERSION_ID >= 80200 + if (hooks->internal_duplicate_count == 0) +#endif + { + zai_hook_entries_remove_resolved(addr); + } } } } @@ -1045,7 +1054,12 @@ void zai_hook_finish(zend_execute_data *ex, zval *rv, zai_hook_memory_t *memory) } zend_hash_index_del(&hooks->hooks, (zend_ulong) -hook->id); if (zend_hash_num_elements(&hooks->hooks) == 0) { - zai_hook_entries_remove_resolved(address); +#if PHP_VERSION_ID >= 80200 + if (hooks->internal_duplicate_count == 0) +#endif + { + zai_hook_entries_remove_resolved(address); + } } } } @@ -1310,7 +1324,12 @@ bool zai_hook_remove_resolved(zai_install_address function_address, zend_long in } if (zend_hash_num_elements(&hooks->hooks) == 0) { - zai_hook_entries_remove_resolved(function_address); +#if PHP_VERSION_ID >= 80200 + if (hooks->internal_duplicate_count == 0) +#endif + { + zai_hook_entries_remove_resolved(function_address); + } } return true; From f49454a143ca4416e884b8d643e878a894e27ae9 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Mon, 20 Nov 2023 16:21:34 +0100 Subject: [PATCH 13/16] Fix crash with functions with no run_time_cache yet (#2373) Signed-off-by: Bob Weinand --- zend_abstract_interface/interceptor/php8/interceptor.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zend_abstract_interface/interceptor/php8/interceptor.c b/zend_abstract_interface/interceptor/php8/interceptor.c index e1e85d6a5f4..4e9f8096159 100644 --- a/zend_abstract_interface/interceptor/php8/interceptor.c +++ b/zend_abstract_interface/interceptor/php8/interceptor.c @@ -523,9 +523,9 @@ static void zai_interceptor_observer_placeholder_handler(zend_execute_data *exec void zai_interceptor_replace_observer(zend_function *func, bool remove) { #if PHP_VERSION_ID < 80200 - if (!RUN_TIME_CACHE(&func->op_array) || (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) != 0) { + if (!ZEND_MAP_PTR(func->op_array.run_time_cache) || !RUN_TIME_CACHE(&func->op_array) || (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) != 0) { #else - if (!RUN_TIME_CACHE(&func->common) || !ZEND_OBSERVER_DATA(func) || (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) != 0) { + if (!ZEND_MAP_PTR(func->common.run_time_cache) || !RUN_TIME_CACHE(&func->common) || !ZEND_OBSERVER_DATA(func) || (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) != 0) { #endif return; } @@ -590,7 +590,7 @@ void zai_interceptor_replace_observer(zend_function *func, bool remove) { } #else void zai_interceptor_replace_observer(zend_function *func, bool remove) { - if (!RUN_TIME_CACHE(&func->common) || !ZEND_OBSERVER_DATA(func) || (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) != 0) { + if (!ZEND_MAP_PTR(func->op_array.run_time_cache) || !RUN_TIME_CACHE(&func->common) || !ZEND_OBSERVER_DATA(func) || (func->common.fn_flags & ZEND_ACC_HEAP_RT_CACHE) != 0) { return; } From 002a5c39eddf12441320500390f337983aa6f8fe Mon Sep 17 00:00:00 2001 From: Gustavo Lopes Date: Mon, 20 Nov 2023 15:23:08 +0000 Subject: [PATCH 14/16] Workaround php bug #81634 in PHP 8.0-8.1.0 (#2353) --- .../extension/extension_no_static_tls.phpt | 26 ++++++++++++ ext/compat_string.c | 1 + ext/compatibility.h | 1 + ext/ddtrace.c | 4 +- ext/ddtrace.h | 10 ++++- ext/handlers_api.h | 11 +++++ ext/hook/uhook.c | 2 +- ext/weakrefs.c | 2 +- tests/ext/extension_no_static_tls.phpt | 42 +++++++++++++++++++ zend_abstract_interface/config/config_ini.c | 1 + .../config/config_runtime.c | 1 + zend_abstract_interface/env/env.c | 1 + .../exceptions/exceptions.c | 1 + zend_abstract_interface/headers/headers.c | 1 + zend_abstract_interface/hook/hook.c | 1 + .../interceptor/php8/interceptor.c | 1 + .../interceptor/php8/resolver_pre-8_2.c | 1 + .../jit_utils/jit_blacklist.c | 1 + .../sandbox/php8/sandbox.c | 1 + zend_abstract_interface/sandbox/sandbox.h | 1 + zend_abstract_interface/symbols/call.c | 1 + zend_abstract_interface/symbols/lookup.c | 1 + zend_abstract_interface/tsrmls_cache.h | 14 +++++++ .../uri_normalization/uri_normalization.c | 1 + 24 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 appsec/tests/extension/extension_no_static_tls.phpt create mode 100644 tests/ext/extension_no_static_tls.phpt create mode 100644 zend_abstract_interface/tsrmls_cache.h diff --git a/appsec/tests/extension/extension_no_static_tls.phpt b/appsec/tests/extension/extension_no_static_tls.phpt new file mode 100644 index 00000000000..4b7ebea3741 --- /dev/null +++ b/appsec/tests/extension/extension_no_static_tls.phpt @@ -0,0 +1,26 @@ +--TEST-- +Extension is not compiled with STATIC_TLS +--SKIPIF-- + +--FILE-- + diff --git a/ext/compatibility.h b/ext/compatibility.h index af93acb357c..1461f61601f 100644 --- a/ext/compatibility.h +++ b/ext/compatibility.h @@ -3,6 +3,7 @@ #include #include +#include #include "ext/standard/base64.h" diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 4f983679748..eff1e090178 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -2,6 +2,7 @@ #ifdef HAVE_CONFIG_H #include "config.h" #endif +#include "ddtrace.h" #include #include #include @@ -40,7 +41,6 @@ #include "config/config.h" #include "configuration.h" #include "ddshared.h" -#include "ddtrace.h" #include "ddtrace_string.h" #include "dogstatsd_client.h" #include "engine_hooks.h" @@ -110,7 +110,7 @@ ZEND_DECLARE_MODULE_GLOBALS(ddtrace) #ifdef COMPILE_DL_DDTRACE ZEND_GET_MODULE(ddtrace) #ifdef ZTS -ZEND_TSRMLS_CACHE_DEFINE(); +TSRM_TLS void *TSRMLS_CACHE = NULL; #endif #endif diff --git a/ext/ddtrace.h b/ext/ddtrace.h index 48150a23d1e..d7072328f96 100644 --- a/ext/ddtrace.h +++ b/ext/ddtrace.h @@ -107,9 +107,15 @@ ZEND_END_MODULE_GLOBALS(ddtrace) // clang-format on #ifdef ZTS -#define DDTRACE_G(v) TSRMG(ddtrace_globals_id, zend_ddtrace_globals *, v) +# if defined(__has_attribute) && __has_attribute(tls_model) +# define ATTR_TLS_LOCAL_DYNAMIC __attribute__((tls_model("local-dynamic"))) +# else +# define ATTR_TLS_LOCAL_DYNAMIC +# endif +extern __thread void *ATTR_TLS_LOCAL_DYNAMIC TSRMLS_CACHE; +# define DDTRACE_G(v) TSRMG(ddtrace_globals_id, zend_ddtrace_globals *, v) #else -#define DDTRACE_G(v) (ddtrace_globals.v) +# define DDTRACE_G(v) (ddtrace_globals.v) #endif #define PHP_DDTRACE_EXTNAME "ddtrace" diff --git a/ext/handlers_api.h b/ext/handlers_api.h index 470824cc4e1..353183bef1d 100644 --- a/ext/handlers_api.h +++ b/ext/handlers_api.h @@ -5,6 +5,17 @@ #include +#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80101 && defined(ZTS) + +# if defined(__has_attribute) && __has_attribute(tls_model) +# define ATTR_TLS_LOCAL_DYNAMIC __attribute__((tls_model("local-dynamic"))) +# else +# define ATTR_TLS_LOCAL_DYNAMIC +# endif + +extern __thread void *ATTR_TLS_LOCAL_DYNAMIC TSRMLS_CACHE; +#endif + typedef struct datadog_php_zif_handler_s { const char *name; size_t name_len; diff --git a/ext/hook/uhook.c b/ext/hook/uhook.c index c9f9556d5c2..9a07c1c2f28 100644 --- a/ext/hook/uhook.c +++ b/ext/hook/uhook.c @@ -1,3 +1,4 @@ +#include "../ddtrace.h" #include #include @@ -14,7 +15,6 @@ #include #include "uhook.h" -#include "../ddtrace.h" #include #include diff --git a/ext/weakrefs.c b/ext/weakrefs.c index 811e40292f5..459527c9c87 100644 --- a/ext/weakrefs.c +++ b/ext/weakrefs.c @@ -1,4 +1,4 @@ -#include +#include "ddtrace.h" #include "compatibility.h" diff --git a/tests/ext/extension_no_static_tls.phpt b/tests/ext/extension_no_static_tls.phpt new file mode 100644 index 00000000000..6ae89ca2fd4 --- /dev/null +++ b/tests/ext/extension_no_static_tls.phpt @@ -0,0 +1,42 @@ +--TEST-- +Extension is not compiled with STATIC_TLS +--SKIPIF-- + +--FILE-- + diff --git a/zend_abstract_interface/config/config_runtime.c b/zend_abstract_interface/config/config_runtime.c index 15652230d16..0b480942681 100644 --- a/zend_abstract_interface/config/config_runtime.c +++ b/zend_abstract_interface/config/config_runtime.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include
#include "config.h" diff --git a/zend_abstract_interface/env/env.c b/zend_abstract_interface/env/env.c index 6f5e414b03d..28a65a87a9e 100644 --- a/zend_abstract_interface/env/env.c +++ b/zend_abstract_interface/env/env.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include
#include
#include diff --git a/zend_abstract_interface/exceptions/exceptions.c b/zend_abstract_interface/exceptions/exceptions.c index bc6d437d05d..166829e6784 100644 --- a/zend_abstract_interface/exceptions/exceptions.c +++ b/zend_abstract_interface/exceptions/exceptions.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include "exceptions.h" #include diff --git a/zend_abstract_interface/headers/headers.c b/zend_abstract_interface/headers/headers.c index 70e8e58bdfa..4e79715b415 100644 --- a/zend_abstract_interface/headers/headers.c +++ b/zend_abstract_interface/headers/headers.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include "headers.h" #include diff --git a/zend_abstract_interface/hook/hook.c b/zend_abstract_interface/hook/hook.c index a3d5ddf82a3..0b064cd6ead 100644 --- a/zend_abstract_interface/hook/hook.c +++ b/zend_abstract_interface/hook/hook.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include #include diff --git a/zend_abstract_interface/interceptor/php8/interceptor.c b/zend_abstract_interface/interceptor/php8/interceptor.c index 4e9f8096159..1d7aa8f60a0 100644 --- a/zend_abstract_interface/interceptor/php8/interceptor.c +++ b/zend_abstract_interface/interceptor/php8/interceptor.c @@ -1,3 +1,4 @@ +#include "../../tsrmls_cache.h" #include #include #include diff --git a/zend_abstract_interface/interceptor/php8/resolver_pre-8_2.c b/zend_abstract_interface/interceptor/php8/resolver_pre-8_2.c index 4b163a5e32e..4491947356e 100644 --- a/zend_abstract_interface/interceptor/php8/resolver_pre-8_2.c +++ b/zend_abstract_interface/interceptor/php8/resolver_pre-8_2.c @@ -1,3 +1,4 @@ +#include "../../tsrmls_cache.h" #include #include #include diff --git a/zend_abstract_interface/jit_utils/jit_blacklist.c b/zend_abstract_interface/jit_utils/jit_blacklist.c index 4c1833884ae..7f6ded56cdb 100644 --- a/zend_abstract_interface/jit_utils/jit_blacklist.c +++ b/zend_abstract_interface/jit_utils/jit_blacklist.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include "jit_blacklist.h" #include "zend_extensions.h" #include diff --git a/zend_abstract_interface/sandbox/php8/sandbox.c b/zend_abstract_interface/sandbox/php8/sandbox.c index 5b605471c44..92fc0589eac 100644 --- a/zend_abstract_interface/sandbox/php8/sandbox.c +++ b/zend_abstract_interface/sandbox/php8/sandbox.c @@ -1,3 +1,4 @@ +#include "../../tsrmls_cache.h" #include "../sandbox.h" long zai_sandbox_active = 0; diff --git a/zend_abstract_interface/sandbox/sandbox.h b/zend_abstract_interface/sandbox/sandbox.h index 571bd7fb0f2..65a03e3a4c4 100644 --- a/zend_abstract_interface/sandbox/sandbox.h +++ b/zend_abstract_interface/sandbox/sandbox.h @@ -1,6 +1,7 @@ #ifndef ZAI_SANDBOX_H #define ZAI_SANDBOX_H +#include "../tsrmls_cache.h" #include
#include diff --git a/zend_abstract_interface/symbols/call.c b/zend_abstract_interface/symbols/call.c index c61551712e8..9155a1390c1 100644 --- a/zend_abstract_interface/symbols/call.c +++ b/zend_abstract_interface/symbols/call.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include "symbols.h" #if PHP_VERSION_ID >= 80000 diff --git a/zend_abstract_interface/symbols/lookup.c b/zend_abstract_interface/symbols/lookup.c index e11b69ba541..de247ed840c 100644 --- a/zend_abstract_interface/symbols/lookup.c +++ b/zend_abstract_interface/symbols/lookup.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include #include "symbols.h" diff --git a/zend_abstract_interface/tsrmls_cache.h b/zend_abstract_interface/tsrmls_cache.h new file mode 100644 index 00000000000..a703909f58c --- /dev/null +++ b/zend_abstract_interface/tsrmls_cache.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80101 && defined(ZTS) + +# if defined(__has_attribute) && __has_attribute(tls_model) +# define ATTR_TLS_LOCAL_DYNAMIC __attribute__((tls_model("local-dynamic"))) +# else +# define ATTR_TLS_LOCAL_DYNAMIC +# endif + +extern __thread void *ATTR_TLS_LOCAL_DYNAMIC TSRMLS_CACHE; +#endif diff --git a/zend_abstract_interface/uri_normalization/uri_normalization.c b/zend_abstract_interface/uri_normalization/uri_normalization.c index 182bd81fd28..113ebffd6a7 100644 --- a/zend_abstract_interface/uri_normalization/uri_normalization.c +++ b/zend_abstract_interface/uri_normalization/uri_normalization.c @@ -1,3 +1,4 @@ +#include "../tsrmls_cache.h" #include "uri_normalization.h" #include "../sandbox/sandbox.h" From 5c73ee9a6b6dac869d617edf580938326a7ae834 Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Mon, 20 Nov 2023 18:06:33 +0100 Subject: [PATCH 15/16] Allow everyone to modify release files without waiting for approval from all teams (#2375) --- .github/CODEOWNERS | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e69a29eba89..c36779f7749 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,16 @@ # Default owners * @DataDog/apm-php -Cargo.lock @DataDog/apm-php @DataDog/profiling-php # Profiling team /profiling/ @DataDog/profiling-php # ASM Team /appsec/ @DataDog/asm-php + +# Release files +Cargo.lock @DataDog/apm-php @DataDog/profiling-php @Datadog/asm-php +package.xml @DataDog/apm-php @DataDog/profiling-php @Datadog/asm-php +profiling/Cargo.toml @DataDog/apm-php @DataDog/profiling-php @Datadog/asm-php +src/DDTrace/Tracer.php @DataDog/apm-php @DataDog/profiling-php @Datadog/asm-php +appsec/CMakeLists.txt @DataDog/apm-php @DataDog/profiling-php @Datadog/asm-php +ext/version.h @DataDog/apm-php @DataDog/profiling-php @Datadog/asm-php From dc23b0c7f826fb832e8d69e350524de5a07002c2 Mon Sep 17 00:00:00 2001 From: Alexandre Choura <42672104+PROFeNoM@users.noreply.github.com> Date: Mon, 20 Nov 2023 18:09:04 +0100 Subject: [PATCH 16/16] Add release notes for 0.94.0 (#2374) * Add release notes for 0.94.0 * Add release notes for 0.94.0 * Add release notes for 0.94.0 * Change release notes for 0.94.0 * Change release notes Co-authored-by: Pierre Bonet * Change release notes for 0.94.0 --------- Co-authored-by: Pierre Bonet --- Cargo.lock | 2 +- appsec/CMakeLists.txt | 2 +- ext/version.h | 2 +- package.xml | 56 +++++++++++++++++------------------------- profiling/Cargo.toml | 2 +- src/DDTrace/Tracer.php | 2 +- 6 files changed, 28 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e8496ef7c9..982fe7ed451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,7 +678,7 @@ dependencies = [ [[package]] name = "datadog-php-profiling" -version = "0.93.0" +version = "0.94.0" dependencies = [ "ahash 0.8.3", "anyhow", diff --git a/appsec/CMakeLists.txt b/appsec/CMakeLists.txt index c1861681a79..0b19037650c 100644 --- a/appsec/CMakeLists.txt +++ b/appsec/CMakeLists.txt @@ -11,7 +11,7 @@ HunterGate( configure_file(${CMAKE_CURRENT_SOURCE_DIR}/hunter-cache.id.in ${CMAKE_CURRENT_SOURCE_DIR}/hunter-cache.id) -project(ddappsec VERSION 0.93.0) +project(ddappsec VERSION 0.94.0) include(CheckCXXCompilerFlag) diff --git a/ext/version.h b/ext/version.h index 57e5adf5858..5e93e716c79 100644 --- a/ext/version.h +++ b/ext/version.h @@ -1,4 +1,4 @@ #ifndef PHP_DDTRACE_VERSION // Must begin with a number for Debian packaging requirements -#define PHP_DDTRACE_VERSION "0.93.0" +#define PHP_DDTRACE_VERSION "0.94.0" #endif diff --git a/package.xml b/package.xml index 71f548d7b8a..1892154b29c 100644 --- a/package.xml +++ b/package.xml @@ -62,54 +62,44 @@ BSD 3-Clause diff --git a/profiling/Cargo.toml b/profiling/Cargo.toml index 61d2aaeb6b7..90e806c3bc8 100644 --- a/profiling/Cargo.toml +++ b/profiling/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "datadog-php-profiling" -version = "0.93.0" +version = "0.94.0" edition = "2021" license = "Apache-2.0" rust-version = "1.64" diff --git a/src/DDTrace/Tracer.php b/src/DDTrace/Tracer.php index 01af1c97c1a..9d4c70268d8 100644 --- a/src/DDTrace/Tracer.php +++ b/src/DDTrace/Tracer.php @@ -24,7 +24,7 @@ final class Tracer implements TracerInterface * Must begin with a number for Debian packaging requirements * Must use single-quotes for packaging script to work */ - const VERSION = '0.93.0'; + const VERSION = '0.94.0'; /** * @var Span[][]