Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ext/autoload_php_files.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ static zend_class_entry *dd_perform_autoload(zend_string *class_name, zend_strin
}
}

if ((get_DD_TRACE_OTEL_ENABLED() || get_DD_METRICS_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() || get_DD_LOGS_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;
Expand Down
1 change: 1 addition & 0 deletions ext/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ enum ddtrace_sidecar_connection_mode {
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_LOGS_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, \
Expand Down
12 changes: 12 additions & 0 deletions ext/otel_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@ bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer *buf, bool pre_rinit) {
return false;
}

bool ddtrace_conf_otel_logs_exporter(zai_env_buffer *buf, bool pre_rinit) {
if (get_otel_value((zai_str)ZAI_STRL("OTEL_LOGS_EXPORTER"), buf, pre_rinit)) {
if (strcmp(buf->ptr, "none") == 0) {
buf->ptr = "0"; buf->len = 1;
return true;
}
LOG_ONCE(WARN, "OTEL_LOGS_EXPORTER has invalid value: %s", buf->ptr);
report_otel_cfg_telemetry_invalid("otel_logs_exporter", "dd_logs_otel_enabled", pre_rinit);
}
return false;
}

bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit) {
ZAI_ENV_BUFFER_INIT(local, ZAI_ENV_MAX_BUFSIZ);
if (!get_otel_value((zai_str)ZAI_STRL("OTEL_RESOURCE_ATTRIBUTES"), &local, pre_rinit)) {
Expand Down
1 change: 1 addition & 0 deletions ext/otel_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ bool ddtrace_conf_otel_propagators(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_sample_rate(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_traces_exporter(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_metrics_exporter(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_logs_exporter(zai_env_buffer *buf, bool pre_rinit);
bool ddtrace_conf_otel_resource_attributes_tags(zai_env_buffer *buf, bool pre_rinit);

#endif // DD_OTEL_CONFIG_H
108 changes: 80 additions & 28 deletions src/DDTrace/OpenTelemetry/CompositeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,22 @@ class DatadogResolver implements ResolverInterface

public function retrieveValue(string $name): mixed
{
if (!$this->isMetricsEnabled($name)) {
if ($name === 'OTEL_PHP_AUTOLOAD_ENABLED') {
return \dd_trace_env_config('DD_METRICS_OTEL_ENABLED')
|| \dd_trace_env_config('DD_LOGS_OTEL_ENABLED');
}

if (!$this->isSignalEnabled($name)) {
return null;
}

if ($name === 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE') {
return 'delta';
}

if ($name === 'OTEL_EXPORTER_OTLP_ENDPOINT' || $name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') {
if ($name === 'OTEL_EXPORTER_OTLP_ENDPOINT'
|| $name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
|| $name === 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') {
return $this->resolveEndpoint($name);
}

Expand All @@ -36,66 +43,107 @@ public function retrieveValue(string $name): mixed

public function hasVariable(string $variableName): bool
{
if ($variableName === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' ||
$variableName === 'OTEL_EXPORTER_OTLP_ENDPOINT' ||
$variableName === 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE') {
if ($variableName === 'OTEL_PHP_AUTOLOAD_ENABLED') {
return \dd_trace_env_config('DD_METRICS_OTEL_ENABLED')
|| \dd_trace_env_config('DD_LOGS_OTEL_ENABLED');
}

if ($variableName === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
|| $variableName === 'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE') {
return \dd_trace_env_config('DD_METRICS_OTEL_ENABLED');
}

if ($variableName === 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') {
return \dd_trace_env_config('DD_LOGS_OTEL_ENABLED');
}

if ($variableName === 'OTEL_EXPORTER_OTLP_ENDPOINT') {
return \dd_trace_env_config('DD_METRICS_OTEL_ENABLED')
|| \dd_trace_env_config('DD_LOGS_OTEL_ENABLED');
}

return false;
}

private function isMetricsEnabled(string $name): bool
private function isSignalEnabled(string $name): bool
{
$metricsOnlySettings = [
if (in_array($name, [
'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE',
'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
];

if (in_array($name, $metricsOnlySettings, true)) {
], true)) {
return \dd_trace_env_config('DD_METRICS_OTEL_ENABLED');
}

if ($name === 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') {
return \dd_trace_env_config('DD_LOGS_OTEL_ENABLED');
}

return true;
}

private function resolveEndpoint(string $name): string
{
$isMetricsEndpoint = ($name === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT');
$protocol = $this->resolveProtocol($isMetricsEndpoint);
$isLogsEndpoint = ($name === 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT');
$protocol = $this->resolveProtocol($isMetricsEndpoint, $isLogsEndpoint);

// Check for user-configured general OTLP endpoint (only when requesting metrics endpoint)
// For signal-specific endpoints, check whether the user configured a general OTLP endpoint
// and derive the signal path from it rather than the agent address.
if ($isMetricsEndpoint && Configuration::has('OTEL_EXPORTER_OTLP_ENDPOINT')) {
return $this->buildMetricsEndpointFromGeneral($protocol);
return $this->buildSignalEndpointFromGeneral($protocol, Signals::METRICS);
}

if ($isLogsEndpoint && Configuration::has('OTEL_EXPORTER_OTLP_ENDPOINT')) {
return $this->buildSignalEndpointFromGeneral($protocol, Signals::LOGS);
}

return $this->buildEndpointFromAgent($protocol, $isMetricsEndpoint);
return $this->buildEndpointFromAgent($protocol, $name);
}

private function resolveProtocol(bool $metricsSpecific): ?string
private function resolveProtocol(bool $metricsSpecific, bool $logsSpecific): string
{
if ($metricsSpecific && Configuration::has('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL')) {
return Configuration::getEnum('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL');
return $this->validateProtocol(Configuration::getEnum('OTEL_EXPORTER_OTLP_METRICS_PROTOCOL'));
}

// Call getEnum without has() check to match original behavior -
// allows SDK defaults to be applied if they exist
if ($logsSpecific && Configuration::has('OTEL_EXPORTER_OTLP_LOGS_PROTOCOL')) {
return $this->validateProtocol(Configuration::getEnum('OTEL_EXPORTER_OTLP_LOGS_PROTOCOL'));
}

// Call getEnum without has() check to match original behavior —
// allows SDK defaults to be applied if they exist.
$protocol = Configuration::getEnum('OTEL_EXPORTER_OTLP_PROTOCOL');

return $protocol ?? self::DEFAULT_PROTOCOL;
return $this->validateProtocol($protocol ?? self::DEFAULT_PROTOCOL);
}

private function buildMetricsEndpointFromGeneral(string $protocol): string
private function validateProtocol(string $protocol): string
{
static $valid = ['grpc', 'http/protobuf', 'http/json', 'http/ndjson'];
if (!in_array($protocol, $valid, true)) {
trigger_error(
"OTEL_EXPORTER_OTLP_PROTOCOL '$protocol' is not recognized. "
. "Valid values are: grpc, http/protobuf, http/json, http/ndjson. "
. "Falling back to 'http/protobuf'.",
E_USER_WARNING
);
return self::DEFAULT_PROTOCOL;
}
return $protocol;
}

private function buildSignalEndpointFromGeneral(string $protocol, string $signal): string
{
$generalEndpoint = rtrim(Configuration::getString('OTEL_EXPORTER_OTLP_ENDPOINT'), '/');

if ($this->isGrpc($protocol)) {
return $generalEndpoint . OtlpUtil::method(Signals::METRICS);
return $generalEndpoint . OtlpUtil::method($signal);
}

return $generalEndpoint . '/v1/metrics';
return $generalEndpoint . '/v1/' . $signal;
}

private function buildEndpointFromAgent(string $protocol, bool $isMetricsEndpoint): string
private function buildEndpointFromAgent(string $protocol, string $endpointName): string
{
$agentInfo = $this->resolveAgentInfo();

Expand All @@ -107,8 +155,12 @@ private function buildEndpointFromAgent(string $protocol, bool $isMetricsEndpoin
$port = $this->isGrpc($protocol) ? self::GRPC_PORT : self::HTTP_PORT;
$endpoint = $agentInfo['scheme'] . '://' . $agentInfo['host'] . ':' . $port;

if ($isMetricsEndpoint) {
return $this->appendMetricsPath($endpoint, $protocol);
if ($endpointName === 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') {
return $this->appendSignalPath($endpoint, $protocol, Signals::METRICS);
}

if ($endpointName === 'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') {
return $this->appendSignalPath($endpoint, $protocol, Signals::LOGS);
}

return $endpoint;
Expand Down Expand Up @@ -156,13 +208,13 @@ private function resolveAgentInfo(): array
return ['scheme' => $scheme, 'host' => $host];
}

private function appendMetricsPath(string $endpoint, string $protocol): string
private function appendSignalPath(string $endpoint, string $protocol, string $signal): string
{
if ($this->isGrpc($protocol)) {
return $endpoint . OtlpUtil::method(Signals::METRICS);
return $endpoint . OtlpUtil::method($signal);
}

return $endpoint . '/v1/metrics';
return $endpoint . '/v1/' . $signal;
}

private function isGrpc(string $protocol): bool
Expand Down
11 changes: 11 additions & 0 deletions src/DDTrace/OpenTelemetry/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,25 @@
'OTEL_METRIC_EXPORT_INTERVAL',
'OTEL_METRIC_EXPORT_TIMEOUT',

// OpenTelemetry Logs SDK Configurations
'OTEL_LOGS_EXPORTER',
'OTEL_BLRP_SCHEDULE_DELAY',
'OTEL_BLRP_MAX_QUEUE_SIZE',
'OTEL_BLRP_MAX_EXPORT_BATCH_SIZE',
'OTEL_BLRP_EXPORT_TIMEOUT',

// OTLP Exporter Configurations
'OTEL_EXPORTER_OTLP_METRICS_PROTOCOL',
'OTEL_EXPORTER_OTLP_LOGS_PROTOCOL',
'OTEL_EXPORTER_OTLP_PROTOCOL',
'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT',
'OTEL_EXPORTER_OTLP_LOGS_ENDPOINT',
'OTEL_EXPORTER_OTLP_ENDPOINT',
'OTEL_EXPORTER_OTLP_METRICS_HEADERS',
'OTEL_EXPORTER_OTLP_LOGS_HEADERS',
'OTEL_EXPORTER_OTLP_HEADERS',
'OTEL_EXPORTER_OTLP_METRICS_TIMEOUT',
'OTEL_EXPORTER_OTLP_LOGS_TIMEOUT',
'OTEL_EXPORTER_OTLP_TIMEOUT',
'OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE',
];
Expand Down
2 changes: 1 addition & 1 deletion src/DDTrace/OpenTelemetry/Detectors/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function (\DDTrace\HookData $hook) {

$ddEnv = \dd_trace_env_config('DD_ENV');
if ($ddEnv !== '') {
$attributes['deployment.environment.name'] = $ddEnv;
$attributes['deployment.environment'] = $ddEnv;
}

$ddVersion = \dd_trace_env_config('DD_VERSION');
Expand Down
22 changes: 15 additions & 7 deletions src/DDTrace/OpenTelemetry/Detectors/Host.php
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
<?php

use DDTrace\OpenTelemetry\Detectors\DetectorHelper;
use OpenTelemetry\SDK\Common\Attribute\AttributesFactory;
use OpenTelemetry\SDK\Resource\ResourceInfo;

\DDTrace\install_hook(
'OpenTelemetry\SDK\Resource\Detectors\Host::getResource',
null,
function (\DDTrace\HookData $hook) {
$attributes = [];

if (\dd_trace_env_config('DD_TRACE_REPORT_HOSTNAME')) {
$ddHostname = \dd_trace_env_config('DD_HOSTNAME');
// Only override if DD_HOSTNAME is explicitly set to avoid
// clobbering the hostname detected by OTel's Host detector
if ($ddHostname !== '') {
$attributes['host.name'] = $ddHostname;
DetectorHelper::mergeAttributes($hook, ['host.name' => $ddHostname]);
}
return;
}

DetectorHelper::mergeAttributes($hook, $attributes);
});
// DD_TRACE_REPORT_HOSTNAME is not set — strip auto-detected host.name so it
// doesn't appear in logs unless explicitly set in OTEL_RESOURCE_ATTRIBUTES.
$filtered = [];
foreach ($hook->returned->getAttributes() as $key => $value) {
if ($key !== 'host.name') {
$filtered[$key] = $value;
}
}
$builder = (new AttributesFactory())->builder($filtered);
$hook->overrideReturnValue(ResourceInfo::create($builder->build(), $hook->returned->getSchemaUrl()));
});
79 changes: 79 additions & 0 deletions src/DDTrace/OpenTelemetry/OtlpHooks.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

// This file installs hooks that improve OTLP export reliability:
// 1. A one-time actionable warning when the Datadog Agent returns HTTP 404
// (i.e. Agent < 7.48.0 that does not support OTLP ingest)
// 2. A graceful fallback to 'http/protobuf' when an unrecognized protocol
// value reaches Protocols::contentType(), preventing an uncaught
// UnexpectedValueException from silently killing all exports

// One-time 404 warning ---------------------------------------------------
// PsrTransport wraps 4xx (non-408/429) errors in ErrorFuture. Peek at the
// stored exception via reflection so we can emit a human-actionable message
// once, without modifying the future that the caller receives.
\DDTrace\install_hook(
'OpenTelemetry\SDK\Common\Export\Http\PsrTransport::send',
null,
function (\DDTrace\HookData $hook) {
$future = $hook->returned;
if (!($future instanceof \OpenTelemetry\SDK\Common\Future\ErrorFuture)) {
return;
}

try {
$r = new \ReflectionProperty(\OpenTelemetry\SDK\Common\Future\ErrorFuture::class, 'throwable');
$r->setAccessible(true);
$e = $r->getValue($future);
} catch (\Throwable $ignored) {
return;
}

if (!($e instanceof \RuntimeException) || $e->getCode() !== 404) {
return;
}

static $warned404 = false;
if (!$warned404) {
$warned404 = true;
trigger_error(
'Datadog OpenTelemetry OTLP export received HTTP 404 Not Found. '
. 'Ensure Datadog Agent >= 7.48.0 is running and configured to accept OTLP data '
. '(set DD_OTLP_CONFIG_RECEIVER_PROTOCOLS_HTTP_ENDPOINT or equivalent).',
E_USER_WARNING
);
}
}
);

// Protocol fallback -------------------------------------------------------
// The OTel SDK's Protocols::contentType() calls validate() which throws
// UnexpectedValueException for unknown protocols. We replace an invalid value
// with 'http/protobuf' before the method body runs, emitting a one-time
// warning so the user knows their configuration was ignored.
\DDTrace\install_hook(
'OpenTelemetry\Contrib\Otlp\Protocols::contentType',
function (\DDTrace\HookData $hook) {
$protocol = $hook->args[0] ?? null;
if ($protocol === null) {
return;
}

static $valid = ['grpc', 'http/protobuf', 'http/json', 'http/ndjson'];
if (in_array($protocol, $valid, true)) {
return;
}

static $warnedProtocol = false;
if (!$warnedProtocol) {
$warnedProtocol = true;
trigger_error(
"OpenTelemetry OTLP protocol '$protocol' is not recognized. "
. "Valid values are: grpc, http/protobuf, http/json, http/ndjson. "
. "Falling back to 'http/protobuf'.",
E_USER_WARNING
);
}

$hook->args[0] = 'http/protobuf';
}
);
2 changes: 1 addition & 1 deletion src/api/Log/DatadogLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ private static function format(string $level, $message, array $context = []): st
private static function handleLogInjection(): array
{
$logInjection = \dd_trace_env_config('DD_LOGS_INJECTION');
if ($logInjection) {
if ($logInjection && !\dd_trace_env_config('DD_LOGS_OTEL_ENABLED')) {
$traceId = \DDTrace\logs_correlation_trace_id();
$spanId = \dd_trace_peek_span_id();
if ($traceId && $spanId) {
Expand Down
1 change: 1 addition & 0 deletions src/bridge/_files_opentelemetry.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
__DIR__ . '/../DDTrace/OpenTelemetry/CachedInstrumentation.php',
__DIR__ . '/../DDTrace/OpenTelemetry/CompositeResolver.php',
__DIR__ . '/../DDTrace/OpenTelemetry/Configuration.php',
__DIR__ . '/../DDTrace/OpenTelemetry/OtlpHooks.php',
__DIR__ . '/../DDTrace/OpenTelemetry/Detectors/DetectorHelper.php',
__DIR__ . '/../DDTrace/OpenTelemetry/Detectors/Environment.php',
__DIR__ . '/../DDTrace/OpenTelemetry/Detectors/Host.php',
Expand Down
Loading
Loading