diff --git a/ext/autoload_php_files.c b/ext/autoload_php_files.c index f51c3aabd5a..ec762d330a3 100644 --- a/ext/autoload_php_files.c +++ b/ext/autoload_php_files.c @@ -211,7 +211,7 @@ static zend_class_entry *dd_perform_autoload(zend_string *class_name, zend_strin } } - if (get_DD_TRACE_OTEL_ENABLED() && zend_string_starts_with_literal(lc_name, "opentelemetry\\") && !DDTRACE_G(otel_is_loaded)) { + if ((get_DD_TRACE_OTEL_ENABLED() || get_DD_METRICS_OTEL_ENABLED()) && zend_string_starts_with_literal(lc_name, "opentelemetry\\") && !DDTRACE_G(otel_is_loaded)) { DDTRACE_G(otel_is_loaded) = 1; dd_load_files("opentelemetry"); if ((ce = zend_hash_find_ptr(EG(class_table), lc_name))) { diff --git a/ext/configuration.h b/ext/configuration.h index bc3674aefa1..af8f7a8dd84 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -139,6 +139,7 @@ enum ddtrace_sampling_rules_format { CONFIG(INT, DD_EXCEPTION_REPLAY_CAPTURE_INTERVAL_SECONDS, "3600") \ CONFIG(STRING, DD_TRACE_MEMORY_LIMIT, "") \ CONFIG(BOOL, DD_TRACE_REPORT_HOSTNAME, "false") \ + CONFIG(STRING, DD_HOSTNAME, "") \ CONFIG(BOOL, DD_TRACE_FLUSH_COLLECT_CYCLES, "false") \ CONFIG(BOOL, DD_TRACE_FORCE_FLUSH_ON_SHUTDOWN, "false") /* true if pid == 1 || ppid == 1 */ \ CONFIG(BOOL, DD_TRACE_FORCE_FLUSH_ON_SIGTERM, "false") /* true if pid == 1 || ppid == 1 */ \ @@ -227,6 +228,7 @@ enum ddtrace_sampling_rules_format { CONFIG(BOOL, DD_TRACE_WORDPRESS_CALLBACKS, "true") \ CONFIG(BOOL, DD_INTEGRATION_METRICS_ENABLED, "true", \ .env_config_fallback = ddtrace_conf_otel_metrics_exporter) \ + CONFIG(BOOL, DD_METRICS_OTEL_ENABLED, "false") \ CONFIG(BOOL, DD_TRACE_OTEL_ENABLED, "false") \ CONFIG(STRING, DD_TRACE_LOG_FILE, "", .ini_change = zai_config_system_ini_change) \ CONFIG(STRING, DD_TRACE_LOG_LEVEL, "error", .ini_change = ddtrace_alter_dd_trace_log_level, \ diff --git a/ext/ddtrace.c b/ext/ddtrace.c index ab3077d8432..346e16fcd1f 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -2851,6 +2851,25 @@ PHP_FUNCTION(dd_trace_internal_fn) { if (Z_TYPE_P(name) == IS_STRING && Z_TYPE_P(version) == IS_STRING) { ddtrace_telemetry_notify_integration_version(Z_STRVAL_P(name), Z_STRLEN_P(name), Z_STRVAL_P(version), Z_STRLEN_P(version)); } + } else if (params_count == 2 && FUNCTION_NAME_MATCHES("track_otel_config")) { + zval *config_name = ZVAL_VARARG_PARAM(params, 0); + zval *config_value = ZVAL_VARARG_PARAM(params, 1); + if (Z_TYPE_P(config_name) == IS_STRING) { + // Store the config name and value in the HashTable + zval value_copy; + ZVAL_COPY(&value_copy, config_value); + zend_hash_update(&DDTRACE_G(otel_config_telemetry), Z_STR_P(config_name), &value_copy); + RETVAL_TRUE; + } + } else if (params_count == 3 && FUNCTION_NAME_MATCHES("track_telemetry_metrics")) { + zval *metric_name = ZVAL_VARARG_PARAM(params, 0); + zval *metric_value = ZVAL_VARARG_PARAM(params, 1); + zval *tags = ZVAL_VARARG_PARAM(params, 2); + if (Z_TYPE_P(metric_name) == IS_STRING && Z_TYPE_P(tags) == IS_STRING) { + ddtrace_metric_register_buffer(Z_STR_P(metric_name), DDOG_METRIC_TYPE_COUNT, DDOG_METRIC_NAMESPACE_TRACERS); + ddtrace_metric_add_point(Z_STR_P(metric_name), zval_get_double(metric_value), Z_STR_P(tags)); + RETVAL_TRUE; + } } else if (FUNCTION_NAME_MATCHES("dump_sidecar")) { if (!ddtrace_sidecar) { RETURN_FALSE; diff --git a/ext/ddtrace.h b/ext/ddtrace.h index ba1353811dc..a7469e27ee4 100644 --- a/ext/ddtrace.h +++ b/ext/ddtrace.h @@ -179,6 +179,8 @@ ZEND_BEGIN_MODULE_GLOBALS(ddtrace) HashTable resource_weak_storage; dtor_func_t resource_dtor_func; + + HashTable otel_config_telemetry; ZEND_END_MODULE_GLOBALS(ddtrace) // clang-format on diff --git a/ext/serializer.c b/ext/serializer.c index 1e1fcd1ffbd..5e35b1eae29 100644 --- a/ext/serializer.c +++ b/ext/serializer.c @@ -816,18 +816,25 @@ void ddtrace_set_root_span_properties(ddtrace_root_span_data *span) { } if (get_DD_TRACE_REPORT_HOSTNAME()) { + if (ZSTR_LEN(get_DD_HOSTNAME())) { + zval hostname_zv; + ZVAL_STR_COPY(&hostname_zv, get_DD_HOSTNAME()); + zend_hash_str_add_new(meta, ZEND_STRL("_dd.hostname"), &hostname_zv); + } else { + #ifndef HOST_NAME_MAX #define HOST_NAME_MAX 255 #endif - zend_string *hostname = zend_string_alloc(HOST_NAME_MAX, 0); - if (gethostname(ZSTR_VAL(hostname), HOST_NAME_MAX + 1)) { - zend_string_release(hostname); - } else { - hostname = zend_string_truncate(hostname, strlen(ZSTR_VAL(hostname)), 0); - zval hostname_zv; - ZVAL_STR(&hostname_zv, hostname); - zend_hash_str_add_new(meta, ZEND_STRL("_dd.hostname"), &hostname_zv); + zend_string *hostname = zend_string_alloc(HOST_NAME_MAX, 0); + if (gethostname(ZSTR_VAL(hostname), HOST_NAME_MAX + 1)) { + zend_string_release(hostname); + } else { + hostname = zend_string_truncate(hostname, strlen(ZSTR_VAL(hostname)), 0); + zval hostname_zv; + ZVAL_STR(&hostname_zv, hostname); + zend_hash_str_add_new(meta, ZEND_STRL("_dd.hostname"), &hostname_zv); + } } } diff --git a/ext/telemetry.c b/ext/telemetry.c index a8a2cec469b..5abb5065454 100644 --- a/ext/telemetry.c +++ b/ext/telemetry.c @@ -89,6 +89,7 @@ void ddtrace_telemetry_first_init(void) { void ddtrace_telemetry_rinit(void) { zend_hash_init(&DDTRACE_G(telemetry_spans_created_per_integration), 8, unused, NULL, 0); + zend_hash_init(&DDTRACE_G(otel_config_telemetry), 8, unused, ZVAL_PTR_DTOR, 0); DDTRACE_G(baggage_extract_count) = 0; DDTRACE_G(baggage_inject_count) = 0; DDTRACE_G(baggage_malformed_count) = 0; @@ -98,6 +99,7 @@ void ddtrace_telemetry_rinit(void) { void ddtrace_telemetry_rshutdown(void) { zend_hash_destroy(&DDTRACE_G(telemetry_spans_created_per_integration)); + zend_hash_destroy(&DDTRACE_G(otel_config_telemetry)); } // Register in the sidecar services not bound to the request lifetime @@ -195,6 +197,18 @@ void ddtrace_telemetry_finalize() { if (injection_enabled) { ddog_sidecar_telemetry_enqueueConfig_buffer(buffer, DDOG_CHARSLICE_C("ssi_injection_enabled"), (ddog_CharSlice) {.ptr = injection_enabled, .len = strlen(injection_enabled)}, DDOG_CONFIGURATION_ORIGIN_ENV_VAR, DDOG_CHARSLICE_C("")); } + + // Send OTel configuration telemetry + zend_string *config_name; + zval *config_value; + ZEND_HASH_FOREACH_STR_KEY_VAL(&DDTRACE_G(otel_config_telemetry), config_name, config_value) { + if (config_name && Z_TYPE_P(config_value) == IS_STRING) { + ddog_CharSlice name = dd_zend_string_to_CharSlice(config_name); + ddog_CharSlice value = dd_zend_string_to_CharSlice(Z_STR_P(config_value)); + // OTel configurations are from environment variables + ddog_sidecar_telemetry_enqueueConfig_buffer(buffer, name, value, DDOG_CONFIGURATION_ORIGIN_ENV_VAR, DDOG_CHARSLICE_C("")); + } + } ZEND_HASH_FOREACH_END(); } // Send information about explicitly disabled integrations diff --git a/src/DDTrace/OpenTelemetry/CompositeResolver.php b/src/DDTrace/OpenTelemetry/CompositeResolver.php new file mode 100644 index 00000000000..58d5822b002 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/CompositeResolver.php @@ -0,0 +1,125 @@ +addResolver(new class () implements \OpenTelemetry\SDK\Common\Configuration\Resolver\ResolverInterface { + public function retrieveValue(string $name): mixed + { + // Only configure metrics-related settings if DD_METRICS_OTEL_ENABLED is true + if (($name === 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE' || + $name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') && + !\dd_trace_env_config('DD_METRICS_OTEL_ENABLED')) { + return null; + } + + if ($name === 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE') { + return "delta"; + } + if ($name === 'OTEL_EXPORTER_OTLP_ENDPOINT' || $name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') { + // Determine protocol + $protocol = null; + + if ($name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' && \OpenTelemetry\SDK\Common\Configuration\Configuration::has('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL')) { + // Get metrics-specific protocol + $protocol = \OpenTelemetry\SDK\Common\Configuration\Configuration::getEnum('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL'); + } + + if ($protocol === null) { + // Get general OTLP protocol + $protocol = \OpenTelemetry\SDK\Common\Configuration\Configuration::getEnum('OTEL_EXPORTER_OTLP_PROTOCOL'); + } + + if ($protocol === null) { + // Use language default + $protocol = 'http/protobuf'; + } + + // Determine endpoint + + // Check for general OTLP endpoint (only when requesting metrics endpoint) + if ($name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' && \OpenTelemetry\SDK\Common\Configuration\Configuration::has('OTEL_EXPORTER_OTLP_ENDPOINT')) { + $generalEndpoint = rtrim(\OpenTelemetry\SDK\Common\Configuration\Configuration::getString('OTEL_EXPORTER_OTLP_ENDPOINT'), '/'); + // May need to add subpath for metrics endpoint with HTTP protocol + if ($protocol !== 'grpc') { + return "$generalEndpoint/v1/metrics"; + } + return $generalEndpoint.OtlpUtil::method(Signals::METRICS); + } + + // Get agent host from DD_AGENT_HOST or DD_TRACE_AGENT_URL + $host = null; + $scheme = 'http'; + $port = null; + + // First check DD_TRACE_AGENT_URL for unix sockets or full URLs + $agentUrl = \dd_trace_env_config('DD_TRACE_AGENT_URL'); + if ($agentUrl !== '') { + $component = \parse_url($agentUrl); + if ($component !== false) { + $scheme = $component['scheme'] ?? 'http'; + + // Handle unix scheme - return as-is + if ($scheme === 'unix') { + // Unix sockets: pass through the full URL + // The SDK must be configured with a URL in the format unix:///path/to/socket.sock + return $agentUrl; + } + + $host = $component['host'] ?? null; + } + } + + // Fall back to DD_AGENT_HOST if no URL was set + if ($host === null) { + $ddAgentHost = \dd_trace_env_config('DD_AGENT_HOST'); + if ($ddAgentHost !== '') { + $host = $ddAgentHost; + } + } + + // Build endpoint: {scheme}://{host}:{port} + if ($host === '') { + $host = 'localhost'; + } + + // Determine port based on protocol if not already set + $port = ($protocol === 'grpc') ? '4317' : '4318'; + $endpoint = $scheme . '://' . $host . ':' . $port; + + if ($name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') { + // Add subpath for metrics endpoint with HTTP protocol + if ($protocol !== 'grpc') { + return $endpoint.'/v1/metrics'; + } + else { + return $endpoint.OtlpUtil::method(Signals::METRICS); + } + } + return $endpoint; + } + + // Explicitly return null to match the original implicit behavior. + return null; + } + + public function hasVariable(string $variableName): bool { + // Only provide default values if DD_METRICS_OTEL_ENABLED is true + // AND the variable is not already set in the environment + if ($variableName === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' || + $variableName === 'OTEL_EXPORTER_OTLP_ENDPOINT' || + $variableName === 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE') { + return \dd_trace_env_config('DD_METRICS_OTEL_ENABLED'); + } + return false; + } + }); + } +); \ No newline at end of file diff --git a/src/DDTrace/OpenTelemetry/Configuration.php b/src/DDTrace/OpenTelemetry/Configuration.php new file mode 100644 index 00000000000..da5dce23a83 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/Configuration.php @@ -0,0 +1,150 @@ + $value ? 'true' : 'false', + is_null($value) => '', + is_array($value) => json_encode($value), + default => (string)$value, + }; + + \dd_trace_internal_fn('track_otel_config', $name, $value_str); + } +} + +// Hook Configuration::getString +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getString', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); + +// Hook Configuration::getInt +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getInt', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); + +// Hook Configuration::getBoolean +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getBoolean', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); + +// Hook Configuration::getMixed +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getMixed', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); + +// Hook Configuration::getMap +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getMap', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); + +// Hook Configuration::getList +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getList', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); + +// Hook Configuration::getEnum +\DDTrace\install_hook( + 'OpenTelemetry\SDK\Common\Configuration\Configuration::getEnum', + function (\DDTrace\HookData $hook) { + $name = $hook->args[0] ?? null; + if ($name && is_string($name)) { + $hook->data = $name; + } + }, + function (\DDTrace\HookData $hook) { + if (isset($hook->data) && $hook->returned !== null) { + track_otel_config_if_whitelisted($hook->data, $hook->returned); + } + } +); diff --git a/src/DDTrace/OpenTelemetry/Detectors/Environment.php b/src/DDTrace/OpenTelemetry/Detectors/Environment.php new file mode 100644 index 00000000000..35b72b704f4 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/Detectors/Environment.php @@ -0,0 +1,29 @@ + $value) { + $attributes[$key] = $value; + } + + $builder = (new AttributesFactory)->builder($attributes); + $newResource = ResourceInfo::create($builder->build()); + $resource = $hook->returned; + $resource = $resource->merge($newResource); + $hook->overrideReturnValue($resource); + }); \ No newline at end of file diff --git a/src/DDTrace/OpenTelemetry/Detectors/Host.php b/src/DDTrace/OpenTelemetry/Detectors/Host.php new file mode 100644 index 00000000000..f9245444d88 --- /dev/null +++ b/src/DDTrace/OpenTelemetry/Detectors/Host.php @@ -0,0 +1,21 @@ +builder($attributes); + $newResource = ResourceInfo::create($builder->build()); + $resource = $hook->returned; + $resource = $resource->merge($newResource); + $hook->overrideReturnValue($resource); + }); \ No newline at end of file diff --git a/src/DDTrace/OpenTelemetry/Detectors/Service.php b/src/DDTrace/OpenTelemetry/Detectors/Service.php new file mode 100644 index 00000000000..8c8bac91aac --- /dev/null +++ b/src/DDTrace/OpenTelemetry/Detectors/Service.php @@ -0,0 +1,27 @@ +service; + } else { + if (ddtrace_config_app_name() === '') { + return; + } + $attributes['service.name'] = \ddtrace_config_app_name(); + } + + $builder = (new AttributesFactory)->builder($attributes); + $newResource = ResourceInfo::create($builder->build()); + $resource = $hook->returned; + $resource = $resource->merge($newResource); + $hook->overrideReturnValue($resource); + }); \ No newline at end of file diff --git a/src/bridge/_files_opentelemetry.php b/src/bridge/_files_opentelemetry.php index e5e2a91d707..5193a26f0b3 100644 --- a/src/bridge/_files_opentelemetry.php +++ b/src/bridge/_files_opentelemetry.php @@ -3,8 +3,13 @@ return [ __DIR__ . '/../DDTrace/OpenTelemetry/Context.php', __DIR__ . '/../DDTrace/OpenTelemetry/Convention.php', + __DIR__ . '/../DDTrace/OpenTelemetry/CompositeResolver.php', + __DIR__ . '/../DDTrace/OpenTelemetry/Configuration.php', __DIR__ . '/../DDTrace/OpenTelemetry/SpanContext.php', __DIR__ . '/../DDTrace/OpenTelemetry/Span.php', __DIR__ . '/../DDTrace/OpenTelemetry/SpanBuilder.php', __DIR__ . '/../DDTrace/OpenTelemetry/CachedInstrumentation.php', + __DIR__ . '/../DDTrace/OpenTelemetry/Detectors/Environment.php', + __DIR__ . '/../DDTrace/OpenTelemetry/Detectors/Host.php', + __DIR__ . '/../DDTrace/OpenTelemetry/Detectors/Service.php', ];