Skip to content

Commit a7e104f

Browse files
committed
fix(debugger): correct agent endpoint matching
The agent's /info endpoints list may or may not include a leading slash depending on the agent version. The signal uploader only matched the leading-slash form, so newer agents that advertise the debugger endpoints without one were misreported as unsupported: snapshot and log uploads were disabled and an "unsupported agent" warning was logged on every upload cycle (as often as once per second). Normalize the advertised endpoints before matching, consistent with remote config and CI visibility. Also throttle the agent capability check so /info is not polled every tick, bounding the warning when the agent genuinely lacks the required endpoints.
1 parent 68b1ddb commit a7e104f

4 files changed

Lines changed: 40 additions & 6 deletions

File tree

ddtrace/debugging/_uploader.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,20 @@ def info_check(self, agent_info: Optional[dict[str, Any]]) -> bool:
118118
log.debug("Unsupported Datadog agent detected. Please upgrade to 7.49.0.")
119119
return False
120120

121-
endpoints = set(agent_info.get("endpoints", []))
121+
# Agent /info entries may or may not carry a leading slash depending on
122+
# the agent version, so normalize before matching (see remoteconfig).
123+
endpoints = {endpoint.lstrip("/") for endpoint in agent_info.get("endpoints", [])}
122124

123125
logs_track = self._tracks[SignalTrack.LOGS]
124126
snapshot_track = self._tracks[SignalTrack.SNAPSHOT]
125127
logs_track.enabled = True
126128
snapshot_track.enabled = True
127129

128-
if "/debugger/v2/input" in endpoints:
130+
if "debugger/v2/input" in endpoints:
129131
log.debug("Detected /debugger/v2/input endpoint")
130132
logs_track.endpoint = f"/debugger/v2/input{self._endpoint_suffix}"
131133
snapshot_track.endpoint = f"/debugger/v2/input{self._endpoint_suffix}"
132-
elif "/debugger/v1/diagnostics" in endpoints:
134+
elif "debugger/v1/diagnostics" in endpoints:
133135
log.debug("Detected /debugger/v1/diagnostics endpoint fallback")
134136
logs_track.endpoint = f"/debugger/v1/diagnostics{self._endpoint_suffix}"
135137
snapshot_track.endpoint = f"/debugger/v1/diagnostics{self._endpoint_suffix}"

ddtrace/internal/agent.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
from ddtrace.internal.periodic import ForksafeAwakeablePeriodicService
88
from ddtrace.internal.process_tags import compute_base_hash
99
from ddtrace.internal.settings._agent import config
10+
from ddtrace.internal.utils.time import HourGlass
1011

1112
from .utils.http import get_connection
1213

1314

1415
log = get_logger(__name__)
1516

17+
# Bound how often /info is polled while the agent is unreachable or unsupported.
18+
AGENT_CHECK_INTERVAL = 60.0
19+
1620

1721
def process_info_headers(resp):
1822
try:
@@ -51,11 +55,16 @@ def __init__(self, interval: float = 0.0):
5155
super().__init__(interval=interval)
5256

5357
self._state = self._agent_check
58+
self._agent_check_throttle = HourGlass(duration=AGENT_CHECK_INTERVAL)
5459

5560
@abc.abstractmethod
5661
def info_check(self, agent_info: t.Optional[dict]) -> bool: ...
5762

5863
def _agent_check(self) -> None:
64+
if self._agent_check_throttle.trickling():
65+
return
66+
self._agent_check_throttle.turn()
67+
5968
try:
6069
agent_info = info()
6170
except Exception:
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
fixes:
3+
- |
4+
Dynamic Instrumentation, Exception Replay, and Code Origin for Spans: This fix resolves an issue where a
5+
supported Datadog Agent could sometimes be incorrectly reported as unsupported, disabling data uploads and
6+
producing repeated warning logs.

tests/debugging/test_uploader.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,17 @@ def test_info_check_endpoint_selection():
114114
from ddtrace.debugging._signal.model import SignalTrack
115115
from ddtrace.debugging._uploader import SignalUploader
116116

117-
# Test 1: v2 endpoint available - both tracks use v2
117+
# Test 1: v2 endpoint available (agent reports endpoints without a leading
118+
# slash) - both tracks use v2
118119
uploader = SignalUploader(interval=LONG_INTERVAL)
119-
agent_info = {"endpoints": ["/debugger/v2/input", "/debugger/v1/diagnostics"]}
120+
agent_info = {"endpoints": ["debugger/v2/input", "debugger/v1/diagnostics"]}
120121
assert uploader.info_check(agent_info) is True
121122
assert uploader._tracks[SignalTrack.LOGS].enabled is True
122123
assert uploader._tracks[SignalTrack.SNAPSHOT].enabled is True
123124
assert "/debugger/v2/input" in uploader._tracks[SignalTrack.LOGS].endpoint
124125
assert "/debugger/v2/input" in uploader._tracks[SignalTrack.SNAPSHOT].endpoint
125126

126-
# Test 2: Only diagnostics available - both tracks fallback to diagnostics
127+
# Test 2: Only diagnostics available (leading-slash form) - both tracks fallback
127128
uploader = SignalUploader(interval=LONG_INTERVAL)
128129
agent_info = {"endpoints": ["/debugger/v1/diagnostics"]}
129130
assert uploader.info_check(agent_info) is True
@@ -313,6 +314,22 @@ def test_online_raises_when_tracks_disabled():
313314
uploader.online()
314315

315316

317+
def test_agent_check_is_throttled():
318+
# A missing/unsupported agent must not cause /info to be polled on every
319+
# periodic tick; the check is throttled independently of the upload interval.
320+
from ddtrace.internal import agent as agent_module
321+
322+
uploader = SignalUploader(interval=LONG_INTERVAL)
323+
agent_info = {"endpoints": ["/some/other/endpoint"]}
324+
325+
with patch.object(agent_module, "info", return_value=agent_info) as mock_info:
326+
uploader._agent_check()
327+
uploader._agent_check()
328+
329+
assert mock_info.call_count == 1
330+
assert uploader._state == uploader._agent_check
331+
332+
316333
class _IsolatedUploader(MockSignalUploader):
317334
"""Subclass to isolate class-level _products/_instance state from production code."""
318335

0 commit comments

Comments
 (0)