From 660bb504ca016da8ae895b80176017e415561c79 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 14 Nov 2025 15:35:48 +0000 Subject: [PATCH] chore(co): handle view methods We extend support in Code Origin for Spans to handle view methods, in addition to functions. --- ddtrace/debugging/_origin/span.py | 6 +++- .../debugging/_products/code_origin/span.py | 4 ++- tests/debugging/origin/test_span.py | 28 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/ddtrace/debugging/_origin/span.py b/ddtrace/debugging/_origin/span.py index ddbbe66842b..8c0acb49c9f 100644 --- a/ddtrace/debugging/_origin/span.py +++ b/ddtrace/debugging/_origin/span.py @@ -5,6 +5,7 @@ from time import monotonic_ns from types import FrameType from types import FunctionType +from types import MethodType import typing as t import uuid @@ -210,8 +211,11 @@ class SpanCodeOriginProcessorEntry: _lock = Lock() @classmethod - def instrument_view(cls, f): + def instrument_view(cls, f: t.Union[FunctionType, MethodType]) -> None: + if isinstance(f, MethodType): + f = t.cast(FunctionType, f.__func__) if not _isinstance(f, FunctionType): + log.warning("Cannot instrument view %r: not a function", f) return with cls._lock: diff --git a/ddtrace/debugging/_products/code_origin/span.py b/ddtrace/debugging/_products/code_origin/span.py index e4dad6ac720..4f6e89042fd 100644 --- a/ddtrace/debugging/_products/code_origin/span.py +++ b/ddtrace/debugging/_products/code_origin/span.py @@ -1,5 +1,7 @@ import enum from functools import partial +from types import FunctionType +from types import MethodType import typing as t import ddtrace.internal.core as core @@ -32,7 +34,7 @@ def start(): # We need to instrument the entrypoints on boot because this is the only # time the tracer will notify us of entrypoints being registered. @partial(core.on, "service_entrypoint.patch") - def _(f: t.Callable) -> None: + def _(f: t.Union[FunctionType, MethodType]) -> None: from ddtrace.debugging._origin.span import SpanCodeOriginProcessorEntry SpanCodeOriginProcessorEntry.instrument_view(f) diff --git a/tests/debugging/origin/test_span.py b/tests/debugging/origin/test_span.py index 05287a1ce7d..92831f528e9 100644 --- a/tests/debugging/origin/test_span.py +++ b/tests/debugging/origin/test_span.py @@ -169,3 +169,31 @@ def entry_call(): assert _exit.get_tag("_dd.code_origin.type") is None assert _exit.get_tag("_dd.code_origin.frames.0.file") is None assert _exit.get_tag("_dd.code_origin.frames.0.line") is None + + def test_span_origin_entry_method(self): + class App: + def entry_call(self): + pass + + app = App() + + core.dispatch("service_entrypoint.patch", (app.entry_call,)) + + with self.tracer.trace("entry"): + app.entry_call() + with self.tracer.trace("middle"): + with self.tracer.trace("exit", span_type=SpanTypes.HTTP): + pass + + self.assert_span_count(3) + entry, *_ = self.get_spans() + + # Check for the expected tags on the entry span + assert entry.get_tag("_dd.code_origin.type") == "entry" + assert entry.get_tag("_dd.code_origin.frames.0.file") == str(Path(__file__).resolve()) + assert entry.get_tag("_dd.code_origin.frames.0.line") == str(App.entry_call.__code__.co_firstlineno) + assert entry.get_tag("_dd.code_origin.frames.0.type") == __name__ + assert ( + entry.get_tag("_dd.code_origin.frames.0.method") + == "SpanProbeTestCase.test_span_origin_entry_method..App.entry_call" + )