Skip to content

Commit 74b0870

Browse files
emmettbutlerYun-Kimncybulnsrip-ddwconti27
authored
feat: integration with dd-trace-api (#12057)
This change adds an integration for the [dd-trace-api-py package](https://github.com/DataDog/dd-trace-api-py). That package is still in pre-release development, so the code this change adds will not be exercised except in CI. The basic integration added here instruments the interface exposed by the dd-trace-api package, proxying its calls to real `Tracer` and `Span` objects. The basic functionality of tracing is implemented, including `start_span`, `trace`, `finish`, and `current_span`. More operations, like setting links or tags on spans, will be implemented in a future pull request. Other parts of the public API like `Pin` and `data_streams` will also be included in future pull requests. [RFC about the API package idea](https://docs.google.com/document/d/1Kszr2AjBj5ni3cgQLXTkDPbZZ0Cbai403tcu_iSWfkc/edit?tab=t.0#heading=h.rnd972k0hiye) ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Yun Kim <[email protected]> Co-authored-by: Nicole Cybul <[email protected]> Co-authored-by: Nick Ripley <[email protected]> Co-authored-by: William Conti <[email protected]> Co-authored-by: Christophe Papazian <[email protected]> Co-authored-by: Munir Abdinur <[email protected]> Co-authored-by: Laplie Anderson <[email protected]> Co-authored-by: Brett Langdon <[email protected]>
1 parent 41b7f35 commit 74b0870

26 files changed

+766
-3
lines changed

.riot/requirements/10e65d1.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.10
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/10e65d1.in
6+
#
7+
attrs==24.3.0
8+
certifi==2024.12.14
9+
charset-normalizer==3.4.1
10+
coverage[toml]==7.6.10
11+
dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py
12+
exceptiongroup==1.2.2
13+
hypothesis==6.45.0
14+
idna==3.10
15+
iniconfig==2.0.0
16+
mock==5.1.0
17+
opentracing==2.4.0
18+
packaging==24.2
19+
pluggy==1.5.0
20+
pytest==8.3.4
21+
pytest-cov==6.0.0
22+
pytest-mock==3.14.0
23+
pyyaml==6.0.2
24+
requests==2.32.3
25+
sortedcontainers==2.4.0
26+
tomli==2.2.1
27+
urllib3==2.3.0

.riot/requirements/1261872.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.12
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/1261872.in
6+
#
7+
attrs==24.3.0
8+
certifi==2024.12.14
9+
charset-normalizer==3.4.1
10+
coverage[toml]==7.6.10
11+
dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py
12+
hypothesis==6.45.0
13+
idna==3.10
14+
iniconfig==2.0.0
15+
mock==5.1.0
16+
opentracing==2.4.0
17+
packaging==24.2
18+
pluggy==1.5.0
19+
pytest==8.3.4
20+
pytest-cov==6.0.0
21+
pytest-mock==3.14.0
22+
requests==2.32.3
23+
sortedcontainers==2.4.0
24+
urllib3==2.3.0

.riot/requirements/14d1688.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.8
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/14d1688.in
6+
#
7+
attrs==24.3.0
8+
certifi==2024.12.14
9+
charset-normalizer==3.4.1
10+
coverage[toml]==7.6.1
11+
dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py
12+
exceptiongroup==1.2.2
13+
hypothesis==6.45.0
14+
idna==3.10
15+
iniconfig==2.0.0
16+
mock==5.1.0
17+
opentracing==2.4.0
18+
packaging==24.2
19+
pluggy==1.5.0
20+
pytest==8.3.4
21+
pytest-cov==5.0.0
22+
pytest-mock==3.14.0
23+
pyyaml==6.0.2
24+
requests==2.32.3
25+
sortedcontainers==2.4.0
26+
tomli==2.2.1
27+
urllib3==2.2.3

.riot/requirements/668f2f5.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.9
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/668f2f5.in
6+
#
7+
attrs==24.3.0
8+
certifi==2024.12.14
9+
charset-normalizer==3.4.1
10+
coverage[toml]==7.6.10
11+
dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py
12+
exceptiongroup==1.2.2
13+
hypothesis==6.45.0
14+
idna==3.10
15+
iniconfig==2.0.0
16+
mock==5.1.0
17+
opentracing==2.4.0
18+
packaging==24.2
19+
pluggy==1.5.0
20+
pytest==8.3.4
21+
pytest-cov==6.0.0
22+
pytest-mock==3.14.0
23+
pyyaml==6.0.2
24+
requests==2.32.3
25+
sortedcontainers==2.4.0
26+
tomli==2.2.1
27+
urllib3==2.3.0

.riot/requirements/d5f777e.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.13
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/d5f777e.in
6+
#
7+
attrs==24.3.0
8+
certifi==2024.12.14
9+
charset-normalizer==3.4.1
10+
coverage[toml]==7.6.10
11+
dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py
12+
hypothesis==6.45.0
13+
idna==3.10
14+
iniconfig==2.0.0
15+
mock==5.1.0
16+
opentracing==2.4.0
17+
packaging==24.2
18+
pluggy==1.5.0
19+
pytest==8.3.4
20+
pytest-cov==6.0.0
21+
pytest-mock==3.14.0
22+
pyyaml==6.0.2
23+
requests==2.32.3
24+
sortedcontainers==2.4.0
25+
urllib3==2.3.0

.riot/requirements/e49670c.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#
2+
# This file is autogenerated by pip-compile with Python 3.11
3+
# by the following command:
4+
#
5+
# pip-compile --no-annotate .riot/requirements/e49670c.in
6+
#
7+
attrs==24.3.0
8+
certifi==2024.12.14
9+
charset-normalizer==3.4.1
10+
coverage[toml]==7.6.10
11+
dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py
12+
hypothesis==6.45.0
13+
idna==3.10
14+
iniconfig==2.0.0
15+
mock==5.1.0
16+
opentracing==2.4.0
17+
packaging==24.2
18+
pluggy==1.5.0
19+
pytest==8.3.4
20+
pytest-cov==6.0.0
21+
pytest-mock==3.14.0
22+
pyyaml==6.0.2
23+
requests==2.32.3
24+
sortedcontainers==2.4.0
25+
urllib3==2.3.0

ddtrace/_monkey.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"cassandra": True,
3838
"celery": True,
3939
"consul": True,
40+
"dd_trace_api": True,
4041
"django": True,
4142
"dramatiq": True,
4243
"elasticsearch": True,
@@ -157,6 +158,8 @@
157158
),
158159
}
159160

161+
_NOT_PATCHABLE_VIA_ENVVAR = {"dd_trace_api"}
162+
160163

161164
class PatchException(Exception):
162165
"""Wraps regular `Exception` class when patching modules"""
@@ -211,8 +214,7 @@ def on_import(hook):
211214
return on_import
212215

213216

214-
def patch_all(**patch_modules):
215-
# type: (bool) -> None
217+
def patch_all(**patch_modules: bool) -> None:
216218
"""Enables ddtrace library instrumentation.
217219
218220
In addition to ``patch_modules``, an override can be specified via an
@@ -229,7 +231,7 @@ def patch_all(**patch_modules):
229231
# The enabled setting can be overridden by environment variables
230232
for module, _enabled in modules.items():
231233
env_var = "DD_TRACE_%s_ENABLED" % module.upper()
232-
if env_var in os.environ:
234+
if module not in _NOT_PATCHABLE_VIA_ENVVAR and env_var in os.environ:
233235
modules[module] = formats.asbool(os.environ[env_var])
234236

235237
# Enable all dependencies for the module

ddtrace/contrib/internal/dd_trace_api/__init__.py

Whitespace-only changes.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import inspect
2+
from typing import Any
3+
from typing import Dict
4+
from typing import List
5+
from typing import Optional
6+
from typing import Tuple
7+
from typing import TypeVar
8+
import weakref
9+
10+
import dd_trace_api
11+
12+
import ddtrace
13+
from ddtrace.internal.logger import get_logger
14+
from ddtrace.internal.wrapping.context import WrappingContext
15+
16+
17+
_DD_HOOK_NAME = "dd.hook"
18+
_TRACER_KEY = "Tracer"
19+
_STUB_TO_REAL = weakref.WeakKeyDictionary()
20+
_STUB_TO_REAL[dd_trace_api.tracer] = ddtrace.tracer
21+
log = get_logger(__name__)
22+
T = TypeVar("T")
23+
_FN_PARAMS: Dict[str, List[str]] = dict()
24+
25+
26+
def _params_for_fn(wrapping_context: WrappingContext, instance: dd_trace_api._Stub, fn_name: str):
27+
key = f"{instance.__class__.__name__}.{fn_name}"
28+
if key not in _FN_PARAMS:
29+
_FN_PARAMS[key] = list(inspect.signature(wrapping_context.__wrapped__).parameters.keys())
30+
return _FN_PARAMS[key]
31+
32+
33+
class DDTraceAPIWrappingContextBase(WrappingContext):
34+
def _handle_return(self) -> None:
35+
stub = self.get_local("self")
36+
fn_name = self.__frame__.f_code.co_name
37+
_call_on_real_instance(
38+
stub,
39+
fn_name,
40+
self.get_local("retval"),
41+
**{param: self.get_local(param) for param in _params_for_fn(self, stub, fn_name) if param != "self"},
42+
)
43+
44+
def __return__(self, value: T) -> T:
45+
"""Always return the original value no matter what our instrumentation does"""
46+
try:
47+
self._handle_return()
48+
except Exception: # noqa: E722
49+
log.debug("Error handling instrumentation return", exc_info=True)
50+
51+
return value
52+
53+
54+
def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]:
55+
"""Convert all dd_trace_api.Span objects in the args/kwargs collections to their held ddtrace.Span objects"""
56+
57+
def convert(arg):
58+
return _STUB_TO_REAL[arg] if isinstance(arg, dd_trace_api.Span) else arg
59+
60+
return [convert(arg) for arg in args], {name: convert(kwarg) for name, kwarg in kwargs.items()}
61+
62+
63+
def _call_on_real_instance(
64+
operand_stub: dd_trace_api._Stub, method_name: str, retval_from_api: Optional[Any], *args: List, **kwargs: Dict
65+
) -> None:
66+
"""
67+
Call `method_name` on the real object corresponding to `operand_stub` with `args` and `kwargs` as arguments.
68+
69+
Store the value that will be returned from the API call we're in the middle of, for the purpose
70+
of mapping from those Stub objects to their real counterparts.
71+
"""
72+
args, kwargs = _proxy_span_arguments(args, kwargs)
73+
retval_from_impl = getattr(_STUB_TO_REAL[operand_stub], method_name)(*args, **kwargs)
74+
if retval_from_api is not None:
75+
_STUB_TO_REAL[retval_from_api] = retval_from_impl
76+
77+
78+
def get_version() -> str:
79+
return getattr(dd_trace_api, "__version__", "")
80+
81+
82+
def patch(tracer=None):
83+
if getattr(dd_trace_api, "__datadog_patch", False):
84+
return
85+
_STUB_TO_REAL[dd_trace_api.tracer] = tracer
86+
87+
DDTraceAPIWrappingContextBase(dd_trace_api.Tracer.start_span).wrap()
88+
DDTraceAPIWrappingContextBase(dd_trace_api.Tracer.trace).wrap()
89+
DDTraceAPIWrappingContextBase(dd_trace_api.Tracer.current_span).wrap()
90+
DDTraceAPIWrappingContextBase(dd_trace_api.Tracer.current_root_span).wrap()
91+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.finish).wrap()
92+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.set_exc_info).wrap()
93+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.finish_with_ancestors).wrap()
94+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.set_tags).wrap()
95+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.set_traceback).wrap()
96+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.__enter__).wrap()
97+
DDTraceAPIWrappingContextBase(dd_trace_api.Span.__exit__).wrap()
98+
99+
dd_trace_api.__datadog_patch = True
100+
101+
102+
def unpatch():
103+
if not getattr(dd_trace_api, "__datadog_patch", False):
104+
return
105+
dd_trace_api.__datadog_patch = False
106+
107+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Tracer.start_span).unwrap()
108+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Tracer.trace).unwrap()
109+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Tracer.current_span).unwrap()
110+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Tracer.current_root_span).unwrap()
111+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.finish).unwrap()
112+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.set_exc_info).unwrap()
113+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.finish_with_ancestors).unwrap()
114+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.set_tags).unwrap()
115+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.set_traceback).unwrap()
116+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.__enter__).unwrap()
117+
DDTraceAPIWrappingContextBase.extract(dd_trace_api.Span.__exit__).unwrap()
118+
119+
dd_trace_api.__datadog_patch = False

riotfile.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT
653653
),
654654
],
655655
),
656+
Venv(
657+
name="dd_trace_api",
658+
command="pytest {cmdargs} tests/contrib/dd_trace_api",
659+
pkgs={"git+https://github.com/DataDog/dd-trace-api-py": latest, "requests": latest},
660+
pys=select_pys(min_version="3.8"),
661+
),
656662
# Django Python version support
657663
# 2.2 3.5, 3.6, 3.7, 3.8 3.9
658664
# 3.2 3.6, 3.7, 3.8, 3.9, 3.10

0 commit comments

Comments
 (0)