diff --git a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy index 3287e6827bc..a0d5a230a9d 100644 --- a/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy +++ b/appsec/tests/integration/src/main/groovy/com/datadog/appsec/php/docker/AppSecContainer.groovy @@ -90,6 +90,7 @@ class AppSecContainer> extends GenericContain withEnv 'DD_TRACE_DEBUG', '1' withEnv 'DD_AUTOLOAD_NO_COMPILE', 'true' // must be exactly 'true' withEnv 'DD_TRACE_GIT_METADATA_ENABLED', '0' + withEnv 'DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED', '0' withEnv 'DD_INSTRUMENTATION_TELEMETRY_ENABLED', '1' // very verbose: withEnv '_DD_DEBUG_SIDECAR_LOG_METHOD', 'file:///tmp/logs/sidecar.log' diff --git a/components-rs/ddtrace.h b/components-rs/ddtrace.h index 0644a3fae84..b88aa97111d 100644 --- a/components-rs/ddtrace.h +++ b/components-rs/ddtrace.h @@ -47,6 +47,8 @@ ddog_Configurator *ddog_library_configurator_new_dummy(bool debug_logs, ddog_Cha int posix_spawn_file_actions_addchdir_np(void *file_actions, const char *path); +uint64_t dd_fnv1a_64(const uint8_t *data, uintptr_t len); + const char *ddog_normalize_process_tag_value(ddog_CharSlice tag_value); void ddog_free_normalized_tag_value(const char *ptr); diff --git a/components-rs/lib.rs b/components-rs/lib.rs index d331ca1df1a..7c539641036 100644 --- a/components-rs/lib.rs +++ b/components-rs/lib.rs @@ -156,6 +156,24 @@ pub unsafe extern "C" fn posix_spawn_file_actions_addchdir_np( } const MAX_TAG_VALUE_LENGTH: usize = 100; +const DD_FNV_PRIME: u64 = 1_099_511_628_211; +const DD_FNV_OFFSET_BASIS: u64 = 14_695_981_039_346_656_037; + +#[no_mangle] +pub unsafe extern "C" fn dd_fnv1a_64(data: *const u8, len: usize) -> u64 { + if data.is_null() || len == 0 { + return DD_FNV_OFFSET_BASIS; + } + + let bytes = std::slice::from_raw_parts(data, len); + let mut hash = DD_FNV_OFFSET_BASIS; + for byte in bytes { + hash ^= u64::from(*byte); + hash = hash.wrapping_mul(DD_FNV_PRIME); + } + + hash +} #[no_mangle] pub extern "C" fn ddog_normalize_process_tag_value( diff --git a/components-rs/sidecar.h b/components-rs/sidecar.h index 47d21f70b24..0b0f49e3023 100644 --- a/components-rs/sidecar.h +++ b/components-rs/sidecar.h @@ -401,6 +401,12 @@ struct ddog_AgentInfoReader *ddog_get_agent_info_reader(const struct ddog_Endpoi */ ddog_CharSlice ddog_get_agent_info_env(struct ddog_AgentInfoReader *reader, bool *changed); +/** + * Gets the container tags hash from agent info (or empty if not existing) + */ +ddog_CharSlice ddog_get_agent_info_container_tags_hash(struct ddog_AgentInfoReader *reader, + bool *changed); + void ddog_send_traces_to_sidecar(ddog_TracesBytes *traces, struct ddog_SenderParameters *parameters); diff --git a/ext/agent_info.c b/ext/agent_info.c index 4c2f8d6e3f7..645dabf8bde 100644 --- a/ext/agent_info.c +++ b/ext/agent_info.c @@ -2,6 +2,7 @@ #include "ddtrace.h" #include "sidecar.h" #include "configuration.h" +#include "process_tags.h" ZEND_EXTERN_MODULE_GLOBALS(ddtrace); @@ -20,3 +21,18 @@ void ddtrace_agent_info_rinit() { DDTRACE_G(agent_info_reader) = ddog_get_agent_info_reader(ddtrace_endpoint); } } + +void ddtrace_get_container_tags_hash(void) { + if (DDTRACE_G(agent_info_reader)) { + bool changed; + ddog_CharSlice hash = ddog_get_agent_info_container_tags_hash( + DDTRACE_G(agent_info_reader), + &changed + ); + if (hash.len > 0) { + zend_string *hash_str = zend_string_init(hash.ptr, hash.len, 1); + ddtrace_process_tags_set_container_tags_hash(hash_str); + zend_string_release(hash_str); + } + } +} diff --git a/ext/agent_info.h b/ext/agent_info.h index e0c4e10fb16..19c1ee397fb 100644 --- a/ext/agent_info.h +++ b/ext/agent_info.h @@ -1,7 +1,11 @@ #ifndef DD_AGENT_INFO_H #define DD_AGENT_INFO_H +#include +#include "Zend/zend_types.h" + void ddtrace_check_agent_info_env(void); void ddtrace_agent_info_rinit(void); +void ddtrace_get_container_tags_hash(void); #endif // DD_AGENT_INFO_H diff --git a/ext/configuration.h b/ext/configuration.h index 058eb17c91f..6d39fbe9291 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -231,6 +231,7 @@ enum ddtrace_sidecar_connection_mode { CONFIG(STRING, DD_TRACE_AGENT_TEST_SESSION_TOKEN, "", .ini_change = ddtrace_alter_test_session_token) \ CONFIG(BOOL, DD_TRACE_PROPAGATE_USER_ID_DEFAULT, "false") \ CONFIG(CUSTOM(INT), DD_DBM_PROPAGATION_MODE, "disabled", .parser = dd_parse_dbm_mode) \ + CONFIG(BOOL, DD_DBM_INJECT_SQL_BASEHASH, "false") \ CONFIG(CUSTOM(INT), DD_TRACE_SIDECAR_CONNECTION_MODE, "auto", .parser = dd_parse_sidecar_connection_mode) \ CONFIG(SET, DD_TRACE_WORDPRESS_ADDITIONAL_ACTIONS, "") \ CONFIG(BOOL, DD_TRACE_WORDPRESS_CALLBACKS, "true") \ diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 1ace3957a31..cc5979db839 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1720,6 +1720,8 @@ static void dd_initialize_request(void) { ddtrace_agent_info_rinit(); + ddtrace_get_container_tags_hash(); + // Reset compile time after request init hook has compiled ddtrace_compile_time_reset(); @@ -2583,6 +2585,17 @@ PHP_FUNCTION(DDTrace_System_container_id) { } } +PHP_FUNCTION(DDTrace_System_process_tags_base_hash) { + UNUSED(execute_data); + + zend_string *base_hash = ddtrace_process_tags_get_base_hash(); + if (base_hash) { + RETVAL_STRINGL(ZSTR_VAL(base_hash), ZSTR_LEN(base_hash)); + } else { + RETURN_NULL(); + } +} + PHP_FUNCTION(DDTrace_Testing_trigger_error) { ddtrace_string message; ddtrace_zpplong_t error_type; @@ -3028,6 +3041,20 @@ PHP_FUNCTION(dd_trace_internal_fn) { ddtrace_generate_runtime_id(); ddtrace_force_new_instance_id(); RETURN_TRUE; + } else if (FUNCTION_NAME_MATCHES("reload_process_tags")) { + if (ddtrace_process_tags_enabled()) { + ddtrace_process_tags_reload(); + ddtrace_sidecar_update_process_tags(); + } + RETVAL_TRUE; + } else if (params_count == 1 && FUNCTION_NAME_MATCHES("set_container_tags_hash")) { + zval *container_tags_hash = ZVAL_VARARG_PARAM(params, 0); + if (Z_TYPE_P(container_tags_hash) == IS_STRING) { + ddtrace_process_tags_set_container_tags_hash(Z_STR_P(container_tags_hash)); + RETVAL_TRUE; + } else { + RETVAL_FALSE; + } } else if (FUNCTION_NAME_MATCHES("synchronous_flush")) { uint32_t timeout = 100; if (params_count == 1) { diff --git a/ext/ddtrace.stub.php b/ext/ddtrace.stub.php index 2b18fe79ba1..ff371b1d1a5 100644 --- a/ext/ddtrace.stub.php +++ b/ext/ddtrace.stub.php @@ -849,6 +849,16 @@ function add_endpoint(string $path, string $operation_name, string $resource_nam * @return string|null The container id, or 'null' if no id was found */ function container_id(): string|null {} + + /** + * Get the process tags base hash + * + * Returns the FNV-1a 64-bit hash of serialized process tags combined with container tags hash. + * This hash is used for Database Monitoring to correlate queries with application processes. + * + * @return string|null The base hash as a binary string (8 bytes), or 'null' if process tags are disabled or not computed + */ + function process_tags_base_hash(): string|null {} } namespace DDTrace\Config { diff --git a/ext/ddtrace_arginfo.h b/ext/ddtrace_arginfo.h index f5dbd2a5046..14e3f266ab6 100644 --- a/ext/ddtrace_arginfo.h +++ b/ext/ddtrace_arginfo.h @@ -177,6 +177,8 @@ 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() +#define arginfo_DDTrace_System_process_tags_base_hash arginfo_DDTrace_System_container_id + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_DDTrace_Config_integration_analytics_enabled, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, integrationName, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -390,6 +392,7 @@ ZEND_FUNCTION(DDTrace_resource_weak_get); ZEND_FUNCTION(DDTrace_are_endpoints_collected); ZEND_FUNCTION(DDTrace_add_endpoint); ZEND_FUNCTION(DDTrace_System_container_id); +ZEND_FUNCTION(DDTrace_System_process_tags_base_hash); ZEND_FUNCTION(DDTrace_Config_integration_analytics_enabled); ZEND_FUNCTION(DDTrace_Config_integration_analytics_sample_rate); ZEND_FUNCTION(DDTrace_UserRequest_has_listeners); @@ -483,6 +486,7 @@ static const zend_function_entry ext_functions[] = { ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "are_endpoints_collected"), zif_DDTrace_are_endpoints_collected, arginfo_DDTrace_are_endpoints_collected, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace", "add_endpoint"), zif_DDTrace_add_endpoint, arginfo_DDTrace_add_endpoint, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\System", "container_id"), zif_DDTrace_System_container_id, arginfo_DDTrace_System_container_id, 0, NULL, NULL) + ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\System", "process_tags_base_hash"), zif_DDTrace_System_process_tags_base_hash, arginfo_DDTrace_System_process_tags_base_hash, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Config", "integration_analytics_enabled"), zif_DDTrace_Config_integration_analytics_enabled, arginfo_DDTrace_Config_integration_analytics_enabled, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\Config", "integration_analytics_sample_rate"), zif_DDTrace_Config_integration_analytics_sample_rate, arginfo_DDTrace_Config_integration_analytics_sample_rate, 0, NULL, NULL) ZEND_RAW_FENTRY(ZEND_NS_NAME("DDTrace\\UserRequest", "has_listeners"), zif_DDTrace_UserRequest_has_listeners, arginfo_DDTrace_UserRequest_has_listeners, 0, NULL, NULL) diff --git a/ext/process_tags.c b/ext/process_tags.c index 76d0b51cba3..60be49c36c2 100644 --- a/ext/process_tags.c +++ b/ext/process_tags.c @@ -1,5 +1,6 @@ #include "php.h" #include +#include #include #include #include @@ -32,11 +33,41 @@ typedef struct { size_t count; size_t capacity; zend_string *serialized; + zend_string *base_hash; + zend_string *container_tags_hash; ddog_Vec_Tag vec; } process_tags_t; static process_tags_t process_tags = {0}; +static void clear_process_tags(void) { + for (size_t i = 0; i < process_tags.count; i++) { + ddog_free_normalized_tag_value(process_tags.tag_list[i].value); + } + + if (process_tags.tag_list) { + pefree(process_tags.tag_list, 1); + } + + if (process_tags.serialized) { + zend_string_release(process_tags.serialized); + } + + if (process_tags.vec.ptr) { + ddog_Vec_Tag_drop(process_tags.vec); + } + + if (process_tags.base_hash) { + zend_string_release(process_tags.base_hash); + } + + if (process_tags.container_tags_hash) { + zend_string_release(process_tags.container_tags_hash); + } + + memset(&process_tags, 0, sizeof(process_tags)); +} + static inline const char *get_basename(const char *path) { if (!path || !*path) return NULL; @@ -168,6 +199,37 @@ static int cmp_process_tag_by_key(const void *tag1, const void* tag2) { return strcmp(tag_entry_1->key, tag_entry_2->key); } +static void recompute_base_hash(void) { + if (!ddtrace_process_tags_enabled() || !process_tags.serialized) { + return; + } + + if (process_tags.base_hash) { + zend_string_release(process_tags.base_hash); + process_tags.base_hash = NULL; + } + + uint64_t hash_value; + if (process_tags.container_tags_hash) { + size_t total_len = ZSTR_LEN(process_tags.serialized) + ZSTR_LEN(process_tags.container_tags_hash); + unsigned char *combined = emalloc(total_len); + + memcpy(combined, ZSTR_VAL(process_tags.serialized), ZSTR_LEN(process_tags.serialized)); + memcpy(combined + ZSTR_LEN(process_tags.serialized), ZSTR_VAL(process_tags.container_tags_hash), ZSTR_LEN(process_tags.container_tags_hash)); + + hash_value = dd_fnv1a_64(combined, total_len); + efree(combined); + } else { + hash_value = dd_fnv1a_64((const uint8_t *)ZSTR_VAL(process_tags.serialized), ZSTR_LEN(process_tags.serialized)); + } + + smart_str hash_buf = {0}; + smart_str_alloc(&hash_buf, 21, 1); + smart_str_append_printf(&hash_buf, "%" PRIu64, hash_value); + smart_str_0(&hash_buf); + process_tags.base_hash = hash_buf.s; +} + static void serialize_process_tags(void) { if (!ddtrace_process_tags_enabled() || !process_tags.count) { return; @@ -202,6 +264,33 @@ static void serialize_process_tags(void) { (ddog_CharSlice) {.ptr = value, .len = strlen(value)} )); } + + recompute_base_hash(); +} + +static void init_process_tags(void) { + process_tags.capacity = 4; + process_tags.tag_list = pemalloc(process_tags.capacity * sizeof(process_tag_entry_t), 1); + if (!process_tags.tag_list) { + process_tags.capacity = 0; + return; + } + + collect_process_tags(); + serialize_process_tags(); +} + +void ddtrace_process_tags_set_container_tags_hash(zend_string *container_tags_hash) { + if (!container_tags_hash || !ddtrace_process_tags_enabled()) { + return; + } + + if (process_tags.container_tags_hash) { + zend_string_release(process_tags.container_tags_hash); + } + process_tags.container_tags_hash = zend_string_copy(container_tags_hash); + + recompute_base_hash(); } zend_string *ddtrace_process_tags_get_serialized(void) { @@ -220,38 +309,22 @@ const ddog_Vec_Tag *ddtrace_process_tags_get_vec(void) { return &empty_vec; } +zend_string *ddtrace_process_tags_get_base_hash(void) { + return (ddtrace_process_tags_enabled() && process_tags.base_hash) ? process_tags.base_hash : NULL; +} + bool ddtrace_process_tags_enabled(void){ - return get_global_DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED(); + return get_DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED(); } void ddtrace_process_tags_first_rinit(void) { - // process_tags struct initializations - process_tags.count = 0; - process_tags.capacity = 4; - process_tags.tag_list = pemalloc(process_tags.capacity * sizeof(process_tag_entry_t), 1); - - if (!process_tags.tag_list) { - process_tags.capacity = 0; - return; - } - - collect_process_tags(); - serialize_process_tags(); + init_process_tags(); } +void ddtrace_process_tags_reload(void) { + clear_process_tags(); + init_process_tags(); +} void ddtrace_process_tags_mshutdown(void) { - for (size_t i = 0; i < process_tags.count; i++) { - ddog_free_normalized_tag_value(process_tags.tag_list[i].value); - } - pefree(process_tags.tag_list, 1); - - if (process_tags.serialized) { - zend_string_release(process_tags.serialized); - } - - if (process_tags.vec.ptr) { - ddog_Vec_Tag_drop(process_tags.vec); - } - - memset(&process_tags, 0, sizeof(process_tags)); + clear_process_tags(); } diff --git a/ext/process_tags.h b/ext/process_tags.h index d2052675011..0840b6b951d 100644 --- a/ext/process_tags.h +++ b/ext/process_tags.h @@ -9,6 +9,8 @@ // Called at first RINIT to collect process tags void ddtrace_process_tags_first_rinit(void); +// Reload process tags in current request +void ddtrace_process_tags_reload(void); // Called at MSHUTDOWN to free resources void ddtrace_process_tags_mshutdown(void); @@ -24,4 +26,11 @@ DDTRACE_PUBLIC zend_string *ddtrace_process_tags_get_serialized(void); // Returns a pointer to an empty Vec if disabled or not yet collected const ddog_Vec_Tag *ddtrace_process_tags_get_vec(void); +// Set the container tags hash +void ddtrace_process_tags_set_container_tags_hash(zend_string *hash); + +// Get the base hash which is the hash of container_tags and process_tags +// Returns NULL if disabled or not yet computed +zend_string *ddtrace_process_tags_get_base_hash(void); + #endif // DD_PROCESS_TAGS_H diff --git a/libdatadog b/libdatadog index d88e70e7669..cc4a550bf60 160000 --- a/libdatadog +++ b/libdatadog @@ -1 +1 @@ -Subproject commit d88e70e76691678dd0304005497ecb4418abc2f5 +Subproject commit cc4a550bf6063f80e969332485df806e2c420ebf diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index e9596d47c7e..f74667cfd9a 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -284,6 +284,13 @@ "default": "true" } ], + "DD_DBM_INJECT_SQL_BASEHASH": [ + { + "implementation": "A", + "type": "boolean", + "default": "false" + } + ], "DD_DBM_PROPAGATION_MODE": [ { "implementation": "A", diff --git a/src/DDTrace/Integrations/DatabaseIntegrationHelper.php b/src/DDTrace/Integrations/DatabaseIntegrationHelper.php index 9a1dee33d6e..59a96f4870f 100644 --- a/src/DDTrace/Integrations/DatabaseIntegrationHelper.php +++ b/src/DDTrace/Integrations/DatabaseIntegrationHelper.php @@ -48,6 +48,14 @@ public static function injectDatabaseIntegrationData(HookData $hook, $backend, $ $dbName = $span->meta[Tag::DB_NAME] ?? $span->meta[Tag::DB_INSTANCE] ?? ''; $peerService = $span->meta['peer.service'] ?? ''; + // Inject base hash into span tags if enabled + if (dd_trace_env_config("DD_DBM_INJECT_SQL_BASEHASH")) { + $baseHash = \DDTrace\System\process_tags_base_hash(); + if ($baseHash !== null) { + $span->meta[Tag::PROPAGATED_HASH] = $baseHash; + } + } + $query = self::propagateViaSqlComments( $hook->args[$argNum], $span->service, @@ -127,6 +135,14 @@ public static function propagateViaSqlComments( } } + // Inject base hash into SQL comment if enabled + if (dd_trace_env_config("DD_DBM_INJECT_SQL_BASEHASH")) { + $baseHash = \DDTrace\System\process_tags_base_hash(); + if ($baseHash !== null) { + $tags["ddsh"] = $baseHash; + } + } + return self::injectSqlComment($query, $tags); } diff --git a/src/api/Tag.php b/src/api/Tag.php index 619882832de..f2cb6b7c1e4 100644 --- a/src/api/Tag.php +++ b/src/api/Tag.php @@ -59,6 +59,7 @@ class Tag const DB_ROW_COUNT = 'db.row_count'; const DB_STMT = 'sql.query'; const DB_USER = 'db.user'; + const PROPAGATED_HASH = '_dd.propagated_hash'; // Kafka const KAFKA_CLIENT_ID = 'messaging.kafka.client_id'; diff --git a/tests/Integration/DatabaseMonitoringTest.php b/tests/Integration/DatabaseMonitoringTest.php index df607d7347d..d95539882c2 100644 --- a/tests/Integration/DatabaseMonitoringTest.php +++ b/tests/Integration/DatabaseMonitoringTest.php @@ -15,10 +15,15 @@ public function ddTearDown() parent::ddTearDown(); self::putenv('DD_TRACE_DEBUG_PRNG_SEED'); self::putenv('DD_DBM_PROPAGATION_MODE'); + self::putenv('DD_DBM_INJECT_SQL_BASEHASH'); + self::putenv('DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED'); self::putEnv("DD_ENV"); self::putEnv("DD_SERVICE"); self::putEnv("DD_SERVICE_MAPPING"); self::putEnv("DD_VERSION"); + + // Reload config to reset process tags + \dd_trace_internal_fn('reload_process_tags'); } public function instrumented($arg, $optionalArg = null) @@ -284,4 +289,122 @@ public function noInjectionWithUnsupportedDriver() \DDTrace\remove_hook($hook); } } + + public function testBaseHashInjection() + { + self::putEnv('DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=true'); + self::putEnv('DD_DBM_INJECT_SQL_BASEHASH=true'); + self::putEnv('DD_DBM_PROPAGATION_MODE=full'); + self::putEnv('DD_TRACE_DEBUG_PRNG_SEED=42'); + + \dd_trace_internal_fn('reload_process_tags'); + + // we don't have access to the agent info endpoint in this test so we have to mock the container tags hash + \dd_trace_internal_fn('set_container_tags_hash', 'abc123'); + + try { + $hook = \DDTrace\install_hook(self::class . "::instrumented", function (HookData $hook) { + $hook->span()->service = "testdb"; + $hook->span()->name = "instrumented"; + DatabaseIntegrationHelper::injectDatabaseIntegrationData($hook, 'mysql', 1); + }); + + $this->assertNotNull(\DDTrace\System\process_tags_base_hash(), 'process_tags_base_hash() is null'); + + $traces = $this->isolateTracer(function () use (&$commentedQuery) { + \DDTrace\start_trace_span(); + $commentedQuery = $this->instrumented(0, "SELECT 1"); + \DDTrace\close_span(); + }); + } finally { + \DDTrace\remove_hook($hook); + } + + // SQL Comment should have ddsh tag + preg_match('/ddsh=\'([^\']+)\'/', $commentedQuery, $matches); + $this->assertNotEmpty($matches, 'ddsh not found in SQL comment'); + $ddshValue = $matches[1]; + + $propagatedHash = $traces[0][1]['meta']['_dd.propagated_hash'] ?? null; + $this->assertNotNull($propagatedHash, '_dd.propagated_hash not found in span'); + $this->assertSame($ddshValue, $propagatedHash, 'ddsh in SQL comment does not match _dd.propagated_hash in span'); + } + + public function testBaseHashInjectionWithoutContainerTagsHash() + { + self::putEnv('DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=true'); + self::putEnv('DD_DBM_INJECT_SQL_BASEHASH=true'); + self::putEnv('DD_DBM_PROPAGATION_MODE=full'); + self::putEnv('DD_TRACE_DEBUG_PRNG_SEED=42'); + + \dd_trace_internal_fn('reload_process_tags'); + + try { + $hook = \DDTrace\install_hook(self::class . "::instrumented", function (HookData $hook) { + $hook->span()->service = "testdb"; + $hook->span()->name = "instrumented"; + DatabaseIntegrationHelper::injectDatabaseIntegrationData($hook, 'mysql', 1); + }); + + $this->assertNotNull(\DDTrace\System\process_tags_base_hash(), 'process_tags_base_hash() is null without container tags hash'); + + $traces = $this->isolateTracer(function () use (&$commentedQuery) { + \DDTrace\start_trace_span(); + $commentedQuery = $this->instrumented(0, "SELECT 1"); + \DDTrace\close_span(); + }); + } finally { + \DDTrace\remove_hook($hook); + } + + preg_match('/ddsh=\'([^\']+)\'/', $commentedQuery, $matches); + $this->assertNotEmpty($matches, 'ddsh not found in SQL comment'); + $ddshValue = $matches[1]; + + $propagatedHash = $traces[0][1]['meta']['_dd.propagated_hash'] ?? null; + $this->assertNotNull($propagatedHash, '_dd.propagated_hash not found in span'); + $this->assertSame($ddshValue, $propagatedHash, 'ddsh in SQL comment does not match _dd.propagated_hash in span'); + } + + public function testBaseHashNotInjectedWhenDisabled() + { + try { + $hook = \DDTrace\install_hook(self::class . "::instrumented", function (HookData $hook) { + $hook->span()->service = "testdb"; + $hook->span()->name = "instrumented"; + DatabaseIntegrationHelper::injectDatabaseIntegrationData($hook, 'mysql', 1); + }); + self::putEnv("DD_TRACE_DEBUG_PRNG_SEED=42"); + self::putEnv("DD_DBM_PROPAGATION_MODE=full"); + // DD_DBM_INJECT_SQL_BASEHASH is not set (defaults to false) + self::putEnv("DD_EXPERIMENTAL_PROPAGATE_PROCESS_TAGS_ENABLED=true"); + + $traces = $this->isolateTracer(function () use (&$commentedQuery) { + \DDTrace\start_trace_span(); + $commentedQuery = $this->instrumented(0, "SELECT 1"); + \DDTrace\close_span(); + }); + } finally { + \DDTrace\remove_hook($hook); + } + + + // Compatibility with older PHP Version + if (method_exists($this, 'assertDoesNotMatchRegularExpression')) { + // The query should NOT contain the base hash + $this->assertDoesNotMatchRegularExpression('/ddsh=/', $commentedQuery); + } else { + $this->assertNotRegExp('/ddsh=/', $commentedQuery); + } + + // The span should NOT have the _dd.propagated_hash tag + $this->assertFlameGraph($traces, [ + SpanAssertion::exists("phpunit")->withChildren([ + SpanAssertion::exists('instrumented')->withExactTags([ + "_dd.dbm_trace_injected" => "true", + "_dd.base_service" => "phpunit", + ]) + ]) + ]); + } } diff --git a/tests/api/Unit/UserAvailableConstantsTest.php b/tests/api/Unit/UserAvailableConstantsTest.php index 40f67f65067..0df908057d9 100644 --- a/tests/api/Unit/UserAvailableConstantsTest.php +++ b/tests/api/Unit/UserAvailableConstantsTest.php @@ -139,6 +139,7 @@ public function tags() [Tag::DB_ROW_COUNT, 'db.row_count'], [Tag::DB_STMT, 'sql.query'], [Tag::DB_USER, 'db.user'], + [Tag::PROPAGATED_HASH, '_dd.propagated_hash'], [Tag::KAFKA_CLIENT_ID, 'messaging.kafka.client_id'], [Tag::KAFKA_GROUP_ID, 'messaging.kafka.group_id'], [Tag::KAFKA_HOST_LIST, 'messaging.kafka.bootstrap.servers'],