diff --git a/ddtrace/internal/opentelemetry/logs.py b/ddtrace/internal/opentelemetry/logs.py index b4f98b1545f..9e9544a4b35 100644 --- a/ddtrace/internal/opentelemetry/logs.py +++ b/ddtrace/internal/opentelemetry/logs.py @@ -10,8 +10,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE -from ddtrace.settings._agent import get_agent_hostname -from ddtrace.settings._opentelemetry import exporter_config +from ddtrace.settings._opentelemetry import otel_config log = get_logger(__name__) @@ -36,7 +35,7 @@ def set_otel_logs_provider() -> None: if resource is None: return - protocol = (exporter_config.OTLP_PROTOCOL or exporter_config.OTLP_LOGS_PROTOCOL or DEFAULT_PROTOCOL).lower() + protocol = otel_config.exporter.LOGS_PROTOCOL exporter_class = _import_exporter(protocol) if exporter_class is None: return @@ -180,13 +179,9 @@ def _initialize_logging(exporter_class, protocol, resource): try: from opentelemetry.sdk._configuration import _init_logging - if not exporter_config.OTLP_ENDPOINT and not exporter_config.OTLP_LOGS_ENDPOINT: - if protocol in ("http/json", "http/protobuf"): - endpoint = f"http://{get_agent_hostname()}:{HTTP_PORT}{HTTP_LOGS_ENDPOINT}" - else: - endpoint = f"http://{get_agent_hostname()}:{GRPC_PORT}" - os.environ["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"] = endpoint - + # Ensure logging exporter is configured to send payloads to a Datadog Agent. + # The default endpoint is resolved using the hostname from DD_AGENT.. and DD_TRACE_AGENT_... configs + os.environ["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT"] = otel_config.exporter.LOGS_ENDPOINT _init_logging({protocol: exporter_class}, resource=resource) return True except ImportError as e: diff --git a/ddtrace/settings/_opentelemetry.py b/ddtrace/settings/_opentelemetry.py index 0bd6fb78ede..df4554f913d 100644 --- a/ddtrace/settings/_opentelemetry.py +++ b/ddtrace/settings/_opentelemetry.py @@ -1,21 +1,69 @@ import typing as t +from ddtrace.internal.telemetry import get_config from ddtrace.internal.telemetry import report_configuration +from ddtrace.settings._agent import get_agent_hostname from ddtrace.settings._core import DDConfig -class OpenTelemetryExporterConfig(DDConfig): - __prefix__ = "otel.exporter" +def _derive_endpoint(config: "ExporterConfig"): + if config.PROTOCOL.lower() in ("http/json", "http/protobuf"): + default_endpoint = ( + f"http://{get_agent_hostname()}:{ExporterConfig.HTTP_PORT}{ExporterConfig.HTTP_LOGS_ENDPOINT}" + ) + else: + default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.GRPC_PORT}" + return get_config("OTEL_EXPORTER_OTLP_ENDPOINT", default_endpoint) - # OTLP exporter configuration - OTLP_PROTOCOL = DDConfig.v(t.Optional[str], "otlp.protocol", default=None) - OTLP_LOGS_PROTOCOL = DDConfig.v(t.Optional[str], "otlp.logs.protocol", default=None) - OTLP_ENDPOINT = DDConfig.v(t.Optional[str], "otlp.endpoint", default=None) - OTLP_LOGS_ENDPOINT = DDConfig.v(t.Optional[str], "otlp.logs.endpoint", default=None) - OTLP_HEADERS = DDConfig.v(t.Optional[str], "otlp.headers", default=None) - OTLP_LOGS_HEADERS = DDConfig.v(t.Optional[str], "otlp.logs.headers", default=None) +def _derive_logs_endpoint(config: "ExporterConfig"): + if config.LOGS_PROTOCOL.lower() in ("http/json", "http/protobuf"): + default_endpoint = ( + f"http://{get_agent_hostname()}:{ExporterConfig.HTTP_PORT}{ExporterConfig.HTTP_LOGS_ENDPOINT}" + ) + else: + default_endpoint = f"http://{get_agent_hostname()}:{ExporterConfig.GRPC_PORT}" + return get_config(["OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", "OTEL_EXPORTER_OTLP_ENDPOINT"], default_endpoint) -exporter_config = OpenTelemetryExporterConfig() -report_configuration(exporter_config) +def _derive_logs_protocol(config: "ExporterConfig"): + return get_config("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", config.PROTOCOL) + + +def _derive_logs_headers(config: "ExporterConfig"): + return get_config("OTEL_EXPORTER_OTLP_LOGS_HEADERS", config.HEADERS) + + +def _derive_logs_timeout(config: "ExporterConfig"): + return get_config("OTEL_EXPORTER_OTLP_LOGS_TIMEOUT", config.DEFAULT_TIMEOUT, int) + + +class OpenTelemetryConfig(DDConfig): + __prefix__ = "otel" + + +class ExporterConfig(DDConfig): + __prefix__ = "exporter" + + GRPC_PORT: int = 4317 + HTTP_PORT: int = 4318 + HTTP_LOGS_ENDPOINT: str = "/v1/logs" + DEFAULT_HEADERS: str = "" + DEFAULT_TIMEOUT: int = 10000 + + PROTOCOL = DDConfig.v(t.Optional[str], "otlp.protocol", default="grpc") + ENDPOINT = DDConfig.d(str, _derive_endpoint) + HEADERS = DDConfig.v(str, "otlp.headers", default=DEFAULT_HEADERS) + TIMEOUT = DDConfig.v(int, "otlp.timeout", default=DEFAULT_TIMEOUT) + + LOGS_PROTOCOL = DDConfig.d(str, _derive_logs_protocol) + LOGS_ENDPOINT = DDConfig.d(str, _derive_logs_endpoint) + LOGS_HEADERS = DDConfig.d(str, _derive_logs_headers) + LOGS_TIMEOUT = DDConfig.d(int, _derive_logs_timeout) + + +OpenTelemetryConfig.include(ExporterConfig, namespace="exporter") + +otel_config = OpenTelemetryConfig() + +report_configuration(otel_config) diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 975174e8c04..1dd87e7e80c 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -270,6 +270,8 @@ def test_app_started_event_configuration_override(test_agent_session, run_python env["DD_TRACE_WRITER_REUSE_CONNECTIONS"] = "True" env["DD_TAGS"] = "team:apm,component:web" env["DD_INSTRUMENTATION_CONFIG_ID"] = "abcedf123" + env["DD_LOGS_OTEL_ENABLED"] = "True" + env["OTEL_EXPORTER_OTLP_ENDPOINT"] = "http://localhost:4317" file = tmpdir.join("moon_ears.json") file.write('[{"service":"xy?","name":"a*c"}]') @@ -424,7 +426,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_LLMOBS_ML_APP", "origin": "default", "value": None}, {"name": "DD_LLMOBS_SAMPLE_RATE", "origin": "default", "value": 1.0}, {"name": "DD_LOGS_INJECTION", "origin": "env_var", "value": True}, - {"name": "DD_LOGS_OTEL_ENABLED", "origin": "default", "value": False}, + {"name": "DD_LOGS_OTEL_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_METRICS_OTEL_ENABLED", "origin": "default", "value": False}, {"name": "DD_PROFILING_AGENTLESS", "origin": "default", "value": False}, {"name": "DD_PROFILING_API_TIMEOUT", "origin": "default", "value": 10.0}, @@ -532,6 +534,46 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_USER_MODEL_LOGIN_FIELD", "origin": "default", "value": ""}, {"name": "DD_USER_MODEL_NAME_FIELD", "origin": "default", "value": ""}, {"name": "DD_VERSION", "origin": "default", "value": None}, + { + "name": "OTEL_EXPORTER_OTLP_ENDPOINT", + "origin": "env_var", + "value": "http://localhost:4317", + }, + { + "name": "OTEL_EXPORTER_OTLP_HEADERS", + "origin": "default", + "value": "", + }, + { + "name": "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT", + "origin": "env_var", + "value": "http://localhost:4317", + }, + { + "name": "OTEL_EXPORTER_OTLP_LOGS_HEADERS", + "origin": "default", + "value": "", + }, + { + "name": "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL", + "origin": "default", + "value": "grpc", + }, + { + "name": "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT", + "origin": "default", + "value": 10000, + }, + { + "name": "OTEL_EXPORTER_OTLP_PROTOCOL", + "origin": "default", + "value": "grpc", + }, + { + "name": "OTEL_EXPORTER_OTLP_TIMEOUT", + "origin": "default", + "value": 10000, + }, {"name": "_DD_APPSEC_DEDUPLICATION_ENABLED", "origin": "default", "value": True}, {"name": "_DD_IAST_LAZY_TAINT", "origin": "default", "value": False}, {"name": "_DD_IAST_USE_ROOT_SPAN", "origin": "default", "value": False},