From eb66781c065f2ac01d19d69f8e335839d0fd446e Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Wed, 22 Jan 2025 12:49:12 -0800 Subject: [PATCH 01/33] stub integration with dd-trace-api --- .riot/requirements/131db98.txt | 25 +++++++++++++++ .riot/requirements/1707381.txt | 20 ++++++++++++ .riot/requirements/1e77fb3.txt | 20 ++++++++++++ .riot/requirements/1ff8ce3.txt | 25 +++++++++++++++ .riot/requirements/7cd7251.txt | 26 ++++++++++++++++ ddtrace/_monkey.py | 1 + ddtrace/contrib/dd_trace_api/__init__.py | 23 ++++++++++++++ .../contrib/internal/dd_trace_api/__init__.py | 0 .../contrib/internal/dd_trace_api/patch.py | 30 ++++++++++++++++++ riotfile.py | 6 ++++ tests/contrib/dd_trace_api/__init__.py | 0 .../dd_trace_api/test_dd_trace_api_patch.py | 31 +++++++++++++++++++ 12 files changed, 207 insertions(+) create mode 100644 .riot/requirements/131db98.txt create mode 100644 .riot/requirements/1707381.txt create mode 100644 .riot/requirements/1e77fb3.txt create mode 100644 .riot/requirements/1ff8ce3.txt create mode 100644 .riot/requirements/7cd7251.txt create mode 100644 ddtrace/contrib/dd_trace_api/__init__.py create mode 100644 ddtrace/contrib/internal/dd_trace_api/__init__.py create mode 100644 ddtrace/contrib/internal/dd_trace_api/patch.py create mode 100644 tests/contrib/dd_trace_api/__init__.py create mode 100644 tests/contrib/dd_trace_api/test_dd_trace_api_patch.py diff --git a/.riot/requirements/131db98.txt b/.riot/requirements/131db98.txt new file mode 100644 index 00000000000..1428d10f091 --- /dev/null +++ b/.riot/requirements/131db98.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/131db98.in +# +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.6.10 +dd-trace-api @ file:///root/dd-trace-api-py +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.3.0 diff --git a/.riot/requirements/1707381.txt b/.riot/requirements/1707381.txt new file mode 100644 index 00000000000..a5c167be2d3 --- /dev/null +++ b/.riot/requirements/1707381.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1707381.in +# +-e file:///root/dd-trace-api-py +attrs==24.3.0 +coverage[toml]==7.6.10 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e77fb3.txt b/.riot/requirements/1e77fb3.txt new file mode 100644 index 00000000000..515842460bd --- /dev/null +++ b/.riot/requirements/1e77fb3.txt @@ -0,0 +1,20 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1e77fb3.in +# +-e file:///root/dd-trace-api-py +attrs==24.3.0 +coverage[toml]==7.6.10 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1ff8ce3.txt b/.riot/requirements/1ff8ce3.txt new file mode 100644 index 00000000000..a3cad870c1b --- /dev/null +++ b/.riot/requirements/1ff8ce3.txt @@ -0,0 +1,25 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1ff8ce3.in +# +-e file:///root/dd-trace-api-py +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.6.10 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.3.0 diff --git a/.riot/requirements/7cd7251.txt b/.riot/requirements/7cd7251.txt new file mode 100644 index 00000000000..b751ea6aaec --- /dev/null +++ b/.riot/requirements/7cd7251.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/7cd7251.in +# +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.6.10 +dd-trace-api @ file:///root/dd-trace-api-py +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +requests==2.32.3 +sortedcontainers==2.4.0 +urllib3==2.3.0 +wheel==0.45.1 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 75c70114ef2..51aa104ea97 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -37,6 +37,7 @@ "cassandra": True, "celery": True, "consul": True, + "dd_trace_api": True, "django": True, "dramatiq": True, "elasticsearch": True, diff --git a/ddtrace/contrib/dd_trace_api/__init__.py b/ddtrace/contrib/dd_trace_api/__init__.py new file mode 100644 index 00000000000..10a065bb8f7 --- /dev/null +++ b/ddtrace/contrib/dd_trace_api/__init__.py @@ -0,0 +1,23 @@ +""" + +Enabling +~~~~~~~~ + +The dd_trace_api integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(dd_trace_api=True) + + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +""" diff --git a/ddtrace/contrib/internal/dd_trace_api/__init__.py b/ddtrace/contrib/internal/dd_trace_api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py new file mode 100644 index 00000000000..8adfd470d00 --- /dev/null +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -0,0 +1,30 @@ +import os + +import dd_trace_api +from wrapt import wrap_function_wrapper as _w + +from ddtrace import config +from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SSRF +from ddtrace.contrib.internal.trace_utils import unwrap as _u +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.formats import asbool +from ddtrace.settings.asm import config as asm_config +from ddtrace.trace import Pin + + +def get_version() -> str: + return getattr(dd_trace_api, "__version__", "") + + +def patch(): + if getattr(dd_trace_api, "__datadog_patch", False): + return + dd_trace_api.__datadog_patch = True + + +def unpatch(): + if not getattr(dd_trace_api, "__datadog_patch", False): + return + dd_trace_api.__datadog_patch = False diff --git a/riotfile.py b/riotfile.py index b52fa77cbe1..065ca4d9fef 100644 --- a/riotfile.py +++ b/riotfile.py @@ -756,6 +756,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), ], ), + Venv( + name="dd_trace_api", + command="pytest {cmdargs} tests/contrib/dd_trace_api", + pkgs={"/root/dd-trace-api-py": latest, "requests": latest}, + pys=select_pys(), + ), # Django Python version support # 2.2 3.5, 3.6, 3.7, 3.8 3.9 # 3.2 3.6, 3.7, 3.8, 3.9, 3.10 diff --git a/tests/contrib/dd_trace_api/__init__.py b/tests/contrib/dd_trace_api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/contrib/dd_trace_api/test_dd_trace_api_patch.py b/tests/contrib/dd_trace_api/test_dd_trace_api_patch.py new file mode 100644 index 00000000000..4f2afd87494 --- /dev/null +++ b/tests/contrib/dd_trace_api/test_dd_trace_api_patch.py @@ -0,0 +1,31 @@ +# This test script was automatically generated by the contrib-patch-tests.py +# script. If you want to make changes to it, you should make sure that you have +# removed the ``_generated`` suffix from the file name, to prevent the content +# from being overwritten by future re-generations. + +from ddtrace.contrib.internal.dd_trace_api.patch import get_version +from ddtrace.contrib.internal.dd_trace_api.patch import patch + + +try: + from ddtrace.contrib.internal.dd_trace_api.patch import unpatch +except ImportError: + unpatch = None +from tests.contrib.patch import PatchTestCase + + +class TestDDTraceAPIPatch(PatchTestCase.Base): + __integration_name__ = "dd_trace_api" + __module_name__ = "dd_trace_api" + __patch_func__ = patch + __unpatch_func__ = unpatch + __get_version__ = get_version + + def assert_module_patched(self, requests): + pass + + def assert_not_module_patched(self, requests): + pass + + def assert_not_module_double_patched(self, requests): + pass From 198aaa900c0318e2b0d01ac54755f11cac830453 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Thu, 23 Jan 2025 13:48:08 -0800 Subject: [PATCH 02/33] a basic but valid failing test --- .../contrib/internal/dd_trace_api/patch.py | 13 ++++++++++++ .../contrib/dd_trace_api/test_integration.py | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/contrib/dd_trace_api/test_integration.py diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 8adfd470d00..f5dc53a7985 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -1,4 +1,5 @@ import os +from sys import addaudithook import dd_trace_api from wrapt import wrap_function_wrapper as _w @@ -14,6 +15,16 @@ from ddtrace.trace import Pin +_DD_HOOK_PREFIX = "dd.hooks." + + +def _hook(name, args): + if not dd_trace_api.__datadog_patch: + return + if name.startswith(_DD_HOOK_PREFIX): + print(f"Triggered hook with name {name}") + + def get_version() -> str: return getattr(dd_trace_api, "__version__", "") @@ -22,9 +33,11 @@ def patch(): if getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = True + addaudithook(_hook) def unpatch(): if not getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = False + # NB sys.addaudithook's cannot be removed diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py new file mode 100644 index 00000000000..a146b342d48 --- /dev/null +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -0,0 +1,21 @@ +import dd_trace_api + +from ddtrace.contrib.internal.dd_trace_api.patch import patch +from ddtrace.contrib.internal.dd_trace_api.patch import unpatch +from tests.utils import TracerTestCase + + +class DDTraceAPITestCase(TracerTestCase): + def setUp(self): + super(DDTraceAPITestCase, self).setUp() + patch() + + def tearDown(self): + super(DDTraceAPITestCase, self).tearDown() + unpatch() + + def test_start_span(self): + with dd_trace_api.tracer.Tracer().start_span("web.request") as span: + span.finish() + spans = self.pop_spans() + assert len(spans) == 1 From d60b73cfa2febfa7aacab1af1cdfca35f42f96c8 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 08:17:49 -0800 Subject: [PATCH 03/33] fleshing out the test setup --- .../contrib/internal/dd_trace_api/patch.py | 33 ++++++++++++++++++- .../contrib/dd_trace_api/test_integration.py | 4 +-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index f5dc53a7985..d42e928fa1d 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -4,6 +4,7 @@ import dd_trace_api from wrapt import wrap_function_wrapper as _w +import ddtrace from ddtrace import config from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink @@ -16,13 +17,43 @@ _DD_HOOK_PREFIX = "dd.hooks." +_CURRENT_SPAN = None + + +def _patched_start_span(*args, **kwargs): + global _CURRENT_SPAN + _CURRENT_SPAN = ddtrace.tracer.start_span(*args, **kwargs) + + +def _patched_span_enter(*args, **kwargs): + _CURRENT_SPAN.__enter__(*args, **kwargs) + + +def _patched_span_exit(*args, **kwargs): + _CURRENT_SPAN.__exit__(*args, **kwargs) + + +def _patched_span_finish(*args, **kwargs): + _CURRENT_SPAN.finish(*args, **kwargs) + + +_HANDLERS = { + "Tracer.start_span": _patched_start_span, + "Span.__enter__": _patched_span_enter, + "Span.__exit__": _patched_span_exit, + "Span.finish": _patched_span_finish, +} def _hook(name, args): if not dd_trace_api.__datadog_patch: return if name.startswith(_DD_HOOK_PREFIX): - print(f"Triggered hook with name {name}") + name_suffix = ".".join(name.split(".")[2:]) + if name_suffix not in _HANDLERS: + return + print(name_suffix) + _HANDLERS[name_suffix](*(args[0][0]), **(args[0][1])) def get_version() -> str: diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index a146b342d48..00f8473c745 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -17,5 +17,5 @@ def tearDown(self): def test_start_span(self): with dd_trace_api.tracer.Tracer().start_span("web.request") as span: span.finish() - spans = self.pop_spans() - assert len(spans) == 1 + spans = self.pop_spans() + assert len(spans) == 1 From 4e1004d12bb056897001d859405b421e909723a1 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 08:25:27 -0800 Subject: [PATCH 04/33] working on the dummy tracer... --- ddtrace/contrib/internal/dd_trace_api/patch.py | 5 +++-- tests/contrib/dd_trace_api/test_integration.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index d42e928fa1d..83652da1884 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -22,7 +22,7 @@ def _patched_start_span(*args, **kwargs): global _CURRENT_SPAN - _CURRENT_SPAN = ddtrace.tracer.start_span(*args, **kwargs) + _CURRENT_SPAN = dd_trace_api._tracer.start_span(*args, **kwargs) def _patched_span_enter(*args, **kwargs): @@ -60,10 +60,11 @@ def get_version() -> str: return getattr(dd_trace_api, "__version__", "") -def patch(): +def patch(tracer=None): if getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = True + Pin.override(dd_trace_api, _tracer=tracer or ddtrace.tracer) addaudithook(_hook) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 00f8473c745..1a8d80b0b73 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -8,7 +8,7 @@ class DDTraceAPITestCase(TracerTestCase): def setUp(self): super(DDTraceAPITestCase, self).setUp() - patch() + patch(tracer=self.tracer) def tearDown(self): super(DDTraceAPITestCase, self).tearDown() From dae3efd5c8341fad26879310b7a3956b5805fc22 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 08:58:27 -0800 Subject: [PATCH 05/33] store tracer globally --- ddtrace/contrib/internal/dd_trace_api/patch.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 83652da1884..1d37ac44ff0 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -18,11 +18,12 @@ _DD_HOOK_PREFIX = "dd.hooks." _CURRENT_SPAN = None +_TRACER = ddtrace.tracer def _patched_start_span(*args, **kwargs): global _CURRENT_SPAN - _CURRENT_SPAN = dd_trace_api._tracer.start_span(*args, **kwargs) + _CURRENT_SPAN = _TRACER.start_span(*args, **kwargs) def _patched_span_enter(*args, **kwargs): @@ -52,7 +53,6 @@ def _hook(name, args): name_suffix = ".".join(name.split(".")[2:]) if name_suffix not in _HANDLERS: return - print(name_suffix) _HANDLERS[name_suffix](*(args[0][0]), **(args[0][1])) @@ -64,7 +64,8 @@ def patch(tracer=None): if getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = True - Pin.override(dd_trace_api, _tracer=tracer or ddtrace.tracer) + global _TRACER + _TRACER = tracer addaudithook(_hook) From 3e40d48716c0fa30922693760da129f43f4f26e5 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 09:18:37 -0800 Subject: [PATCH 06/33] pull from github --- .riot/requirements/10e65d1.txt | 27 +++++++++++++++++ .../requirements/{131db98.txt => 1261872.txt} | 4 +-- .riot/requirements/14d1688.txt | 27 +++++++++++++++++ .riot/requirements/15b1ff4.txt | 30 +++++++++++++++++++ .riot/requirements/1707381.txt | 20 ------------- .riot/requirements/1e77fb3.txt | 20 ------------- .riot/requirements/668f2f5.txt | 27 +++++++++++++++++ .../requirements/{7cd7251.txt => d5f777e.txt} | 7 ++--- .../requirements/{1ff8ce3.txt => e49670c.txt} | 6 ++-- riotfile.py | 2 +- 10 files changed, 120 insertions(+), 50 deletions(-) create mode 100644 .riot/requirements/10e65d1.txt rename .riot/requirements/{131db98.txt => 1261872.txt} (76%) create mode 100644 .riot/requirements/14d1688.txt create mode 100644 .riot/requirements/15b1ff4.txt delete mode 100644 .riot/requirements/1707381.txt delete mode 100644 .riot/requirements/1e77fb3.txt create mode 100644 .riot/requirements/668f2f5.txt rename .riot/requirements/{7cd7251.txt => d5f777e.txt} (65%) rename .riot/requirements/{1ff8ce3.txt => e49670c.txt} (65%) diff --git a/.riot/requirements/10e65d1.txt b/.riot/requirements/10e65d1.txt new file mode 100644 index 00000000000..f4eb9d21b0b --- /dev/null +++ b/.riot/requirements/10e65d1.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/10e65d1.in +# +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.6.10 +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +requests==2.32.3 +sortedcontainers==2.4.0 +tomli==2.2.1 +urllib3==2.3.0 diff --git a/.riot/requirements/131db98.txt b/.riot/requirements/1261872.txt similarity index 76% rename from .riot/requirements/131db98.txt rename to .riot/requirements/1261872.txt index 1428d10f091..5f94623ae41 100644 --- a/.riot/requirements/131db98.txt +++ b/.riot/requirements/1261872.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/131db98.in +# pip-compile --no-annotate .riot/requirements/1261872.in # attrs==24.3.0 certifi==2024.12.14 charset-normalizer==3.4.1 coverage[toml]==7.6.10 -dd-trace-api @ file:///root/dd-trace-api-py +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 diff --git a/.riot/requirements/14d1688.txt b/.riot/requirements/14d1688.txt new file mode 100644 index 00000000000..7956336155d --- /dev/null +++ b/.riot/requirements/14d1688.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/14d1688.in +# +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.6.1 +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +requests==2.32.3 +sortedcontainers==2.4.0 +tomli==2.2.1 +urllib3==2.2.3 diff --git a/.riot/requirements/15b1ff4.txt b/.riot/requirements/15b1ff4.txt new file mode 100644 index 00000000000..d5636cc73ff --- /dev/null +++ b/.riot/requirements/15b1ff4.txt @@ -0,0 +1,30 @@ +# +# This file is autogenerated by pip-compile with Python 3.7 +# by the following command: +# +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/15b1ff4.in +# +attrs==24.2.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.2.7 +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +importlib-metadata==6.7.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.0 +pluggy==1.2.0 +pytest==7.4.4 +pytest-cov==4.1.0 +pytest-mock==3.11.1 +pyyaml==6.0.1 +requests==2.31.0 +sortedcontainers==2.4.0 +tomli==2.0.1 +typing-extensions==4.7.1 +urllib3==2.0.7 +zipp==3.15.0 diff --git a/.riot/requirements/1707381.txt b/.riot/requirements/1707381.txt deleted file mode 100644 index a5c167be2d3..00000000000 --- a/.riot/requirements/1707381.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1707381.in -# --e file:///root/dd-trace-api-py -attrs==24.3.0 -coverage[toml]==7.6.10 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==6.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.2 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e77fb3.txt b/.riot/requirements/1e77fb3.txt deleted file mode 100644 index 515842460bd..00000000000 --- a/.riot/requirements/1e77fb3.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1e77fb3.in -# --e file:///root/dd-trace-api-py -attrs==24.3.0 -coverage[toml]==7.6.10 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==6.0.0 -pytest-mock==3.14.0 -pyyaml==6.0.2 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/668f2f5.txt b/.riot/requirements/668f2f5.txt new file mode 100644 index 00000000000..644799441d7 --- /dev/null +++ b/.riot/requirements/668f2f5.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/668f2f5.in +# +attrs==24.3.0 +certifi==2024.12.14 +charset-normalizer==3.4.1 +coverage[toml]==7.6.10 +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py +exceptiongroup==1.2.2 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pyyaml==6.0.2 +requests==2.32.3 +sortedcontainers==2.4.0 +tomli==2.2.1 +urllib3==2.3.0 diff --git a/.riot/requirements/7cd7251.txt b/.riot/requirements/d5f777e.txt similarity index 65% rename from .riot/requirements/7cd7251.txt rename to .riot/requirements/d5f777e.txt index b751ea6aaec..661f34852c0 100644 --- a/.riot/requirements/7cd7251.txt +++ b/.riot/requirements/d5f777e.txt @@ -1,14 +1,14 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --no-annotate .riot/requirements/7cd7251.in +# pip-compile --no-annotate .riot/requirements/d5f777e.in # attrs==24.3.0 certifi==2024.12.14 charset-normalizer==3.4.1 coverage[toml]==7.6.10 -dd-trace-api @ file:///root/dd-trace-api-py +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 @@ -23,4 +23,3 @@ pyyaml==6.0.2 requests==2.32.3 sortedcontainers==2.4.0 urllib3==2.3.0 -wheel==0.45.1 diff --git a/.riot/requirements/1ff8ce3.txt b/.riot/requirements/e49670c.txt similarity index 65% rename from .riot/requirements/1ff8ce3.txt rename to .riot/requirements/e49670c.txt index a3cad870c1b..4c6bb99ba9e 100644 --- a/.riot/requirements/1ff8ce3.txt +++ b/.riot/requirements/e49670c.txt @@ -1,14 +1,14 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1ff8ce3.in +# pip-compile --no-annotate .riot/requirements/e49670c.in # --e file:///root/dd-trace-api-py attrs==24.3.0 certifi==2024.12.14 charset-normalizer==3.4.1 coverage[toml]==7.6.10 +dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py hypothesis==6.45.0 idna==3.10 iniconfig==2.0.0 diff --git a/riotfile.py b/riotfile.py index 065ca4d9fef..3213d918daa 100644 --- a/riotfile.py +++ b/riotfile.py @@ -759,7 +759,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="dd_trace_api", command="pytest {cmdargs} tests/contrib/dd_trace_api", - pkgs={"/root/dd-trace-api-py": latest, "requests": latest}, + pkgs={"git+https://github.com/DataDog/dd-trace-api-py": latest, "requests": latest}, pys=select_pys(), ), # Django Python version support From 181b1c1343c70bf090b823a03cd190997e4005b7 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 09:32:22 -0800 Subject: [PATCH 07/33] add dd_trace_api integration to CI --- lib-injection/sources/min_compatible_versions.csv | 14 ++++++++------ min_compatible_versions.csv | 14 ++++++++------ tests/contrib/suitespec.yml | 11 +++++++++++ 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/lib-injection/sources/min_compatible_versions.csv b/lib-injection/sources/min_compatible_versions.csv index 4537863f24c..77abca4d8cb 100644 --- a/lib-injection/sources/min_compatible_versions.csv +++ b/lib-injection/sources/min_compatible_versions.csv @@ -17,14 +17,15 @@ algoliasearch,~=2.5 anthropic,==0.26.0 anyio,>=3.4.0 aredis,0 -asgiref,~=3.0 +asgiref,~=3.0.0 astunparse,0 async_generator,~=1.10 -asyncpg,~=0.23 +asyncpg,~=0.23.0 asynctest,==0.13.0 austin-python,~=1.0 avro,0 azure.functions,0 +bcrypt,==4.2.1 blinker,0 boto3,==1.34.49 bottle,>=0.12 @@ -64,19 +65,20 @@ elasticsearch[async],0 envier,==0.5.2 exceptiongroup,0 faiss-cpu,==1.8.0 -falcon,~=3.0 +falcon,~=3.0.0 fastapi,~=0.64.0 flask,~=0.12.0 flask-caching,~=1.10.0 flask-openapi3,0 gevent,~=20.12.0 +git+https://github.com/DataDog/dd-trace-api-py,0 google-ai-generativelanguage,0 google-generativeai,0 googleapis-common-protos,0 graphene,~=3.0.0 graphql-core,~=3.2.0 graphql-relay,0 -greenlet,~=1.0.0 +greenlet,~=1.0 grpcio,~=1.34.0 gunicorn,==20.0.4 gunicorn[gevent],0 @@ -97,13 +99,13 @@ langchain-community,==0.0.14 langchain-core,==0.1.52 langchain-openai,==0.1.6 langchain-pinecone,==0.1.0 -langchain_experimental,==0.0.47 +langgraph,~=0.2.60 logbook,~=1.0.0 loguru,~=0.4.0 lxml,0 lz4,0 mako,~=1.1.0 -mariadb,~=1.0.0 +mariadb,~=1.0 markupsafe,<2.0 mock,0 molten,>=1.0 diff --git a/min_compatible_versions.csv b/min_compatible_versions.csv index 4537863f24c..77abca4d8cb 100644 --- a/min_compatible_versions.csv +++ b/min_compatible_versions.csv @@ -17,14 +17,15 @@ algoliasearch,~=2.5 anthropic,==0.26.0 anyio,>=3.4.0 aredis,0 -asgiref,~=3.0 +asgiref,~=3.0.0 astunparse,0 async_generator,~=1.10 -asyncpg,~=0.23 +asyncpg,~=0.23.0 asynctest,==0.13.0 austin-python,~=1.0 avro,0 azure.functions,0 +bcrypt,==4.2.1 blinker,0 boto3,==1.34.49 bottle,>=0.12 @@ -64,19 +65,20 @@ elasticsearch[async],0 envier,==0.5.2 exceptiongroup,0 faiss-cpu,==1.8.0 -falcon,~=3.0 +falcon,~=3.0.0 fastapi,~=0.64.0 flask,~=0.12.0 flask-caching,~=1.10.0 flask-openapi3,0 gevent,~=20.12.0 +git+https://github.com/DataDog/dd-trace-api-py,0 google-ai-generativelanguage,0 google-generativeai,0 googleapis-common-protos,0 graphene,~=3.0.0 graphql-core,~=3.2.0 graphql-relay,0 -greenlet,~=1.0.0 +greenlet,~=1.0 grpcio,~=1.34.0 gunicorn,==20.0.4 gunicorn[gevent],0 @@ -97,13 +99,13 @@ langchain-community,==0.0.14 langchain-core,==0.1.52 langchain-openai,==0.1.6 langchain-pinecone,==0.1.0 -langchain_experimental,==0.0.47 +langgraph,~=0.2.60 logbook,~=1.0.0 loguru,~=0.4.0 lxml,0 lz4,0 mako,~=1.1.0 -mariadb,~=1.0.0 +mariadb,~=1.0 markupsafe,<2.0 mock,0 molten,>=1.0 diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 8c5fbc72da2..2c0df64643f 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -75,6 +75,9 @@ components: - ddtrace/contrib/dbapi/* - ddtrace/contrib/dbapi_async/* - ddtrace/ext/db.py + dd_trace_api: + - ddtrace/contrib/dd_trace_api/* + - ddtrace/internal/contrib/dd_trace_api/* django: - ddtrace/contrib/django/* - ddtrace/contrib/internal/django/* @@ -471,6 +474,14 @@ suites: - '@datastreams' - tests/datastreams/* runner: riot + dd_trace_api: + paths: + - '@bootstrap' + - '@core' + - '@tracing' + - '@dd_trace_api' + - tests/contrib/dd_trace_api/* + runner: riot django: env: TEST_MEMCACHED_HOST: memcached From ffc79e511e94cf1d41dd95b809105fdd0d9c18fc Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 09:41:12 -0800 Subject: [PATCH 08/33] linting --- ddtrace/contrib/internal/dd_trace_api/patch.py | 11 ----------- tests/contrib/suitespec.yml | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 1d37ac44ff0..b4ce1bc720d 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -1,19 +1,8 @@ -import os from sys import addaudithook import dd_trace_api -from wrapt import wrap_function_wrapper as _w import ddtrace -from ddtrace import config -from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request -from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink -from ddtrace.appsec._iast.constants import VULN_SSRF -from ddtrace.contrib.internal.trace_utils import unwrap as _u -from ddtrace.internal.schema import schematize_service_name -from ddtrace.internal.utils.formats import asbool -from ddtrace.settings.asm import config as asm_config -from ddtrace.trace import Pin _DD_HOOK_PREFIX = "dd.hooks." diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 2c0df64643f..b810d7e2f20 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -77,7 +77,7 @@ components: - ddtrace/ext/db.py dd_trace_api: - ddtrace/contrib/dd_trace_api/* - - ddtrace/internal/contrib/dd_trace_api/* + - ddtrace/contrib/internal/dd_trace_api/* django: - ddtrace/contrib/django/* - ddtrace/contrib/internal/django/* From 89e04a175546ae90bd657ff96335603f18cd2f0b Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 10:00:13 -0800 Subject: [PATCH 09/33] enable snapshots --- tests/contrib/suitespec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index b810d7e2f20..222f25899a0 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -482,6 +482,7 @@ suites: - '@dd_trace_api' - tests/contrib/dd_trace_api/* runner: riot + snapshot: true django: env: TEST_MEMCACHED_HOST: memcached From 9bc342312c9cc2fa4c32f8e96fbf67092f72941f Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 10:15:24 -0800 Subject: [PATCH 10/33] doesnt work on 3.7 --- .riot/requirements/15b1ff4.txt | 30 ------------------------------ riotfile.py | 2 +- 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 .riot/requirements/15b1ff4.txt diff --git a/.riot/requirements/15b1ff4.txt b/.riot/requirements/15b1ff4.txt deleted file mode 100644 index d5636cc73ff..00000000000 --- a/.riot/requirements/15b1ff4.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.7 -# by the following command: -# -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/15b1ff4.in -# -attrs==24.2.0 -certifi==2024.12.14 -charset-normalizer==3.4.1 -coverage[toml]==7.2.7 -dd-trace-api @ git+https://github.com/DataDog/dd-trace-api-py -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==6.7.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.2.0 -pytest==7.4.4 -pytest-cov==4.1.0 -pytest-mock==3.11.1 -pyyaml==6.0.1 -requests==2.31.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.7.1 -urllib3==2.0.7 -zipp==3.15.0 diff --git a/riotfile.py b/riotfile.py index 3213d918daa..6bc153c51f6 100644 --- a/riotfile.py +++ b/riotfile.py @@ -760,7 +760,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT name="dd_trace_api", command="pytest {cmdargs} tests/contrib/dd_trace_api", pkgs={"git+https://github.com/DataDog/dd-trace-api-py": latest, "requests": latest}, - pys=select_pys(), + pys=select_pys(min_version="3.8"), ), # Django Python version support # 2.2 3.5, 3.6, 3.7, 3.8 3.9 From 60ca763431afa82228ed59b04b373e7311c19096 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 10:27:35 -0800 Subject: [PATCH 11/33] stub verification --- tests/contrib/dd_trace_api/test_integration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 1a8d80b0b73..cd96d512000 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -1,5 +1,6 @@ import dd_trace_api +from ddtrace import Span as dd_span_class from ddtrace.contrib.internal.dd_trace_api.patch import patch from ddtrace.contrib.internal.dd_trace_api.patch import unpatch from tests.utils import TracerTestCase @@ -16,6 +17,8 @@ def tearDown(self): def test_start_span(self): with dd_trace_api.tracer.Tracer().start_span("web.request") as span: + assert not isinstance(span, dd_span_class), "Returned span object should be a stub" + assert not hasattr(span, "span_id"), "Returned span stub should not support read operations" span.finish() spans = self.pop_spans() assert len(spans) == 1 From 4ffec90d08652497092814e0c20d9e0148c332c1 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 10:30:44 -0800 Subject: [PATCH 12/33] stub verification --- tests/contrib/dd_trace_api/test_integration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index cd96d512000..9a818d2d1d1 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -19,6 +19,8 @@ def test_start_span(self): with dd_trace_api.tracer.Tracer().start_span("web.request") as span: assert not isinstance(span, dd_span_class), "Returned span object should be a stub" assert not hasattr(span, "span_id"), "Returned span stub should not support read operations" - span.finish() spans = self.pop_spans() assert len(spans) == 1 + generated_span = spans[0] + assert isinstance(generated_span, dd_span_class), "Generated span is a real span" + assert hasattr(generated_span, "span_id"), "Generated span should support read operations" From b1db4dca39ca098ee7780edb102a8dff95d7ed59 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 10:47:21 -0800 Subject: [PATCH 13/33] only hook once --- .../contrib/internal/dd_trace_api/patch.py | 10 ++++++++- .../contrib/dd_trace_api/test_integration.py | 21 +++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index b4ce1bc720d..d5560f11711 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -15,6 +15,11 @@ def _patched_start_span(*args, **kwargs): _CURRENT_SPAN = _TRACER.start_span(*args, **kwargs) +def _patched_trace(*args, **kwargs): + global _CURRENT_SPAN + _CURRENT_SPAN = _TRACER.trace(*args, **kwargs) + + def _patched_span_enter(*args, **kwargs): _CURRENT_SPAN.__enter__(*args, **kwargs) @@ -29,6 +34,7 @@ def _patched_span_finish(*args, **kwargs): _HANDLERS = { "Tracer.start_span": _patched_start_span, + "Tracer.trace": _patched_trace, "Span.__enter__": _patched_span_enter, "Span.__exit__": _patched_span_exit, "Span.finish": _patched_span_finish, @@ -55,7 +61,9 @@ def patch(tracer=None): dd_trace_api.__datadog_patch = True global _TRACER _TRACER = tracer - addaudithook(_hook) + if not getattr(dd_trace_api, "__dd_has_audit_hook", False): + addaudithook(_hook) + dd_trace_api.__dd_has_audit_hook = True def unpatch(): diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 9a818d2d1d1..d8a13e43772 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -1,3 +1,5 @@ +from typing import Any + import dd_trace_api from ddtrace import Span as dd_span_class @@ -15,12 +17,23 @@ def tearDown(self): super(DDTraceAPITestCase, self).tearDown() unpatch() - def test_start_span(self): - with dd_trace_api.tracer.Tracer().start_span("web.request") as span: - assert not isinstance(span, dd_span_class), "Returned span object should be a stub" - assert not hasattr(span, "span_id"), "Returned span stub should not support read operations" + def _assert_span_stub(self, stub: Any): + assert not isinstance(stub, dd_span_class), "Returned span object should be a stub" + assert not hasattr(stub, "span_id"), "Returned span stub should not support read operations" + + def _assert_real_span(self): spans = self.pop_spans() assert len(spans) == 1 generated_span = spans[0] assert isinstance(generated_span, dd_span_class), "Generated span is a real span" assert hasattr(generated_span, "span_id"), "Generated span should support read operations" + + def test_start_span(self): + with dd_trace_api.tracer.Tracer().start_span("web.request") as span: + self._assert_span_stub(span) + self._assert_real_span() + + def test_trace(self): + with dd_trace_api.tracer.Tracer().trace("web.request") as span: + self._assert_span_stub(span) + self._assert_real_span() From d1a7353faff5ba64642b625ef7a808556f8de689 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 24 Jan 2025 12:02:54 -0800 Subject: [PATCH 14/33] more generic code --- .../contrib/internal/dd_trace_api/patch.py | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index d5560f11711..feb0cd9e7c5 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -1,4 +1,5 @@ from sys import addaudithook +from typing import Optional import dd_trace_api @@ -6,38 +7,26 @@ _DD_HOOK_PREFIX = "dd.hooks." -_CURRENT_SPAN = None -_TRACER = ddtrace.tracer +_STATE = {"current_span": None, "tracer": ddtrace.tracer} -def _patched_start_span(*args, **kwargs): - global _CURRENT_SPAN - _CURRENT_SPAN = _TRACER.start_span(*args, **kwargs) +def _generic_patched(method_of, fn_name, store_return: Optional[str] = None): + def _inner(*args, **kwargs): + retval = getattr(_STATE[method_of], fn_name)(*args, **kwargs) + if store_return: + _STATE[store_return] = retval - -def _patched_trace(*args, **kwargs): - global _CURRENT_SPAN - _CURRENT_SPAN = _TRACER.trace(*args, **kwargs) - - -def _patched_span_enter(*args, **kwargs): - _CURRENT_SPAN.__enter__(*args, **kwargs) - - -def _patched_span_exit(*args, **kwargs): - _CURRENT_SPAN.__exit__(*args, **kwargs) - - -def _patched_span_finish(*args, **kwargs): - _CURRENT_SPAN.finish(*args, **kwargs) + return _inner _HANDLERS = { - "Tracer.start_span": _patched_start_span, - "Tracer.trace": _patched_trace, - "Span.__enter__": _patched_span_enter, - "Span.__exit__": _patched_span_exit, - "Span.finish": _patched_span_finish, + "Tracer.flush": _generic_patched("tracer", "flush"), + "Tracer.set_tags": _generic_patched("tracer", "set_tags"), + "Tracer.start_span": _generic_patched("tracer", "start_span", store_return="current_span"), + "Tracer.trace": _generic_patched("tracer", "trace", store_return="current_span"), + "Span.__enter__": _generic_patched("current_span", "__enter__"), + "Span.__exit__": _generic_patched("current_span", "__exit__"), + "Span.finish": _generic_patched("current_span", "finish"), } @@ -59,8 +48,7 @@ def patch(tracer=None): if getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = True - global _TRACER - _TRACER = tracer + _STATE["tracer"] = tracer if not getattr(dd_trace_api, "__dd_has_audit_hook", False): addaudithook(_hook) dd_trace_api.__dd_has_audit_hook = True From 1b3cec28b827b6635b216415bf3b5c5f98e4b662 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 07:48:40 -0800 Subject: [PATCH 15/33] methods on real spans to avoid bookkeeping --- ddtrace/contrib/internal/dd_trace_api/patch.py | 18 ++++++++++++++---- tests/contrib/dd_trace_api/test_integration.py | 5 ++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index feb0cd9e7c5..d1afe78a99c 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -7,13 +7,19 @@ _DD_HOOK_PREFIX = "dd.hooks." +# XXX the "current span" state should not be stored here, instead delegated to the real Tracer _STATE = {"current_span": None, "tracer": ddtrace.tracer} def _generic_patched(method_of, fn_name, store_return: Optional[str] = None): - def _inner(*args, **kwargs): - retval = getattr(_STATE[method_of], fn_name)(*args, **kwargs) + def _inner(shared_state, *args, **kwargs): + if "stub_self" in shared_state and method_of != "tracer": # XXX + operand = getattr(shared_state["stub_self"], "_" + method_of) + else: + operand = _STATE[method_of] + retval = getattr(operand, fn_name)(*args, **kwargs) if store_return: + shared_state[store_return] = retval _STATE[store_return] = retval return _inner @@ -30,14 +36,18 @@ def _inner(*args, **kwargs): } -def _hook(name, args): +def _hook(name, hook_args): if not dd_trace_api.__datadog_patch: return if name.startswith(_DD_HOOK_PREFIX): name_suffix = ".".join(name.split(".")[2:]) if name_suffix not in _HANDLERS: return - _HANDLERS[name_suffix](*(args[0][0]), **(args[0][1])) + print(hook_args) + kwargs = hook_args[0][1] + args = hook_args[0][0] + shared_state, args = args[0], args[1:] + _HANDLERS[name_suffix](shared_state, *args, **kwargs) def get_version() -> str: diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index d8a13e43772..0f8dd39658d 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -19,7 +19,10 @@ def tearDown(self): def _assert_span_stub(self, stub: Any): assert not isinstance(stub, dd_span_class), "Returned span object should be a stub" - assert not hasattr(stub, "span_id"), "Returned span stub should not support read operations" + assert hasattr(stub, "_current_span") + assert isinstance( + stub._current_span, dd_span_class + ), "Returned span stub should hold a private reference to the corresponding real Span" def _assert_real_span(self): spans = self.pop_spans() From d3a6196947ffd35d385a928f787f67ad99141c86 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 09:40:12 -0800 Subject: [PATCH 16/33] current_span APIs --- .../contrib/internal/dd_trace_api/patch.py | 21 ++++++------- .../contrib/dd_trace_api/test_integration.py | 30 +++++++++++++++---- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index d1afe78a99c..4b48cc0d805 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -7,14 +7,14 @@ _DD_HOOK_PREFIX = "dd.hooks." -# XXX the "current span" state should not be stored here, instead delegated to the real Tracer -_STATE = {"current_span": None, "tracer": ddtrace.tracer} +_STATE = {"tracer": ddtrace.tracer} +SELF_KEY = "stub_self" def _generic_patched(method_of, fn_name, store_return: Optional[str] = None): def _inner(shared_state, *args, **kwargs): - if "stub_self" in shared_state and method_of != "tracer": # XXX - operand = getattr(shared_state["stub_self"], "_" + method_of) + if SELF_KEY in shared_state: # XXX + operand = getattr(shared_state[SELF_KEY], "_" + method_of) else: operand = _STATE[method_of] retval = getattr(operand, fn_name)(*args, **kwargs) @@ -28,11 +28,13 @@ def _inner(shared_state, *args, **kwargs): _HANDLERS = { "Tracer.flush": _generic_patched("tracer", "flush"), "Tracer.set_tags": _generic_patched("tracer", "set_tags"), - "Tracer.start_span": _generic_patched("tracer", "start_span", store_return="current_span"), - "Tracer.trace": _generic_patched("tracer", "trace", store_return="current_span"), - "Span.__enter__": _generic_patched("current_span", "__enter__"), - "Span.__exit__": _generic_patched("current_span", "__exit__"), - "Span.finish": _generic_patched("current_span", "finish"), + "Tracer.start_span": _generic_patched("tracer", "start_span", store_return="real_span"), + "Tracer.trace": _generic_patched("tracer", "trace", store_return="real_span"), + "Tracer.current_span": _generic_patched("tracer", "current_span", store_return="real_span"), + "Tracer.current_root_span": _generic_patched("tracer", "current_root_span", store_return="real_span"), + "Span.__enter__": _generic_patched("real_span", "__enter__"), + "Span.__exit__": _generic_patched("real_span", "__exit__"), + "Span.finish": _generic_patched("real_span", "finish"), } @@ -43,7 +45,6 @@ def _hook(name, hook_args): name_suffix = ".".join(name.split(".")[2:]) if name_suffix not in _HANDLERS: return - print(hook_args) kwargs = hook_args[0][1] args = hook_args[0][0] shared_state, args = args[0], args[1:] diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 0f8dd39658d..906bd77d8e8 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -19,14 +19,14 @@ def tearDown(self): def _assert_span_stub(self, stub: Any): assert not isinstance(stub, dd_span_class), "Returned span object should be a stub" - assert hasattr(stub, "_current_span") + assert hasattr(stub, "_real_span") assert isinstance( - stub._current_span, dd_span_class + stub._real_span, dd_span_class ), "Returned span stub should hold a private reference to the corresponding real Span" - def _assert_real_span(self): + def _assert_real_spans(self, count=1): spans = self.pop_spans() - assert len(spans) == 1 + assert len(spans) == count generated_span = spans[0] assert isinstance(generated_span, dd_span_class), "Generated span is a real span" assert hasattr(generated_span, "span_id"), "Generated span should support read operations" @@ -34,9 +34,27 @@ def _assert_real_span(self): def test_start_span(self): with dd_trace_api.tracer.Tracer().start_span("web.request") as span: self._assert_span_stub(span) - self._assert_real_span() + self._assert_real_spans() def test_trace(self): with dd_trace_api.tracer.Tracer().trace("web.request") as span: self._assert_span_stub(span) - self._assert_real_span() + self._assert_real_spans() + + def test_current_span(self): + tracer = dd_trace_api.tracer.Tracer() + with tracer.trace("web.request"): + span = tracer.current_span() + self._assert_span_stub(span) + self._assert_real_spans() + + def test_current_root_span(self): + tracer = dd_trace_api.tracer.Tracer() + with tracer.trace("web.request"): + span = tracer.current_root_span() + self._assert_span_stub(span) + with tracer.trace("web.other.request"): + root_from_nested = tracer.current_root_span() + self._assert_span_stub(root_from_nested) + assert span._real_span == root_from_nested._real_span + self._assert_real_spans(2) From 026c06dbf1d80ebcd46c57c5cf30ca35cb98c7f3 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 09:47:49 -0800 Subject: [PATCH 17/33] use singleton tracer --- .../contrib/dd_trace_api/test_integration.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 906bd77d8e8..3b70f3037dc 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -31,30 +31,31 @@ def _assert_real_spans(self, count=1): assert isinstance(generated_span, dd_span_class), "Generated span is a real span" assert hasattr(generated_span, "span_id"), "Generated span should support read operations" + def test_tracer_singleton(self): + assert isinstance(dd_trace_api.tracer, dd_trace_api.Tracer), "Tracer stub should be exposed as a singleton" + def test_start_span(self): - with dd_trace_api.tracer.Tracer().start_span("web.request") as span: + with dd_trace_api.tracer.start_span("web.request") as span: self._assert_span_stub(span) self._assert_real_spans() def test_trace(self): - with dd_trace_api.tracer.Tracer().trace("web.request") as span: + with dd_trace_api.tracer.trace("web.request") as span: self._assert_span_stub(span) self._assert_real_spans() def test_current_span(self): - tracer = dd_trace_api.tracer.Tracer() - with tracer.trace("web.request"): - span = tracer.current_span() + with dd_trace_api.tracer.trace("web.request"): + span = dd_trace_api.tracer.current_span() self._assert_span_stub(span) self._assert_real_spans() def test_current_root_span(self): - tracer = dd_trace_api.tracer.Tracer() - with tracer.trace("web.request"): - span = tracer.current_root_span() + with dd_trace_api.tracer.trace("web.request"): + span = dd_trace_api.tracer.current_root_span() self._assert_span_stub(span) - with tracer.trace("web.other.request"): - root_from_nested = tracer.current_root_span() + with dd_trace_api.tracer.trace("web.other.request"): + root_from_nested = dd_trace_api.tracer.current_root_span() self._assert_span_stub(root_from_nested) assert span._real_span == root_from_nested._real_span self._assert_real_spans(2) From e1f1a6830160747963701c388052725dac92754c Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 10:01:26 -0800 Subject: [PATCH 18/33] convert stubs to real spans when passed to API functions --- .../contrib/internal/dd_trace_api/patch.py | 52 +++++++++++++++---- .../contrib/dd_trace_api/test_integration.py | 13 +++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 4b48cc0d805..ebb41d6fc36 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -1,5 +1,8 @@ from sys import addaudithook +from typing import Dict +from typing import List from typing import Optional +from typing import Tuple import dd_trace_api @@ -11,12 +14,29 @@ SELF_KEY = "stub_self" -def _generic_patched(method_of, fn_name, store_return: Optional[str] = None): +def _proxy_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: + proxied_args = [] + for arg in args: + if isinstance(arg, dd_trace_api.Span): + proxied_args.append(arg._real_span) + else: + proxied_args.append(arg) + proxied_kwargs = {} + for name, kwarg in kwargs.items(): + if isinstance(kwarg, dd_trace_api.Span): + proxied_kwargs[name] = kwarg._real_span + else: + proxied_kwargs[name] = kwarg + return proxied_args, proxied_kwargs + + +def _patched(method_of, fn_name, store_return: Optional[str] = None): def _inner(shared_state, *args, **kwargs): - if SELF_KEY in shared_state: # XXX + if SELF_KEY in shared_state: operand = getattr(shared_state[SELF_KEY], "_" + method_of) else: operand = _STATE[method_of] + args, kwargs = _proxy_arguments(args, kwargs) retval = getattr(operand, fn_name)(*args, **kwargs) if store_return: shared_state[store_return] = retval @@ -26,15 +46,25 @@ def _inner(shared_state, *args, **kwargs): _HANDLERS = { - "Tracer.flush": _generic_patched("tracer", "flush"), - "Tracer.set_tags": _generic_patched("tracer", "set_tags"), - "Tracer.start_span": _generic_patched("tracer", "start_span", store_return="real_span"), - "Tracer.trace": _generic_patched("tracer", "trace", store_return="real_span"), - "Tracer.current_span": _generic_patched("tracer", "current_span", store_return="real_span"), - "Tracer.current_root_span": _generic_patched("tracer", "current_root_span", store_return="real_span"), - "Span.__enter__": _generic_patched("real_span", "__enter__"), - "Span.__exit__": _generic_patched("real_span", "__exit__"), - "Span.finish": _generic_patched("real_span", "finish"), + "Tracer.flush": _patched("tracer", "flush"), + "Tracer.shutdown": _patched("tracer", "shutdown"), + "Tracer.set_tags": _patched("tracer", "set_tags"), + "Tracer.start_span": _patched("tracer", "start_span", store_return="real_span"), + "Tracer.trace": _patched("tracer", "trace", store_return="real_span"), + "Tracer.current_span": _patched("tracer", "current_span", store_return="real_span"), + "Tracer.current_root_span": _patched("tracer", "current_root_span", store_return="real_span"), + "Span.__enter__": _patched("real_span", "__enter__"), + "Span.__exit__": _patched("real_span", "__exit__"), + "Span.finish": _patched("real_span", "finish"), + "Span.finish_with_ancestors": _patched("real_span", "finish_with_ancestors"), + "Span.set_exc_info": _patched("real_span", "set_exc_info"), + "Span.set_link": _patched("real_span", "set_link"), + "Span.set_traceback": _patched("real_span", "set_traceback"), + "Span.link_span": _patched("real_span", "link_span"), + "Span.set_tags": _patched("real_span", "set_tags"), + "Span.set_tag": _patched("real_span", "set_tag"), + "Span.set_tag_str": _patched("real_span", "set_tag_str"), + "Span.set_struct_tag": _patched("real_span", "set_struct_tag"), } diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 3b70f3037dc..03ab2781956 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -14,6 +14,7 @@ def setUp(self): patch(tracer=self.tracer) def tearDown(self): + self.pop_spans() super(DDTraceAPITestCase, self).tearDown() unpatch() @@ -39,6 +40,18 @@ def test_start_span(self): self._assert_span_stub(span) self._assert_real_spans() + def test_span_finish(self): + span = dd_trace_api.tracer.start_span("web.request") + self._assert_span_stub(span) + span.finish() + self._assert_real_spans() + + def test_span_finish_with_ancestors(self): + span = dd_trace_api.tracer.start_span("web.request") + child_span = dd_trace_api.tracer.start_span("web.request", child_of=span) + child_span.finish_with_ancestors() + self._assert_real_spans(2) + def test_trace(self): with dd_trace_api.tracer.trace("web.request") as span: self._assert_span_stub(span) From 36cdb433a9288f770257ba9a4cfacad7d0ef054a Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 10:19:17 -0800 Subject: [PATCH 19/33] slight refactors --- ddtrace/contrib/internal/dd_trace_api/patch.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index ebb41d6fc36..5e66bc24e91 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -14,7 +14,8 @@ SELF_KEY = "stub_self" -def _proxy_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: +def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: + """Convert all dd_trace_api.Span objects in the args/kwargs collections to their held ddtrace.Span objects""" proxied_args = [] for arg in args: if isinstance(arg, dd_trace_api.Span): @@ -31,15 +32,16 @@ def _proxy_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: def _patched(method_of, fn_name, store_return: Optional[str] = None): - def _inner(shared_state, *args, **kwargs): - if SELF_KEY in shared_state: - operand = getattr(shared_state[SELF_KEY], "_" + method_of) + def _inner(state_shared_with_api, *args, **kwargs): + stub_shares_selfref = SELF_KEY in state_shared_with_api + if stub_shares_selfref: + operand = getattr(state_shared_with_api[SELF_KEY], "_" + method_of) else: operand = _STATE[method_of] - args, kwargs = _proxy_arguments(args, kwargs) + args, kwargs = _proxy_span_arguments(args, kwargs) retval = getattr(operand, fn_name)(*args, **kwargs) if store_return: - shared_state[store_return] = retval + state_shared_with_api[store_return] = retval _STATE[store_return] = retval return _inner @@ -77,8 +79,8 @@ def _hook(name, hook_args): return kwargs = hook_args[0][1] args = hook_args[0][0] - shared_state, args = args[0], args[1:] - _HANDLERS[name_suffix](shared_state, *args, **kwargs) + state_shared_with_api, args = args[0], args[1:] + _HANDLERS[name_suffix](state_shared_with_api, *args, **kwargs) def get_version() -> str: From 9bba449722cb45dcc1feeff15e3dfd3d633afcff Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 10:26:15 -0800 Subject: [PATCH 20/33] remove some duplication from handlers list --- ddtrace/contrib/internal/dd_trace_api/patch.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 5e66bc24e91..bdd0fc0100a 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -41,16 +41,18 @@ def _inner(state_shared_with_api, *args, **kwargs): args, kwargs = _proxy_span_arguments(args, kwargs) retval = getattr(operand, fn_name)(*args, **kwargs) if store_return: - state_shared_with_api[store_return] = retval - _STATE[store_return] = retval + if store_return in _STATE: + _STATE[store_return] = retval + else: + state_shared_with_api[store_return] = retval return _inner _HANDLERS = { - "Tracer.flush": _patched("tracer", "flush"), - "Tracer.shutdown": _patched("tracer", "shutdown"), - "Tracer.set_tags": _patched("tracer", "set_tags"), + "Tracer.flush": None, + "Tracer.shutdown": None, + "Tracer.set_tags": None, "Tracer.start_span": _patched("tracer", "start_span", store_return="real_span"), "Tracer.trace": _patched("tracer", "trace", store_return="real_span"), "Tracer.current_span": _patched("tracer", "current_span", store_return="real_span"), @@ -80,7 +82,10 @@ def _hook(name, hook_args): kwargs = hook_args[0][1] args = hook_args[0][0] state_shared_with_api, args = args[0], args[1:] - _HANDLERS[name_suffix](state_shared_with_api, *args, **kwargs) + handler = _HANDLERS[name_suffix] + if handler is None: + handler = _patched(*(a.lower() for a in name_suffix.split("."))) + handler(state_shared_with_api, *args, **kwargs) def get_version() -> str: From 2448b3ed48e260566ace1e9a436bd2e2b152582b Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 10:30:48 -0800 Subject: [PATCH 21/33] remove some duplication from handlers list --- .../contrib/internal/dd_trace_api/patch.py | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index bdd0fc0100a..8f7dd102bd7 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -12,6 +12,7 @@ _DD_HOOK_PREFIX = "dd.hooks." _STATE = {"tracer": ddtrace.tracer} SELF_KEY = "stub_self" +REAL_SPAN_KEY = "real_span" def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: @@ -53,22 +54,22 @@ def _inner(state_shared_with_api, *args, **kwargs): "Tracer.flush": None, "Tracer.shutdown": None, "Tracer.set_tags": None, - "Tracer.start_span": _patched("tracer", "start_span", store_return="real_span"), - "Tracer.trace": _patched("tracer", "trace", store_return="real_span"), - "Tracer.current_span": _patched("tracer", "current_span", store_return="real_span"), - "Tracer.current_root_span": _patched("tracer", "current_root_span", store_return="real_span"), - "Span.__enter__": _patched("real_span", "__enter__"), - "Span.__exit__": _patched("real_span", "__exit__"), - "Span.finish": _patched("real_span", "finish"), - "Span.finish_with_ancestors": _patched("real_span", "finish_with_ancestors"), - "Span.set_exc_info": _patched("real_span", "set_exc_info"), - "Span.set_link": _patched("real_span", "set_link"), - "Span.set_traceback": _patched("real_span", "set_traceback"), - "Span.link_span": _patched("real_span", "link_span"), - "Span.set_tags": _patched("real_span", "set_tags"), - "Span.set_tag": _patched("real_span", "set_tag"), - "Span.set_tag_str": _patched("real_span", "set_tag_str"), - "Span.set_struct_tag": _patched("real_span", "set_struct_tag"), + "Tracer.start_span": _patched("tracer", "start_span", store_return=REAL_SPAN_KEY), + "Tracer.trace": _patched("tracer", "trace", store_return=REAL_SPAN_KEY), + "Tracer.current_span": _patched("tracer", "current_span", store_return=REAL_SPAN_KEY), + "Tracer.current_root_span": _patched("tracer", "current_root_span", store_return=REAL_SPAN_KEY), + "Span.__enter__": None, + "Span.__exit__": None, + "Span.finish": None, + "Span.finish_with_ancestors": None, + "Span.set_exc_info": None, + "Span.set_link": None, + "Span.set_traceback": None, + "Span.link_span": None, + "Span.set_tags": None, + "Span.set_tag": None, + "Span.set_tag_str": None, + "Span.set_struct_tag": None, } @@ -84,7 +85,7 @@ def _hook(name, hook_args): state_shared_with_api, args = args[0], args[1:] handler = _HANDLERS[name_suffix] if handler is None: - handler = _patched(*(a.lower() for a in name_suffix.split("."))) + handler = _patched(*(REAL_SPAN_KEY if a == "Span" else a.lower() for a in name_suffix.split("."))) handler(state_shared_with_api, *args, **kwargs) From 35d8a4965cf6d2b31f10855117f2f27f04a7c13c Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 27 Jan 2025 11:54:12 -0800 Subject: [PATCH 22/33] reno --- .../notes/dd-trace-api-integration-0fa7ea051a4d6ce1.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 releasenotes/notes/dd-trace-api-integration-0fa7ea051a4d6ce1.yaml diff --git a/releasenotes/notes/dd-trace-api-integration-0fa7ea051a4d6ce1.yaml b/releasenotes/notes/dd-trace-api-integration-0fa7ea051a4d6ce1.yaml new file mode 100644 index 00000000000..9b5d2efaaf2 --- /dev/null +++ b/releasenotes/notes/dd-trace-api-integration-0fa7ea051a4d6ce1.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + dd-trace-api: adds a simple and minimal instrumentation for the dd-trace-api-py package From b787857136f8d959c19af36fd6c84d3bead31f9d Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 28 Jan 2025 10:57:00 -0800 Subject: [PATCH 23/33] simplify --- .../contrib/internal/dd_trace_api/patch.py | 32 +++++++++---------- .../contrib/dd_trace_api/test_integration.py | 5 --- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 8f7dd102bd7..02771efe948 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -3,6 +3,7 @@ from typing import List from typing import Optional from typing import Tuple +import weakref import dd_trace_api @@ -11,6 +12,7 @@ _DD_HOOK_PREFIX = "dd.hooks." _STATE = {"tracer": ddtrace.tracer} +_STUB_TO_SPAN = weakref.WeakKeyDictionary() SELF_KEY = "stub_self" REAL_SPAN_KEY = "real_span" @@ -20,32 +22,30 @@ def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: proxied_args = [] for arg in args: if isinstance(arg, dd_trace_api.Span): - proxied_args.append(arg._real_span) + proxied_args.append(_STUB_TO_SPAN[arg]) else: proxied_args.append(arg) proxied_kwargs = {} for name, kwarg in kwargs.items(): if isinstance(kwarg, dd_trace_api.Span): - proxied_kwargs[name] = kwarg._real_span + proxied_kwargs[name] = _STUB_TO_SPAN[kwarg] else: proxied_kwargs[name] = kwarg return proxied_args, proxied_kwargs -def _patched(method_of, fn_name, store_return: Optional[str] = None): +def _patched(method_of, fn_name): def _inner(state_shared_with_api, *args, **kwargs): - stub_shares_selfref = SELF_KEY in state_shared_with_api - if stub_shares_selfref: - operand = getattr(state_shared_with_api[SELF_KEY], "_" + method_of) + operand_stub = state_shared_with_api.get(SELF_KEY) + if operand_stub: + operand = _STUB_TO_SPAN[operand_stub] else: operand = _STATE[method_of] args, kwargs = _proxy_span_arguments(args, kwargs) retval = getattr(operand, fn_name)(*args, **kwargs) - if store_return: - if store_return in _STATE: - _STATE[store_return] = retval - else: - state_shared_with_api[store_return] = retval + api_return_value = state_shared_with_api.get("api_return_value") + if isinstance(api_return_value, dd_trace_api.Span): + _STUB_TO_SPAN[api_return_value] = retval return _inner @@ -54,10 +54,10 @@ def _inner(state_shared_with_api, *args, **kwargs): "Tracer.flush": None, "Tracer.shutdown": None, "Tracer.set_tags": None, - "Tracer.start_span": _patched("tracer", "start_span", store_return=REAL_SPAN_KEY), - "Tracer.trace": _patched("tracer", "trace", store_return=REAL_SPAN_KEY), - "Tracer.current_span": _patched("tracer", "current_span", store_return=REAL_SPAN_KEY), - "Tracer.current_root_span": _patched("tracer", "current_root_span", store_return=REAL_SPAN_KEY), + "Tracer.start_span": _patched("tracer", "start_span"), + "Tracer.trace": _patched("tracer", "trace"), + "Tracer.current_span": _patched("tracer", "current_span"), + "Tracer.current_root_span": _patched("tracer", "current_root_span"), "Span.__enter__": None, "Span.__exit__": None, "Span.finish": None, @@ -85,7 +85,7 @@ def _hook(name, hook_args): state_shared_with_api, args = args[0], args[1:] handler = _HANDLERS[name_suffix] if handler is None: - handler = _patched(*(REAL_SPAN_KEY if a == "Span" else a.lower() for a in name_suffix.split("."))) + handler = _patched(*(name_suffix.split("."))) handler(state_shared_with_api, *args, **kwargs) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 03ab2781956..6360ed06d32 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -20,10 +20,6 @@ def tearDown(self): def _assert_span_stub(self, stub: Any): assert not isinstance(stub, dd_span_class), "Returned span object should be a stub" - assert hasattr(stub, "_real_span") - assert isinstance( - stub._real_span, dd_span_class - ), "Returned span stub should hold a private reference to the corresponding real Span" def _assert_real_spans(self, count=1): spans = self.pop_spans() @@ -70,5 +66,4 @@ def test_current_root_span(self): with dd_trace_api.tracer.trace("web.other.request"): root_from_nested = dd_trace_api.tracer.current_root_span() self._assert_span_stub(root_from_nested) - assert span._real_span == root_from_nested._real_span self._assert_real_spans(2) From 86d69dd964f5207bcbd821652a5d3c23f2c556f5 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 28 Jan 2025 11:09:24 -0800 Subject: [PATCH 24/33] simplify --- .../contrib/internal/dd_trace_api/patch.py | 61 ++++--------------- 1 file changed, 13 insertions(+), 48 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 02771efe948..cba0bf7f782 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -11,10 +11,9 @@ _DD_HOOK_PREFIX = "dd.hooks." -_STATE = {"tracer": ddtrace.tracer} +_TRACER_KEY = "Tracer" +_STATE = {_TRACER_KEY: ddtrace.tracer} _STUB_TO_SPAN = weakref.WeakKeyDictionary() -SELF_KEY = "stub_self" -REAL_SPAN_KEY = "real_span" def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: @@ -36,57 +35,23 @@ def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: def _patched(method_of, fn_name): def _inner(state_shared_with_api, *args, **kwargs): - operand_stub = state_shared_with_api.get(SELF_KEY) - if operand_stub: - operand = _STUB_TO_SPAN[operand_stub] - else: - operand = _STATE[method_of] + retval_from_api = state_shared_with_api.get("api_return_value") + operand_stub = state_shared_with_api.get("stub_self") args, kwargs = _proxy_span_arguments(args, kwargs) - retval = getattr(operand, fn_name)(*args, **kwargs) - api_return_value = state_shared_with_api.get("api_return_value") - if isinstance(api_return_value, dd_trace_api.Span): - _STUB_TO_SPAN[api_return_value] = retval + retval_from_impl = getattr(_STUB_TO_SPAN[operand_stub] if operand_stub else _STATE[method_of], fn_name)( + *args, **kwargs + ) + if isinstance(retval_from_api, dd_trace_api.Span): + _STUB_TO_SPAN[retval_from_api] = retval_from_impl return _inner -_HANDLERS = { - "Tracer.flush": None, - "Tracer.shutdown": None, - "Tracer.set_tags": None, - "Tracer.start_span": _patched("tracer", "start_span"), - "Tracer.trace": _patched("tracer", "trace"), - "Tracer.current_span": _patched("tracer", "current_span"), - "Tracer.current_root_span": _patched("tracer", "current_root_span"), - "Span.__enter__": None, - "Span.__exit__": None, - "Span.finish": None, - "Span.finish_with_ancestors": None, - "Span.set_exc_info": None, - "Span.set_link": None, - "Span.set_traceback": None, - "Span.link_span": None, - "Span.set_tags": None, - "Span.set_tag": None, - "Span.set_tag_str": None, - "Span.set_struct_tag": None, -} - - def _hook(name, hook_args): - if not dd_trace_api.__datadog_patch: + if not dd_trace_api.__datadog_patch or not name.startswith(_DD_HOOK_PREFIX): return - if name.startswith(_DD_HOOK_PREFIX): - name_suffix = ".".join(name.split(".")[2:]) - if name_suffix not in _HANDLERS: - return - kwargs = hook_args[0][1] - args = hook_args[0][0] - state_shared_with_api, args = args[0], args[1:] - handler = _HANDLERS[name_suffix] - if handler is None: - handler = _patched(*(name_suffix.split("."))) - handler(state_shared_with_api, *args, **kwargs) + args = hook_args[0][0] + _patched(*(name.replace(_DD_HOOK_PREFIX, "").split(".")))(args[0], *args[1:], **hook_args[0][1]) def get_version() -> str: @@ -97,7 +62,7 @@ def patch(tracer=None): if getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = True - _STATE["tracer"] = tracer + _STATE[_TRACER_KEY] = tracer if not getattr(dd_trace_api, "__dd_has_audit_hook", False): addaudithook(_hook) dd_trace_api.__dd_has_audit_hook = True From bae4cac3d779a443a50b1af98e88cbc4dbbbf84f Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 28 Jan 2025 12:40:44 -0800 Subject: [PATCH 25/33] unused import --- ddtrace/contrib/internal/dd_trace_api/patch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index cba0bf7f782..85c538af23b 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -1,7 +1,6 @@ from sys import addaudithook from typing import Dict from typing import List -from typing import Optional from typing import Tuple import weakref From ee4492c591b3fea2f40da33d6d64d7673e3463a6 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 28 Jan 2025 13:01:58 -0800 Subject: [PATCH 26/33] some more tests --- .../contrib/dd_trace_api/test_integration.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 6360ed06d32..ecd2be489d6 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -1,3 +1,4 @@ +import sys from typing import Any import dd_trace_api @@ -27,6 +28,7 @@ def _assert_real_spans(self, count=1): generated_span = spans[0] assert isinstance(generated_span, dd_span_class), "Generated span is a real span" assert hasattr(generated_span, "span_id"), "Generated span should support read operations" + return spans def test_tracer_singleton(self): assert isinstance(dd_trace_api.tracer, dd_trace_api.Tracer), "Tracer stub should be exposed as a singleton" @@ -67,3 +69,48 @@ def test_current_root_span(self): root_from_nested = dd_trace_api.tracer.current_root_span() self._assert_span_stub(root_from_nested) self._assert_real_spans(2) + + def test_set_link(self): + pass + + def test_link_span(self): + pass + + def test_set_traceback(self): + with dd_trace_api.tracer.trace("web.request") as span: + try: + raise Exception + except Exception: # noqa + span.set_traceback() + spans = self._assert_real_spans() + assert "error.stack" in spans[0]._meta + + def test_set_exc_info(self): + with dd_trace_api.tracer.trace("web.request") as span: + try: + raise Exception + except Exception: # noqa + span.set_exc_info(*sys.exc_info()) + spans = self._assert_real_spans() + assert "error.message" in spans[0]._meta + assert "error.stack" in spans[0]._meta + assert "error.type" in spans[0]._meta + + def test_set_tags(self): + with dd_trace_api.tracer.trace("web.request") as span: + span.set_tag("foo", "bar") + spans = self._assert_real_spans() + assert spans[0]._meta["foo"] == "bar", "Tag set via API should be applied to the real spans" + + with dd_trace_api.tracer.trace("web.request") as span: + span.set_tags({"tag1": "value1", "tag2": "value2"}) + spans = self._assert_real_spans() + assert spans[0]._meta["tag1"] == "value1", "Tag set via API should be applied to the real spans" + assert spans[0]._meta["tag2"] == "value2", "Tag set via API should be applied to the real spans" + + with dd_trace_api.tracer.trace("web.request") as span: + span.set_struct_tag("tag1", {"tag2": "value2"}) + spans = self._assert_real_spans() + assert spans[0]._meta_struct["tag1"] == { + "tag2": "value2" + }, "Tag set via API should be applied to the real spans" From 17552dec2d4e3223f02fab3e9b3c5bf91b79f6d6 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 3 Feb 2025 09:54:09 -0800 Subject: [PATCH 27/33] add patching for tracer.wrap, which requires API accessing the return value of tracer.wrap --- .../contrib/internal/dd_trace_api/patch.py | 20 +++++++++---------- .../contrib/dd_trace_api/test_integration.py | 9 +++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 85c538af23b..30871bd2c9a 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -11,8 +11,8 @@ _DD_HOOK_PREFIX = "dd.hooks." _TRACER_KEY = "Tracer" -_STATE = {_TRACER_KEY: ddtrace.tracer} -_STUB_TO_SPAN = weakref.WeakKeyDictionary() +_STUB_TO_REAL = weakref.WeakKeyDictionary() +_STUB_TO_REAL[dd_trace_api.tracer] = ddtrace.tracer def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: @@ -20,13 +20,13 @@ def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: proxied_args = [] for arg in args: if isinstance(arg, dd_trace_api.Span): - proxied_args.append(_STUB_TO_SPAN[arg]) + proxied_args.append(_STUB_TO_REAL[arg]) else: proxied_args.append(arg) proxied_kwargs = {} for name, kwarg in kwargs.items(): if isinstance(kwarg, dd_trace_api.Span): - proxied_kwargs[name] = _STUB_TO_SPAN[kwarg] + proxied_kwargs[name] = _STUB_TO_REAL[kwarg] else: proxied_kwargs[name] = kwarg return proxied_args, proxied_kwargs @@ -37,11 +37,11 @@ def _inner(state_shared_with_api, *args, **kwargs): retval_from_api = state_shared_with_api.get("api_return_value") operand_stub = state_shared_with_api.get("stub_self") args, kwargs = _proxy_span_arguments(args, kwargs) - retval_from_impl = getattr(_STUB_TO_SPAN[operand_stub] if operand_stub else _STATE[method_of], fn_name)( - *args, **kwargs - ) - if isinstance(retval_from_api, dd_trace_api.Span): - _STUB_TO_SPAN[retval_from_api] = retval_from_impl + retval_from_impl = getattr(_STUB_TO_REAL[operand_stub], fn_name)(*args, **kwargs) + if "impl_return_value" in state_shared_with_api: + state_shared_with_api["impl_return_value"] = retval_from_impl + if retval_from_api is not None: + _STUB_TO_REAL[retval_from_api] = retval_from_impl return _inner @@ -61,7 +61,7 @@ def patch(tracer=None): if getattr(dd_trace_api, "__datadog_patch", False): return dd_trace_api.__datadog_patch = True - _STATE[_TRACER_KEY] = tracer + _STUB_TO_REAL[dd_trace_api.tracer] = tracer if not getattr(dd_trace_api, "__dd_has_audit_hook", False): addaudithook(_hook) dd_trace_api.__dd_has_audit_hook = True diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index ecd2be489d6..4ffdb025fda 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -70,6 +70,15 @@ def test_current_root_span(self): self._assert_span_stub(root_from_nested) self._assert_real_spans(2) + def test_wrap(self): + @dd_trace_api.tracer.wrap() + def foo(): + return 1 + 1 + + result = foo() + assert result == 2 + self._assert_real_spans() + def test_set_link(self): pass From d14706729b98775e19ff80a58f8250cd4cf98911 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 3 Feb 2025 11:44:56 -0800 Subject: [PATCH 28/33] remove tests for removed functionality --- tests/contrib/dd_trace_api/test_integration.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index 4ffdb025fda..f46348837dc 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -106,20 +106,8 @@ def test_set_exc_info(self): assert "error.type" in spans[0]._meta def test_set_tags(self): - with dd_trace_api.tracer.trace("web.request") as span: - span.set_tag("foo", "bar") - spans = self._assert_real_spans() - assert spans[0]._meta["foo"] == "bar", "Tag set via API should be applied to the real spans" - with dd_trace_api.tracer.trace("web.request") as span: span.set_tags({"tag1": "value1", "tag2": "value2"}) spans = self._assert_real_spans() assert spans[0]._meta["tag1"] == "value1", "Tag set via API should be applied to the real spans" assert spans[0]._meta["tag2"] == "value2", "Tag set via API should be applied to the real spans" - - with dd_trace_api.tracer.trace("web.request") as span: - span.set_struct_tag("tag1", {"tag2": "value2"}) - spans = self._assert_real_spans() - assert spans[0]._meta_struct["tag1"] == { - "tag2": "value2" - }, "Tag set via API should be applied to the real spans" From db87343688132121fdef0476e3f4e3d59f0fd5ca Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 4 Feb 2025 11:21:46 -0800 Subject: [PATCH 29/33] remove tests --- tests/contrib/dd_trace_api/test_integration.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/contrib/dd_trace_api/test_integration.py b/tests/contrib/dd_trace_api/test_integration.py index f46348837dc..560d198191e 100644 --- a/tests/contrib/dd_trace_api/test_integration.py +++ b/tests/contrib/dd_trace_api/test_integration.py @@ -79,12 +79,6 @@ def foo(): assert result == 2 self._assert_real_spans() - def test_set_link(self): - pass - - def test_link_span(self): - pass - def test_set_traceback(self): with dd_trace_api.tracer.trace("web.request") as span: try: From e043f224aec5de0dbc46626c8794a56fb0827a90 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 4 Feb 2025 12:08:16 -0800 Subject: [PATCH 30/33] code organization --- ddtrace/contrib/dd_trace_api/__init__.py | 23 ---------- .../contrib/internal/dd_trace_api/patch.py | 46 +++++++++++++------ 2 files changed, 33 insertions(+), 36 deletions(-) delete mode 100644 ddtrace/contrib/dd_trace_api/__init__.py diff --git a/ddtrace/contrib/dd_trace_api/__init__.py b/ddtrace/contrib/dd_trace_api/__init__.py deleted file mode 100644 index 10a065bb8f7..00000000000 --- a/ddtrace/contrib/dd_trace_api/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -""" - -Enabling -~~~~~~~~ - -The dd_trace_api integration is enabled automatically when using -:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. - -Or use :func:`patch()` to manually enable the integration:: - - from ddtrace import patch - patch(dd_trace_api=True) - - - -Global Configuration -~~~~~~~~~~~~~~~~~~~~ - - -Instance Configuration -~~~~~~~~~~~~~~~~~~~~~~ - -""" diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 30871bd2c9a..b774b50fe58 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -1,6 +1,8 @@ from sys import addaudithook +from typing import Any from typing import Dict from typing import List +from typing import Optional from typing import Tuple import weakref @@ -32,25 +34,43 @@ def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: return proxied_args, proxied_kwargs -def _patched(method_of, fn_name): - def _inner(state_shared_with_api, *args, **kwargs): - retval_from_api = state_shared_with_api.get("api_return_value") - operand_stub = state_shared_with_api.get("stub_self") - args, kwargs = _proxy_span_arguments(args, kwargs) - retval_from_impl = getattr(_STUB_TO_REAL[operand_stub], fn_name)(*args, **kwargs) - if "impl_return_value" in state_shared_with_api: - state_shared_with_api["impl_return_value"] = retval_from_impl - if retval_from_api is not None: - _STUB_TO_REAL[retval_from_api] = retval_from_impl - - return _inner +def _call_on_real_instance( + operand_stub: dd_trace_api._Stub, + method_name: str, + retval_from_api: Optional[Any], + state_shared_with_api: Dict, + *args: List, + **kwargs: Dict +) -> None: + """ + Call `method_name` on the real object corresponding to `operand_stub` with `args` and `kwargs` as arguments. + Pass the return value back to the API layer via the mutable `state_shared_with_api`. + + Store the value that will be returned from the API call we're in the middle of, for the purpose + of mapping from those Stub objects to their real counterparts. + """ + args, kwargs = _proxy_span_arguments(args, kwargs) + retval_from_impl = getattr(_STUB_TO_REAL[operand_stub], method_name)(*args, **kwargs) + if "impl_return_value" in state_shared_with_api: + state_shared_with_api["impl_return_value"] = retval_from_impl + if retval_from_api is not None: + _STUB_TO_REAL[retval_from_api] = retval_from_impl def _hook(name, hook_args): + """Called in response to `sys.audit` events""" if not dd_trace_api.__datadog_patch or not name.startswith(_DD_HOOK_PREFIX): return args = hook_args[0][0] - _patched(*(name.replace(_DD_HOOK_PREFIX, "").split(".")))(args[0], *args[1:], **hook_args[0][1]) + state_shared_with_api = args[0] + _call_on_real_instance( + state_shared_with_api.get("stub_self"), + name.replace(_DD_HOOK_PREFIX, "").rsplit(".", 1)[-1], + state_shared_with_api.get("api_return_value"), + state_shared_with_api, + *args[1:], + **hook_args[0][1] + ) def get_version() -> str: From 5540bd1ef0765044cebc64c7647c786daa88fe82 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Wed, 5 Feb 2025 07:45:57 -0800 Subject: [PATCH 31/33] no shared state --- .../contrib/internal/dd_trace_api/patch.py | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index b774b50fe58..5d876a12588 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -35,24 +35,16 @@ def _proxy_span_arguments(args: List, kwargs: Dict) -> Tuple[List, Dict]: def _call_on_real_instance( - operand_stub: dd_trace_api._Stub, - method_name: str, - retval_from_api: Optional[Any], - state_shared_with_api: Dict, - *args: List, - **kwargs: Dict + operand_stub: dd_trace_api._Stub, method_name: str, retval_from_api: Optional[Any], *args: List, **kwargs: Dict ) -> None: """ Call `method_name` on the real object corresponding to `operand_stub` with `args` and `kwargs` as arguments. - Pass the return value back to the API layer via the mutable `state_shared_with_api`. Store the value that will be returned from the API call we're in the middle of, for the purpose of mapping from those Stub objects to their real counterparts. """ args, kwargs = _proxy_span_arguments(args, kwargs) retval_from_impl = getattr(_STUB_TO_REAL[operand_stub], method_name)(*args, **kwargs) - if "impl_return_value" in state_shared_with_api: - state_shared_with_api["impl_return_value"] = retval_from_impl if retval_from_api is not None: _STUB_TO_REAL[retval_from_api] = retval_from_impl @@ -62,14 +54,10 @@ def _hook(name, hook_args): if not dd_trace_api.__datadog_patch or not name.startswith(_DD_HOOK_PREFIX): return args = hook_args[0][0] - state_shared_with_api = args[0] + stub_self = args[0] + api_return_value = args[1] _call_on_real_instance( - state_shared_with_api.get("stub_self"), - name.replace(_DD_HOOK_PREFIX, "").rsplit(".", 1)[-1], - state_shared_with_api.get("api_return_value"), - state_shared_with_api, - *args[1:], - **hook_args[0][1] + stub_self, name.replace(_DD_HOOK_PREFIX, "").rsplit(".", 1)[-1], api_return_value, *args[2:], **hook_args[0][1] ) From acac91499fedd0c9090e75b2fa73ade711aa123a Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Wed, 5 Feb 2025 07:50:00 -0800 Subject: [PATCH 32/33] update requirements --- .riot/requirements/1261872.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/.riot/requirements/1261872.txt b/.riot/requirements/1261872.txt index 5f94623ae41..b474929a916 100644 --- a/.riot/requirements/1261872.txt +++ b/.riot/requirements/1261872.txt @@ -19,7 +19,6 @@ pluggy==1.5.0 pytest==8.3.4 pytest-cov==6.0.0 pytest-mock==3.14.0 -pyyaml==6.0.2 requests==2.32.3 sortedcontainers==2.4.0 urllib3==2.3.0 From 0e679f89a846b7d00d56dbd34d973356ae7a6d27 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Wed, 5 Feb 2025 08:37:11 -0800 Subject: [PATCH 33/33] one hook name --- ddtrace/contrib/internal/dd_trace_api/patch.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ddtrace/contrib/internal/dd_trace_api/patch.py b/ddtrace/contrib/internal/dd_trace_api/patch.py index 5d876a12588..965b8c4f318 100644 --- a/ddtrace/contrib/internal/dd_trace_api/patch.py +++ b/ddtrace/contrib/internal/dd_trace_api/patch.py @@ -11,7 +11,7 @@ import ddtrace -_DD_HOOK_PREFIX = "dd.hooks." +_DD_HOOK_NAME = "dd.hook" _TRACER_KEY = "Tracer" _STUB_TO_REAL = weakref.WeakKeyDictionary() _STUB_TO_REAL[dd_trace_api.tracer] = ddtrace.tracer @@ -51,14 +51,11 @@ def _call_on_real_instance( def _hook(name, hook_args): """Called in response to `sys.audit` events""" - if not dd_trace_api.__datadog_patch or not name.startswith(_DD_HOOK_PREFIX): + if not dd_trace_api.__datadog_patch or name != _DD_HOOK_NAME: return args = hook_args[0][0] - stub_self = args[0] - api_return_value = args[1] - _call_on_real_instance( - stub_self, name.replace(_DD_HOOK_PREFIX, "").rsplit(".", 1)[-1], api_return_value, *args[2:], **hook_args[0][1] - ) + api_return_value, stub_self, event_name = args[0:3] + _call_on_real_instance(stub_self, event_name, api_return_value, *args[3:], **hook_args[0][1]) def get_version() -> str: