Skip to content

Commit 40eb26a

Browse files
fix(otel): add /v1/logs endpoint to OTEL_EXPORTER_OTLP_ENDPOINT (#14791)
## Description Fixes the OpenTelemetry logs endpoint configuration when `OTEL_EXPORTER_OTLP_ENDPOINT` is used. Previously, log payloads could be sent without the required `/v1/logs` path, causing the Agent to drop them. The fix ensures the logs-specific endpoint with the correct path is always used. ## Testing Existing tests in `tests/opentelemetry/test_logs.py` verify endpoint configuration scenarios and proper path handling. ## Risks Low risk. Bug fix that only affects OpenTelemetry logs endpoint configuration. Metrics and traces are unaffected. ## Additional Notes None --------- Co-authored-by: Zach Montoya <[email protected]>
1 parent 6512d4c commit 40eb26a

File tree

7 files changed

+33
-47
lines changed

7 files changed

+33
-47
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ ddtrace/opentelemetry/ @DataDog/apm-sdk-capabilities
204204
ddtrace/internal/opentelemetry @DataDog/apm-sdk-capabilities-python
205205
ddtrace/opentracer/ @DataDog/apm-sdk-capabilities-python
206206
ddtrace/propagation/ @DataDog/apm-sdk-capabilities-python
207+
ddtrace/settings/_opentelemetry.py @DataDog/apm-sdk-capabilities-python
207208

208209
ddtrace/internal/sampling.py @DataDog/apm-sdk-capabilities-python
209210
ddtrace/internal/tracemethods.py @DataDog/apm-sdk-capabilities-python

ddtrace/internal/opentelemetry/logs.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121

2222
DEFAULT_PROTOCOL = "grpc"
2323
DD_LOGS_PROVIDER_CONFIGURED = False
24-
GRPC_PORT = 4317
25-
HTTP_PORT = 4318
26-
HTTP_LOGS_ENDPOINT = "/v1/logs"
2724

2825

2926
def set_otel_logs_provider() -> None:
@@ -178,9 +175,9 @@ def _initialize_logging(exporter_class, protocol, resource):
178175
try:
179176
from opentelemetry.sdk._configuration import _init_logging
180177

181-
# Ensure logging exporter is configured to send payloads to a Datadog Agent.
182-
# The default endpoint is resolved using the hostname from DD_AGENT.. and DD_TRACE_AGENT_... configs
183-
os.environ["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"] = otel_config.exporter.LOGS_ENDPOINT
178+
# Ensure logs exporter is configured to send payloads to a Datadog Agent.
179+
if "OTEL_EXPORTER_OTLP_ENDPOINT" not in os.environ and "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT" not in os.environ:
180+
os.environ["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"] = otel_config.exporter.LOGS_ENDPOINT
184181
_init_logging({protocol: exporter_class}, resource=resource)
185182
return True
186183
except ImportError as e:

ddtrace/internal/opentelemetry/metrics.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ def _should_configure_metrics_exporter() -> bool:
5151

5252
if API_VERSION < MINIMUM_SUPPORTED_VERSION:
5353
log.warning(
54-
"OpenTelemetry API requires version %s or higher to enable logs collection. Found version %s. "
55-
"Please upgrade the opentelemetry-api package before enabling ddtrace OpenTelemetry Logs support.",
54+
"OpenTelemetry API requires version %s or higher to enable metrics collection. Found version %s. "
55+
"Please upgrade the opentelemetry-api package before enabling ddtrace OpenTelemetry Metrics support.",
5656
".".join(str(x) for x in MINIMUM_SUPPORTED_VERSION),
5757
".".join(str(x) for x in API_VERSION),
5858
)
@@ -185,8 +185,8 @@ def _initialize_metrics(exporter_class, protocol, resource):
185185
from opentelemetry.sdk._configuration import _init_metrics
186186

187187
# Ensure metrics exporter is configured to send payloads to a Datadog Agent.
188-
# The default endpoint is resolved using the hostname from DD_AGENT.. and DD_TRACE_AGENT_... configs
189-
os.environ["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] = otel_config.exporter.METRICS_ENDPOINT
188+
if "OTEL_EXPORTER_OTLP_ENDPOINT" not in os.environ and "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT" not in os.environ:
189+
os.environ["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] = otel_config.exporter.METRICS_ENDPOINT
190190
os.environ[
191191
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE"
192192
] = otel_config.exporter.METRICS_TEMPORALITY_PREFERENCE

ddtrace/settings/_opentelemetry.py

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,19 @@
11
import typing as t
2-
from urllib.parse import urljoin
32

43
from ddtrace.internal.telemetry import get_config
54
from ddtrace.internal.telemetry import report_configuration
65
from ddtrace.settings._agent import get_agent_hostname
76
from ddtrace.settings._core import DDConfig
87

98

10-
def _append_metrics_path(value):
11-
if value is None:
12-
return None
13-
14-
return urljoin(value, ExporterConfig.HTTP_METRICS_ENDPOINT)
15-
16-
179
def _derive_endpoint(config: "ExporterConfig"):
18-
if config.PROTOCOL.lower() in ("http/json", "http/protobuf"):
19-
default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.HTTP_PORT}"
20-
else:
21-
default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.GRPC_PORT}"
10+
default_endpoint = ExporterConfig._get_default_endpoint(config.PROTOCOL)
2211
return get_config("OTEL_EXPORTER_OTLP_ENDPOINT", default_endpoint)
2312

2413

2514
def _derive_logs_endpoint(config: "ExporterConfig"):
26-
if config.LOGS_PROTOCOL.lower() in ("http/json", "http/protobuf"):
27-
default_endpoint = (
28-
f"http://{get_agent_hostname()}:{ExporterConfig.HTTP_PORT}{ExporterConfig.HTTP_LOGS_ENDPOINT}"
29-
)
30-
else:
31-
default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.GRPC_PORT}"
32-
return get_config(["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "OTEL_EXPORTER_OTLP_ENDPOINT"], default_endpoint)
15+
default_endpoint = ExporterConfig._get_default_endpoint(config.LOGS_PROTOCOL, config.LOGS_PATH)
16+
return get_config("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", default_endpoint)
3317

3418

3519
def _derive_logs_protocol(config: "ExporterConfig"):
@@ -45,14 +29,8 @@ def _derive_logs_timeout(config: "ExporterConfig"):
4529

4630

4731
def _derive_metrics_endpoint(config: "ExporterConfig"):
48-
if config.METRICS_PROTOCOL.lower() in ("http/json", "http/protobuf"):
49-
default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.HTTP_PORT}"
50-
return get_config("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") or _append_metrics_path(
51-
get_config("OTEL_EXPORTER_OTLP_ENDPOINT", default_endpoint)
52-
)
53-
else:
54-
default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.GRPC_PORT}"
55-
return get_config(["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "OTEL_EXPORTER_OTLP_ENDPOINT"], default_endpoint)
32+
default_endpoint = ExporterConfig._get_default_endpoint(config.METRICS_PROTOCOL, config.METRICS_PATH)
33+
return get_config("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", default_endpoint)
5634

5735

5836
def _derive_metrics_protocol(config: "ExporterConfig"):
@@ -88,12 +66,12 @@ class OpenTelemetryConfig(DDConfig):
8866
class ExporterConfig(DDConfig):
8967
__prefix__ = "exporter"
9068

91-
GRPC_PORT: int = 4317
92-
HTTP_PORT: int = 4318
93-
HTTP_LOGS_ENDPOINT: str = "/v1/logs"
94-
HTTP_METRICS_ENDPOINT: str = "/v1/metrics"
9569
DEFAULT_HEADERS: str = ""
9670
DEFAULT_TIMEOUT: int = 10000
71+
LOGS_PATH: str = "/v1/logs"
72+
METRICS_PATH: str = "/v1/metrics"
73+
DEFAULT_GRPC_ENDPOINT: str = f"http://{get_agent_hostname()}:4317"
74+
DEFAULT_HTTP_ENDPOINT: str = f"http://{get_agent_hostname()}:4318"
9775
DEFAULT_METRICS_TEMPORALITY_PREFERENCE: str = "delta"
9876
DEFAULT_METRICS_METRIC_READER_EXPORT_INTERVAL: int = 10000
9977
DEFAULT_METRICS_METRIC_READER_EXPORT_TIMEOUT: int = 7500
@@ -116,6 +94,12 @@ class ExporterConfig(DDConfig):
11694
METRICS_METRIC_READER_EXPORT_INTERVAL = DDConfig.d(int, _derive_metrics_metric_reader_export_interval)
11795
METRICS_METRIC_READER_EXPORT_TIMEOUT = DDConfig.d(int, _derive_metrics_metric_reader_export_timeout)
11896

97+
@staticmethod
98+
def _get_default_endpoint(protocol: str, endpoint: str = ""):
99+
if protocol.lower() in ("http/json", "http/protobuf"):
100+
return f"{ExporterConfig.DEFAULT_HTTP_ENDPOINT}{endpoint}"
101+
return f"{ExporterConfig.DEFAULT_GRPC_ENDPOINT}"
102+
119103

120104
OpenTelemetryConfig.include(ExporterConfig, namespace="exporter")
121105

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
fix(otel): Ensures the /v1/logs path is correctly added to prevent log payloads from being dropped by the Agent when using OTEL_EXPORTER_OTLP_ENDPOINT configuration. Metrics and traces are unaffected.

tests/opentelemetry/test_logs.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ def test_otel_logs_exporter_auto_configured_http():
200200

201201
from opentelemetry._logs import get_logger_provider
202202

203-
from ddtrace.internal.opentelemetry.logs import HTTP_LOGS_ENDPOINT
204203
from tests.opentelemetry.test_logs import decode_logs_request
205204
from tests.opentelemetry.test_logs import extract_log_correlation_attributes
206205

@@ -217,7 +216,7 @@ def test_otel_logs_exporter_auto_configured_http():
217216
request_body = None
218217
for call in mock_request.call_args_list:
219218
method, url = call[0][:2]
220-
if method == "POST" and HTTP_LOGS_ENDPOINT in url:
219+
if method == "POST" and "/v1/logs" in url:
221220
request_body = call[1].get("data", None)
222221
break
223222
assert request_body is not None, (

tests/telemetry/test_writer.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from ddtrace.internal.telemetry.writer import TelemetryWriter
2121
from ddtrace.internal.telemetry.writer import get_runtime_id
2222
from ddtrace.internal.utils.version import _pep440_to_semver
23+
from ddtrace.settings._agent import get_agent_hostname
2324
from ddtrace.settings._telemetry import config as telemetry_config
2425
from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME
2526
from tests.utils import call_program
@@ -446,8 +447,8 @@ def test_app_started_event_configuration_override(test_agent_session, run_python
446447
},
447448
{
448449
"name": "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT",
449-
"origin": "env_var",
450-
"value": "http://localhost:4317",
450+
"origin": "default",
451+
"value": f"http://{get_agent_hostname()}:4317",
451452
},
452453
{
453454
"name": "OTEL_EXPORTER_OTLP_LOGS_HEADERS",
@@ -466,8 +467,8 @@ def test_app_started_event_configuration_override(test_agent_session, run_python
466467
},
467468
{
468469
"name": "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT",
469-
"origin": "env_var",
470-
"value": "http://localhost:4317",
470+
"origin": "default",
471+
"value": f"http://{get_agent_hostname()}:4317",
471472
},
472473
{
473474
"name": "OTEL_EXPORTER_OTLP_METRICS_HEADERS",

0 commit comments

Comments
 (0)