diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 27edede5e97..0fbae7329ca 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -227,6 +227,14 @@ ddog_MaybeError ddog_sidecar_session_set_config(struct ddog_SidecarTransport **t ddog_MaybeError ddog_sidecar_session_set_process_tags(struct ddog_SidecarTransport **transport, const struct ddog_Vec_Tag *process_tags); +/** + * Records the default service name for the session. Empty = user-defined + * (sidecar emits `svc.user:true`); non-empty = auto-resolved with that name + * (sidecar emits `svc.auto:`). + */ +ddog_MaybeError ddog_sidecar_session_set_default_service_name(struct ddog_SidecarTransport **transport, + ddog_CharSlice default_service_name); + /** * Enqueues a telemetry log action to be processed internally. * Non-blocking. Logs might be dropped if the internal queue is full. diff --git a/ext/sidecar.c b/ext/sidecar.c index a151d6fa2d6..e30016fcf91 100644 --- a/ext/sidecar.c +++ b/ext/sidecar.c @@ -153,6 +153,33 @@ void datadog_sidecar_update_process_tags(void) { } ddog_sidecar_session_set_process_tags(&DATADOG_G(sidecar), process_tags); + + // Session-bound, process-stable: the tracer's auto-resolved default name. + zend_string *default_svc = datadog_default_service_name(); + if (default_svc) { + const char *normalized = ddog_normalize_process_tag_value((ddog_CharSlice){ + .ptr = ZSTR_VAL(default_svc), .len = ZSTR_LEN(default_svc) + }); + if (normalized) { + datadog_ffi_try("Failed updating sidecar default service name", + ddog_sidecar_session_set_default_service_name(&DATADOG_G(sidecar), + (ddog_CharSlice){ .ptr = normalized, .len = strlen(normalized) })); + ddog_free_normalized_tag_value(normalized); + } + zend_string_release(default_svc); + } + + datadog_sidecar_refresh_user_service_defined(); +} + +void datadog_sidecar_refresh_user_service_defined(void) { + if (!DATADOG_G(sidecar)) { + return; + } + zend_string *dd_service = get_DD_SERVICE(); + bool is_defined = dd_service && ZSTR_LEN(dd_service) > 0; + datadog_ffi_try("Failed updating sidecar user-service-defined flag", + ddog_sidecar_session_set_user_service_defined(&DATADOG_G(sidecar), is_defined)); } static void datadog_sidecar_setup_thread_mode(void); @@ -799,6 +826,10 @@ void datadog_sidecar_rinit(void) { } ddtrace_sidecar_submit_span_data_direct_defaults(&DATADOG_G(sidecar), NULL); + + if (get_global_DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED()) { + datadog_sidecar_refresh_user_service_defined(); + } } void datadog_sidecar_rshutdown(void) { diff --git a/ext/sidecar.h b/ext/sidecar.h index ae0a79c5c5b..ba5eb1d6601 100644 --- a/ext/sidecar.h +++ b/ext/sidecar.h @@ -47,6 +47,7 @@ void datadog_sidecar_handle_fork(void); bool datadog_sidecar_should_enable(ddog_RemoteConfigFlags *flags); void datadog_sidecar_ensure_active(void); void datadog_sidecar_update_process_tags(void); +void datadog_sidecar_refresh_user_service_defined(void); void datadog_sidecar_finalize(bool clear_id); void datadog_sidecar_shutdown(void); void datadog_force_new_instance_id(void); diff --git a/libdatadog b/libdatadog index 952c2ef75cd..ac68fdaf324 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit 952c2ef75cdf7b2895d7152100ea61c12ccf4439 +Subproject commit ac68fdaf32425645aff9f53873deab6a00ab9c41 diff --git a/tests/Frameworks/Custom/Version_Autoloaded/src/SetServiceController.php b/tests/Frameworks/Custom/Version_Autoloaded/src/SetServiceController.php new file mode 100644 index 00000000000..1a7998bfa2b --- /dev/null +++ b/tests/Frameworks/Custom/Version_Autoloaded/src/SetServiceController.php @@ -0,0 +1,13 @@ +addRoute('GET', '/simple', '\App\SimpleController'); + $r->addRoute('GET', '/set_service', '\App\SetServiceController'); $r->addRoute('GET', '/simple_view', '\App\SimpleViewController'); $r->addRoute('GET', '/error', '\App\ErrorController'); $r->addRoute('GET', '/telemetry', '\App\TelemetryFlushController'); diff --git a/tests/Integrations/Custom/Autoloaded/ProcessTagsWebTest.php b/tests/Integrations/Custom/Autoloaded/ProcessTagsWebTest.php index 406fd9409ce..94ea00cc59a 100644 --- a/tests/Integrations/Custom/Autoloaded/ProcessTagsWebTest.php +++ b/tests/Integrations/Custom/Autoloaded/ProcessTagsWebTest.php @@ -65,4 +65,28 @@ public function testProcessTagsEnabledForWebSapi() ); $this->assertEquals($tags['entrypoint.type'], 'script'); } + + /** + * Proves the per-span svc.* design has no static-state leak between + * requests served by the same FPM worker: request 1 calls + * ini_set('datadog.service', ...) and must report svc.user:true; request 2 + * reuses the same worker but does not override the service, and must + * report svc.auto: (not the leaked svc.user from request 1). + */ + public function testSvcTagDoesNotLeakBetweenRequests() + { + $tracesUser = $this->tracesFromWebRequest(function () { + return $this->call(new RequestSpec(__FUNCTION__ . '_user', 'GET', '/set_service', [])); + }); + $userTags = $tracesUser[0][0]['meta']['_dd.tags.process']; + $this->assertStringContainsString('svc.user:true', $userTags); + $this->assertStringNotContainsString('svc.auto:', $userTags); + + $tracesAuto = $this->tracesFromWebRequest(function () { + return $this->call(new RequestSpec(__FUNCTION__ . '_auto', 'GET', '/simple', [])); + }); + $autoTags = $tracesAuto[0][0]['meta']['_dd.tags.process']; + $this->assertStringNotContainsString('svc.user', $autoTags); + $this->assertStringContainsString('svc.auto:', $autoTags); + } } diff --git a/tests/ext/process_tags.phpt b/tests/ext/process_tags.phpt index 7e532482c0f..9697aa1643c 100644 --- a/tests/ext/process_tags.phpt +++ b/tests/ext/process_tags.phpt @@ -47,6 +47,6 @@ if (isset($spans[1]['meta']['_dd.process_tags'])) { ?> --EXPECTF-- Process tags present in root span: YES -Process tags: entrypoint.basedir:ext,entrypoint.name:process_tags,entrypoint.type:script,entrypoint.workdir:%s,runtime.sapi:cli +Process tags: entrypoint.basedir:ext,entrypoint.name:process_tags,entrypoint.type:script,entrypoint.workdir:%s,runtime.sapi:cli,svc.auto:process_tags.php Keys sorted: YES Process tags present in child span: NO diff --git a/tests/ext/svc_auto_tag_cli.phpt b/tests/ext/svc_auto_tag_cli.phpt new file mode 100644 index 00000000000..a46ee44b268 --- /dev/null +++ b/tests/ext/svc_auto_tag_cli.phpt @@ -0,0 +1,23 @@ +--TEST-- +Process tags include svc.auto: when DD_SERVICE is unset (CLI) +--ENV-- +DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=1 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--FILE-- +name = 'op'; +\DDTrace\close_span(); + +$spans = dd_trace_serialize_closed_spans(); +$processTags = $spans[0]['meta']['_dd.tags.process']; + +echo "has svc.user: " . (strpos($processTags, 'svc.user') !== false ? 'YES' : 'NO') . "\n"; +echo "has svc.auto: " . (strpos($processTags, 'svc.auto:') !== false ? 'YES' : 'NO') . "\n"; +echo "auto value matches script: " . (strpos($processTags, 'svc.auto:svc_auto_tag_cli.php') !== false ? 'YES' : 'NO') . "\n"; +?> +--EXPECT-- +has svc.user: NO +has svc.auto: YES +auto value matches script: YES diff --git a/tests/ext/svc_auto_tag_otel.phpt b/tests/ext/svc_auto_tag_otel.phpt new file mode 100644 index 00000000000..7c05ce2ff5e --- /dev/null +++ b/tests/ext/svc_auto_tag_otel.phpt @@ -0,0 +1,24 @@ +--TEST-- +OTEL_SERVICE_NAME counts as user-defined (emits svc.user:true) +--ENV-- +DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=1 +OTEL_SERVICE_NAME=otel-app +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--FILE-- +name = 'op'; +\DDTrace\close_span(); + +$spans = dd_trace_serialize_closed_spans(); +$processTags = $spans[0]['meta']['_dd.tags.process']; + +echo "DD_SERVICE resolved to: " . ini_get('datadog.service') . "\n"; +echo "has svc.user:true: " . (strpos($processTags, 'svc.user:true') !== false ? 'YES' : 'NO') . "\n"; +echo "has svc.auto: : " . (strpos($processTags, 'svc.auto:') !== false ? 'YES' : 'NO') . "\n"; +?> +--EXPECT-- +DD_SERVICE resolved to: otel-app +has svc.user:true: YES +has svc.auto: : NO diff --git a/tests/ext/svc_runtime_change.phpt b/tests/ext/svc_runtime_change.phpt new file mode 100644 index 00000000000..7a06cbf7a08 --- /dev/null +++ b/tests/ext/svc_runtime_change.phpt @@ -0,0 +1,37 @@ +--TEST-- +Changing datadog.service at runtime recomputes svc.user/svc.auto process tags per-span +--ENV-- +DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=1 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--FILE-- +name = 'before'; +\DDTrace\close_span(); +assert_tags('BEFORE '); + +ini_set('datadog.service', 'changed-svc'); +$span2 = \DDTrace\start_span(); +$span2->name = 'after_set'; +\DDTrace\close_span(); +assert_tags('AFTER '); + +ini_restore('datadog.service'); +$span3 = \DDTrace\start_span(); +$span3->name = 'after_restore'; +\DDTrace\close_span(); +assert_tags('REVERTED'); +?> +--EXPECT-- +BEFORE svc.user=NO svc.auto=YES +AFTER svc.user=YES svc.auto=NO +REVERTED svc.user=NO svc.auto=YES diff --git a/tests/ext/svc_user_tag.phpt b/tests/ext/svc_user_tag.phpt new file mode 100644 index 00000000000..cd673e65e31 --- /dev/null +++ b/tests/ext/svc_user_tag.phpt @@ -0,0 +1,22 @@ +--TEST-- +Process tags include svc.user:true when DD_SERVICE is set +--ENV-- +DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=1 +DD_SERVICE=my-app +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_TRACE_AUTO_FLUSH_ENABLED=0 +--FILE-- +name = 'op'; +\DDTrace\close_span(); + +$spans = dd_trace_serialize_closed_spans(); +$processTags = $spans[0]['meta']['_dd.tags.process']; + +echo "has svc.user:true: " . (strpos($processTags, 'svc.user:true') !== false ? 'YES' : 'NO') . "\n"; +echo "has svc.auto: : " . (strpos($processTags, 'svc.auto:') !== false ? 'YES' : 'NO') . "\n"; +?> +--EXPECT-- +has svc.user:true: YES +has svc.auto: : NO diff --git a/tests/ext/telemetry/telemetry_process_tags.phpt b/tests/ext/telemetry/telemetry_process_tags.phpt index 3ea2bfec053..503ccf10c14 100644 --- a/tests/ext/telemetry/telemetry_process_tags.phpt +++ b/tests/ext/telemetry/telemetry_process_tags.phpt @@ -46,7 +46,7 @@ if ($i == 300) { ?> --EXPECTF-- Included -string(%d) "entrypoint.basedir:telemetry,entrypoint.name:telemetry_process_tags,entrypoint.type:script,entrypoint.workdir:%s,runtime.sapi:cli" +string(%d) "entrypoint.basedir:telemetry,entrypoint.name:telemetry_process_tags,entrypoint.type:script,entrypoint.workdir:%s,runtime.sapi:cli,svc.auto:telemetry_process_tags.php" --CLEAN-- root->property_meta); + if (!zend_hash_str_exists(root_meta, ZEND_STRL("_dd.svc_src"))) { + zval root_svc; + ZVAL_UNDEF(&root_svc); + datadog_convert_to_string(&root_svc, &span->root->property_service); + if (Z_TYPE(root_svc) == IS_STRING && Z_STRLEN(root_svc) > 0) { + normalized_default = ddog_normalize_process_tag_value((ddog_CharSlice){ + .ptr = Z_STRVAL(root_svc), .len = Z_STRLEN(root_svc) + }); + } + zval_ptr_dtor(&root_svc); + } + } + + if (svc_tag_appendix || normalized_default) { + smart_str combined = {0}; + smart_str_append(&combined, process_tags); + if (svc_tag_appendix) { + smart_str_appends(&combined, svc_tag_appendix); + } else { + smart_str_appends(&combined, ",svc.auto:"); + smart_str_appends(&combined, normalized_default); + } + smart_str_0(&combined); + ddog_add_str_span_meta_zstr(rust_span, "_dd.tags.process", combined.s); + smart_str_free(&combined); + } else { + ddog_add_str_span_meta_zstr(rust_span, "_dd.tags.process", process_tags); + } + + if (normalized_default) { + ddog_free_normalized_tag_value(normalized_default); + } } }