Skip to content

Commit d99dfd9

Browse files
authored
fix(er): use correct Path type in exception replay (and code origin) (#14781)
## Description Exception replay was failing when enabled via remote configuration due to using `pathlib.Path` instead of `ddtrace.internal.compat.Path`. The `is_user_code()` function uses `@singledispatch` and is only registered for the `compat.Path` type, causing `Unsupported type <class 'pathlib.PosixPath'>` errors that silently prevented snapshot capture. This issue only manifested when enabled via RC (vs environment variable) due to module initialization timing differences. Changes: - Replace `pathlib.Path` import with `ddtrace.internal.compat.Path` in replay.py - Replace `pathlib.Path` import with `ddtrace.internal.compat.Path` in origin/span.py - Add exception logging in `_attach_tb_frame_snapshot_to_span` for debugging Fixes exception replay not capturing snapshots when enabled through remote configuration. The system-tests pass with these changes here: DataDog/system-tests#5140 ## Testing Tested against the failing test defined here: DataDog/system-tests#5140 ## Risks Low risk: new debug log could be too verbose? ## Additional Notes Files are [best reviewed with the `?w=1` query param](https://github.com/DataDog/dd-trace-py/pull/14781/files?w=1) to reduce the noise of indenting a section of code. Also worth noting that this **does not** fix DataDog/system-tests#5143 unfortunately. There's still an unknown there.
1 parent 57b137d commit d99dfd9

File tree

3 files changed

+44
-39
lines changed

3 files changed

+44
-39
lines changed

ddtrace/debugging/_exception/replay.py

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from collections import deque
22
from dataclasses import dataclass
3-
from pathlib import Path
43
from threading import current_thread
54
from types import FrameType
65
from types import TracebackType
@@ -15,6 +14,7 @@
1514
from ddtrace.debugging._uploader import SignalUploader
1615
from ddtrace.debugging._uploader import UploaderProduct
1716
from ddtrace.internal import core
17+
from ddtrace.internal.compat import Path
1818
from ddtrace.internal.logger import get_logger
1919
from ddtrace.internal.packages import is_user_code
2020
from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter
@@ -255,49 +255,49 @@ def _attach_tb_frame_snapshot_to_span(
255255
only_user_code: bool = True,
256256
cached_only: bool = False,
257257
) -> bool:
258-
frame = tb.tb_frame
259-
code = frame.f_code
260-
if only_user_code and not is_user_code(Path(code.co_filename)):
261-
return False
262-
263-
snapshot = None
264-
snapshot_id = frame.f_locals.get(SNAPSHOT_KEY, None)
265-
if snapshot_id is None:
266-
# We don't have a snapshot for the frame so we create one
267-
if cached_only:
268-
# If we only want a cached snapshot we return True as a signal
269-
# that we would have captured, but we actually skip generating
270-
# a new snapshot.
271-
return True
272-
273-
snapshot = SpanExceptionSnapshot(
274-
probe=SpanExceptionProbe.build(exc_id, frame),
275-
frame=frame,
276-
thread=current_thread(),
277-
trace_context=span,
278-
exc_id=exc_id,
279-
)
258+
try:
259+
frame = tb.tb_frame
260+
code = frame.f_code
261+
if only_user_code and not is_user_code(Path(code.co_filename)):
262+
return False
280263

281-
# Capture
282-
try:
264+
snapshot = None
265+
snapshot_id = frame.f_locals.get(SNAPSHOT_KEY, None)
266+
if snapshot_id is None:
267+
# We don't have a snapshot for the frame so we create one
268+
if cached_only:
269+
# If we only want a cached snapshot we return True as a signal
270+
# that we would have captured, but we actually skip generating
271+
# a new snapshot.
272+
return True
273+
274+
snapshot = SpanExceptionSnapshot(
275+
probe=SpanExceptionProbe.build(exc_id, frame),
276+
frame=frame,
277+
thread=current_thread(),
278+
trace_context=span,
279+
exc_id=exc_id,
280+
)
281+
282+
# Capture
283283
snapshot.do_line()
284-
except Exception:
285-
log.exception("Error capturing exception replay snapshot %r", snapshot)
286-
return False
287284

288-
# Collect
289-
self.__uploader__.get_collector().push(snapshot)
285+
# Collect
286+
self.__uploader__.get_collector().push(snapshot)
290287

291-
# Memoize
292-
frame.f_locals[SNAPSHOT_KEY] = snapshot_id = snapshot.uuid
288+
# Memoize
289+
frame.f_locals[SNAPSHOT_KEY] = snapshot_id = snapshot.uuid
293290

294-
# Add correlation tags on the span
295-
span.set_tag_str(FRAME_SNAPSHOT_ID_TAG % seq_nr, snapshot_id)
296-
span.set_tag_str(FRAME_FUNCTION_TAG % seq_nr, code.co_name)
297-
span.set_tag_str(FRAME_FILE_TAG % seq_nr, code.co_filename)
298-
span.set_tag_str(FRAME_LINE_TAG % seq_nr, str(tb.tb_lineno))
291+
# Add correlation tags on the span
292+
span.set_tag_str(FRAME_SNAPSHOT_ID_TAG % seq_nr, snapshot_id)
293+
span.set_tag_str(FRAME_FUNCTION_TAG % seq_nr, code.co_name)
294+
span.set_tag_str(FRAME_FILE_TAG % seq_nr, code.co_filename)
295+
span.set_tag_str(FRAME_LINE_TAG % seq_nr, str(tb.tb_lineno))
299296

300-
return snapshot is not None
297+
return snapshot is not None
298+
except Exception: # noqa: F841
299+
log.exception("Error capturing exception replay snapshot")
300+
return False
301301

302302
def on_span_exception(
303303
self, span: Span, _exc_type: t.Type[BaseException], exc: BaseException, traceback: t.Optional[TracebackType]

ddtrace/debugging/_origin/span.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from dataclasses import dataclass
22
from functools import partial
33
from itertools import count
4-
from pathlib import Path
54
import sys
65
from threading import current_thread
76
from time import monotonic_ns
@@ -24,6 +23,7 @@
2423
from ddtrace.debugging._uploader import UploaderProduct
2524
from ddtrace.ext import EXIT_SPAN_TYPES
2625
from ddtrace.internal import core
26+
from ddtrace.internal.compat import Path
2727
from ddtrace.internal.packages import is_user_code
2828
from ddtrace.internal.safety import _isinstance
2929
from ddtrace.internal.wrapping.context import WrappingContext
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
dynamic instrumentation: Fixes an issue where Exception Replay would sometimes
5+
fail to capture snapshots when enabled via remote configuration.

0 commit comments

Comments
 (0)