Skip to content

Commit 557b664

Browse files
dd-octo-sts[bot]github-actions[bot]mabdinur
authored
fix(writer): update agentless intake to browser-intake/api/v2/spans endpoint [backport 4.11] (#18549)
Backport #18514 to 4.11 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Munir Abdinur <munir.abdinur@datadoghq.com>
1 parent 8c74dad commit 557b664

4 files changed

Lines changed: 90 additions & 6 deletions

File tree

ddtrace/internal/writer/writer.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -545,10 +545,38 @@ class AgentlessTraceWriter(HTTPWriter):
545545
"""
546546

547547
HTTP_METHOD = "POST"
548-
# Base URL for the agentless trace JSON intake (EvP / track_type:spans).
549-
INTAKE_HOST = "public-trace-http-intake.logs"
550548
# Agentless payloads must be under 15 MB.
551549
MAX_BUFFER_SIZE = 15 << 20 # 15 MB
550+
INTAKE_URLS: dict[str, str] = {
551+
"datadoghq.com": "https://public-trace-http-intake.logs.datadoghq.com",
552+
"datadoghq.eu": "https://public-trace-http-intake.logs.datadoghq.eu",
553+
"us3.datadoghq.com": "https://trace.browser-intake-us3-datadoghq.com",
554+
"us5.datadoghq.com": "https://trace.browser-intake-us5-datadoghq.com",
555+
"ap1.datadoghq.com": "https://browser-intake-ap1-datadoghq.com",
556+
"ap2.datadoghq.com": "https://browser-intake-ap2-datadoghq.com",
557+
"uk1.datadoghq.com": "https://browser-intake-uk1-datadoghq.com",
558+
"datad0g.com": "https://public-trace-http-intake.logs.datad0g.com",
559+
}
560+
FALLBACK_INTAKE_URL_TEMPLATE = "https://browser-intake-{}.{}"
561+
562+
@staticmethod
563+
def compute_intake_url(site: str) -> str:
564+
url = AgentlessTraceWriter.INTAKE_URLS.get(site)
565+
if url is not None:
566+
return url
567+
# Fallback: strip the TLD, replace remaining dots with dashes, reattach TLD.
568+
# e.g. "ddog-gov.com" -> "browser-intake-ddog-gov.com"
569+
# "us2.ddog-gov.com" -> "browser-intake-us2-ddog-gov.com"
570+
prefix, _, tld = site.rpartition(".")
571+
url = AgentlessTraceWriter.FALLBACK_INTAKE_URL_TEMPLATE.format(prefix.replace(".", "-"), tld)
572+
log.warning(
573+
"Datadog site %r is not explicitly supported for agentless tracing. "
574+
"Attempting to use %r. To resolve this, upgrade to a newer version of "
575+
"ddtrace that supports this site, or disable agentless trace export.",
576+
site,
577+
url,
578+
)
579+
return url
552580

553581
def __init__(
554582
self,
@@ -1112,7 +1140,7 @@ def create_trace_writer(
11121140
return LogWriter()
11131141

11141142
if agentless:
1115-
intake_url = "https://{}.{}".format(AgentlessTraceWriter.INTAKE_HOST, config._dd_site)
1143+
intake_url = AgentlessTraceWriter.compute_intake_url(config._dd_site.lower())
11161144
verify_url(intake_url)
11171145
return AgentlessTraceWriter(
11181146
intake_url=intake_url,

ddtrace/internal/writer/writer_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ def __init__(self, buffer_size, max_payload_size):
4040

4141

4242
class AgentlessWriterClient(WriterClientBase):
43-
"""Client for the agentless JSON span intake (EvP / public-trace-http-intake)."""
43+
"""Client for the agentless span intake (api/v2/spans)."""
4444

45-
ENDPOINT = "v1/input"
45+
ENDPOINT = "api/v2/spans"
4646

4747
def __init__(self, buffer_size: int, max_payload_size: int) -> None:
4848
super(AgentlessWriterClient, self).__init__(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
LLM Observability: Fixes agentless export dropping data on the ``us3``, ``us5``, ``ap1``,
5+
and ``ap2`` Datadog sites. This affected customers on these sites when no Datadog Agent was
6+
running or agentless export was explicitly enabled (``DD_LLMOBS_AGENTLESS_ENABLED=1``).

tests/tracer/test_writer.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ def test_agentless_trace_writer_uses_post():
499499
assert writer.HTTP_METHOD == "POST"
500500
assert writer.intake_url == "https://public-trace-http-intake.logs.datadoghq.com"
501501
assert writer._headers.get("dd-api-key") == "test-api-key"
502-
assert writer._clients[0].ENDPOINT == "v1/input"
502+
assert writer._clients[0].ENDPOINT == "api/v2/spans"
503503
assert writer._encoder.content_type == "application/json"
504504

505505

@@ -541,6 +541,56 @@ def test_agentless_trace_writer_encode_traces():
541541
writer.flush_queue(raise_exc=True)
542542

543543

544+
@pytest.mark.subprocess(
545+
env={"_DD_APM_TRACING_AGENTLESS_ENABLED": "1", "DD_API_KEY": "test-key"},
546+
parametrize={
547+
"DD_SITE": [
548+
"datadoghq.com",
549+
"datadoghq.eu",
550+
"us3.datadoghq.com",
551+
"us5.datadoghq.com",
552+
"ap1.datadoghq.com",
553+
"ap2.datadoghq.com",
554+
"uk1.datadoghq.com",
555+
"datad0g.com",
556+
]
557+
},
558+
)
559+
def test_agentless_trace_writer_intake_url():
560+
"""AgentlessTraceWriter sets the correct intake URL for each DD_SITE."""
561+
import os
562+
563+
from ddtrace.internal.writer.writer import AgentlessTraceWriter
564+
from ddtrace.trace import tracer
565+
566+
site = os.environ["DD_SITE"]
567+
writer = tracer._span_aggregator.writer
568+
assert isinstance(writer, AgentlessTraceWriter)
569+
assert writer.intake_url == AgentlessTraceWriter.INTAKE_URLS[site]
570+
571+
572+
@pytest.mark.parametrize("site,expected", list(AgentlessTraceWriter.INTAKE_URLS.items()))
573+
def test_compute_intake_url_known_sites(site, expected):
574+
assert AgentlessTraceWriter.compute_intake_url(site) == expected
575+
576+
577+
@pytest.mark.parametrize(
578+
"site,expected",
579+
[
580+
("ap3.datadoghq.com", "https://browser-intake-ap3-datadoghq.com"),
581+
("ddog-gov.com", "https://browser-intake-ddog-gov.com"),
582+
("us2.ddog-gov.com", "https://browser-intake-us2-ddog-gov.com"),
583+
],
584+
)
585+
def test_compute_intake_url_unknown_site_uses_browser_intake_fallback(site, expected):
586+
with mock.patch("ddtrace.internal.writer.writer.log") as mock_log:
587+
result = AgentlessTraceWriter.compute_intake_url(site)
588+
589+
assert result == expected
590+
mock_log.warning.assert_called_once()
591+
assert site in mock_log.warning.call_args[0][1]
592+
593+
544594
def test_humansize():
545595
assert _human_size(0) == "0B"
546596
assert _human_size(999) == "999B"

0 commit comments

Comments
 (0)