diff --git a/ext/autoload_php_files.c b/ext/autoload_php_files.c index f51c3aabd5a..23d786c862f 100644 --- a/ext/autoload_php_files.c +++ b/ext/autoload_php_files.c @@ -179,6 +179,43 @@ static void dd_load_files(const char *files_file) { #define dd_load_files(file) EXPECTED(get_global_DD_AUTOLOAD_NO_COMPILE() == false) ? dd_load_file("bridge/_generated_" file) : dd_load_files("bridge/_files_" file) +// Remove mixed return types for PHP 7.4 support. OpenTelemetry v2 now requires ": mixed" and drops PHP 7.4, but we fixup the AST here so that OpenTelemetry v1 still works with PHP 7.4. +#if PHP_VERSION_ID >= 70400 && PHP_VERSION_ID < 80000 +void dd_walk_ast_top_stmt(zend_ast *ast) { + if (ast->kind == ZEND_AST_STMT_LIST) { + zend_ast_list *list = zend_ast_get_list(ast); + uint32_t i; + for (i = 0; i < list->children; ++i) { + dd_walk_ast_top_stmt(list->child[i]); + } + } else if (ast->kind == ZEND_AST_FUNC_DECL || ast->kind == ZEND_AST_METHOD) { + zend_ast_decl *decl = (zend_ast_decl *) ast; + zend_ast *return_type_ast = decl->child[3]; + if (return_type_ast && return_type_ast->kind != ZEND_AST_TYPE) { + if (zend_string_equals_literal(zend_ast_get_str(return_type_ast), "mixed")) { + decl->child[3] = NULL; + zend_ast_destroy(return_type_ast); + } + } + } else if (ast->kind == ZEND_AST_CLASS) { + zend_ast_decl *decl = (zend_ast_decl *) ast; + zend_ast *stmt_ast = decl->child[2]; + + dd_walk_ast_top_stmt(stmt_ast); + } else if (ast->kind == ZEND_AST_NAMESPACE && ast->child[1]) { + dd_walk_ast_top_stmt(ast->child[1]); + } +} + +zend_ast_process_t dd_prev_ast_process = NULL; +void dd_remove_mixed_return(zend_ast *ast) { + dd_walk_ast_top_stmt(ast); + if (dd_prev_ast_process) { + dd_prev_ast_process(ast); + } +} +#endif + // We have, at this place, the luxury of knowing that we'll always be called before composers autoloader. // Note that this code will also be called during opcache.preload, allowing us to not consider that scenario separately. // The first time the autoloader gets invoked for ddtrace\\, we load the API @@ -211,9 +248,16 @@ 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; +#if PHP_VERSION_ID >= 70400 && PHP_VERSION_ID < 80000 + dd_prev_ast_process = zend_ast_process; + zend_ast_process = dd_remove_mixed_return; dd_load_files("opentelemetry"); + zend_ast_process = dd_prev_ast_process; +#else + dd_load_files("opentelemetry"); +#endif if ((ce = zend_hash_find_ptr(EG(class_table), lc_name))) { return ce; } diff --git a/ext/configuration.h b/ext/configuration.h index e6f471c2f55..784c39c2e7c 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/serializer.c b/ext/serializer.c index 51c4e1eac93..35ae7b9d52b 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(&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/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/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/DDTrace/OpenTelemetry/Span.php b/src/DDTrace/OpenTelemetry/Span.php index 14ef8b5684e..d298013ca49 100644 --- a/src/DDTrace/OpenTelemetry/Span.php +++ b/src/DDTrace/OpenTelemetry/Span.php @@ -274,7 +274,7 @@ public function getKind(): int /** * @inheritDoc */ - public function getAttribute(string $key) + public function getAttribute(string $key): mixed { return $this->span->meta[$key] ?? ($this->span->metrics[$key] ?? null); } diff --git a/src/bridge/_files_opentelemetry.php b/src/bridge/_files_opentelemetry.php index e5e2a91d707..57efda17bf3 100644 --- a/src/bridge/_files_opentelemetry.php +++ b/src/bridge/_files_opentelemetry.php @@ -3,8 +3,12 @@ return [ __DIR__ . '/../DDTrace/OpenTelemetry/Context.php', __DIR__ . '/../DDTrace/OpenTelemetry/Convention.php', + __DIR__ . '/../DDTrace/OpenTelemetry/CompositeResolver.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', ];