Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(pin): enforce the use of the global tracer #12219

Merged
merged 16 commits into from
Feb 5, 2025
Merged
70 changes: 39 additions & 31 deletions ddtrace/_trace/pin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import wrapt

import ddtrace
from ddtrace.vendor.debtcollector import deprecate

from ..internal.logger import get_logger

Expand All @@ -32,25 +31,17 @@ class Pin(object):
>>> conn = sqlite.connect('/tmp/image.db')
"""

__slots__ = ["tags", "tracer", "_target", "_config", "_initialized"]
__slots__ = ["tags", "_tracer", "_target", "_config", "_initialized"]

def __init__(
self,
service=None, # type: Optional[str]
tags=None, # type: Optional[Dict[str, str]]
tracer=None,
_config=None, # type: Optional[Dict[str, Any]]
):
# type: (...) -> None
if tracer is not None and tracer is not ddtrace.tracer:
deprecate(
"Initializing ddtrace.trace.Pin with `tracer` argument is deprecated",
message="All Pin instances should use the global tracer instance",
removal_version="3.0.0",
)
tracer = tracer or ddtrace.tracer
self.tags = tags
self.tracer = tracer
self._tracer = ddtrace.tracer
self._target = None # type: Optional[int]
# keep the configuration attribute internal because the
# public API to access it is not the Pin class
Expand All @@ -68,10 +59,14 @@ def service(self):
return self._config["service_name"]

def __setattr__(self, name, value):
if getattr(self, "_initialized", False) and name != "_target":
if getattr(self, "_initialized", False) and name not in ("_target", "_tracer"):
raise AttributeError("can't mutate a pin, use override() or clone() instead")
super(Pin, self).__setattr__(name, value)

@property
def tracer(self):
return self._tracer

def __repr__(self):
return "Pin(service=%s, tags=%s, tracer=%s)" % (self.service, self.tags, self.tracer)

Expand Down Expand Up @@ -127,7 +122,6 @@ def override(
obj, # type: Any
service=None, # type: Optional[str]
tags=None, # type: Optional[Dict[str, str]]
tracer=None,
):
# type: (...) -> None
"""Override an object with the given attributes.
Expand All @@ -139,20 +133,32 @@ def override(
>>> # Override a pin for a specific connection
>>> Pin.override(conn, service='user-db')
"""
if tracer is not None:
deprecate(
"Calling ddtrace.trace.Pin.override(...) with the `tracer` argument is deprecated",
message="All Pin instances should use the global tracer instance",
removal_version="3.0.0",
)
Pin._override(obj, service=service, tags=tags)

@classmethod
def _override(
cls,
obj, # type: Any
service=None, # type: Optional[str]
tags=None, # type: Optional[Dict[str, str]]
tracer=None,
):
# type: (...) -> None
"""
Internal method that allows overriding the global tracer in tests
"""
if not obj:
return

pin = cls.get_from(obj)
if pin is None:
Pin(service=service, tags=tags, tracer=tracer).onto(obj)
pin = Pin(service=service, tags=tags)
else:
pin.clone(service=service, tags=tags, tracer=tracer).onto(obj)
pin = pin.clone(service=service, tags=tags)

if tracer:
pin._tracer = tracer
pin.onto(obj)

def enabled(self):
# type: () -> bool
Expand Down Expand Up @@ -198,21 +204,22 @@ def clone(
self,
service=None, # type: Optional[str]
tags=None, # type: Optional[Dict[str, str]]
tracer=None,
):
# type: (...) -> Pin
"""Return a clone of the pin with the given attributes replaced."""
return self._clone(service=service, tags=tags)

def _clone(
self,
service=None, # type: Optional[str]
tags=None, # type: Optional[Dict[str, str]]
tracer=None,
):
"""Internal method that can clone the tracer from an existing Pin. This is used in tests"""
# do a shallow copy of Pin dicts
if not tags and self.tags:
tags = self.tags.copy()

if tracer is not None:
deprecate(
"Initializing ddtrace.trace.Pin with `tracer` argument is deprecated",
message="All Pin instances should use the global tracer instance",
removal_version="3.0.0",
)

# we use a copy instead of a deepcopy because we expect configurations
# to have only a root level dictionary without nested objects. Using
# deepcopy introduces a big overhead:
Expand All @@ -221,9 +228,10 @@ def clone(
# deepcopy: 0.2787208557128906
config = self._config.copy()

return Pin(
pin = Pin(
service=service or self.service,
tags=tags,
tracer=tracer or self.tracer, # do not clone the Tracer
_config=config,
)
pin._tracer = tracer or self.tracer
return pin
8 changes: 2 additions & 6 deletions ddtrace/contrib/internal/asgi/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,8 @@ async def __call__(self, scope, receive, send):
if scope["type"] == "http":
operation_name = schematize_url_operation(operation_name, direction=SpanDirection.INBOUND, protocol="http")

# Calling ddtrace.trace.Pin(...) with the `tracer` argument is deprecated
# Remove this if statement when the `tracer` argument is removed
if self.tracer is ddtrace.tracer:
pin = ddtrace.trace.Pin(service="asgi")
else:
pin = ddtrace.trace.Pin(service="asgi", tracer=self.tracer)
pin = ddtrace.trace.Pin(service="asgi")
pin._tracer = self.tracer

with core.context_with_data(
"asgi.__call__",
Expand Down
10 changes: 3 additions & 7 deletions ddtrace/contrib/internal/django/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import wrapt
from wrapt.importer import when_imported

import ddtrace
from ddtrace import config
from ddtrace.appsec._utils import _UserInfoRetriever
from ddtrace.constants import SPAN_KIND
Expand Down Expand Up @@ -149,12 +148,9 @@ def cursor(django, pin, func, instance, args, kwargs):
tags = {"django.db.vendor": vendor, "django.db.alias": alias}
tags.update(getattr(conn, "_datadog_tags", {}))

# Calling ddtrace.pin.Pin(...) with the `tracer` argument generates a deprecation warning.
# Remove this if statement when the `tracer` argument is removed
if pin.tracer is ddtrace.tracer:
pin = Pin(service, tags=tags)
else:
pin = Pin(service, tags=tags, tracer=pin.tracer)
tracer = pin.tracer
pin = Pin(service, tags=tags)
mabdinur marked this conversation as resolved.
Show resolved Hide resolved
pin._tracer = tracer
erikayasuda marked this conversation as resolved.
Show resolved Hide resolved

cursor = func(*args, **kwargs)

Expand Down
11 changes: 5 additions & 6 deletions ddtrace/contrib/internal/mongoengine/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@ def __call__(self, *args, **kwargs):
client = self.__wrapped__(*args, **kwargs)
pin = ddtrace.trace.Pin.get_from(self)
if pin:
# Calling ddtrace.trace.Pin(...) with the `tracer` argument generates a deprecation warning.
# Remove this if statement when the `tracer` argument is removed
if pin.tracer is ddtrace.tracer:
ddtrace.trace.Pin(service=pin.service).onto(client)
else:
ddtrace.trace.Pin(service=pin.service, tracer=pin.tracer).onto(client)
tracer = pin.tracer
pp = ddtrace.trace.Pin(service=pin.service)
if tracer is not None:
pp._tracer = tracer
pp.onto(client)

return client
8 changes: 2 additions & 6 deletions ddtrace/contrib/internal/pylibmc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,8 @@ def __init__(self, client=None, service=memcached.SERVICE, tracer=None, *args, *
super(TracedClient, self).__init__(client)

schematized_service = schematize_service_name(service)
# Calling ddtrace.trace.Pin(...) with the `tracer` argument generates a deprecation warning.
# Remove this if statement when the `tracer` argument is removed
if tracer is ddtrace.tracer:
pin = ddtrace.trace.Pin(service=schematized_service)
else:
pin = ddtrace.trace.Pin(service=schematized_service, tracer=tracer)
pin = ddtrace.trace.Pin(service=schematized_service)
pin._tracer = tracer
pin.onto(self)

# attempt to collect the pool of urls this client talks to
Expand Down
9 changes: 3 additions & 6 deletions ddtrace/contrib/internal/sqlalchemy/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,9 @@ def __init__(self, tracer, service, engine):
self.name = schematize_database_operation("%s.query" % self.vendor, database_provider=self.vendor)

# attach the PIN
# Calling ddtrace.trace.Pin(...) with the `tracer` argument generates a deprecation warning.
# Remove this if statement when the `tracer` argument is removed
if self.tracer is ddtrace.tracer:
Pin(service=self.service).onto(engine)
else:
Pin(tracer=tracer, service=self.service).onto(engine)
pin = Pin(service=self.service)
pin._tracer = self.tracer
pin.onto(engine)

listen(engine, "before_cursor_execute", self._before_cur_exec)
listen(engine, "after_cursor_execute", self._after_cur_exec)
Expand Down
10 changes: 3 additions & 7 deletions ddtrace/contrib/internal/tornado/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,6 @@ def tracer_config(__init__, app, args, kwargs):
if tags:
tracer.set_tags(tags)

# configure the PIN object for template rendering
# Required for backwards compatibility. Remove the else clause when
# the `ddtrace.trace.Pin` object no longer accepts the Pin argument.
if tracer is ddtrace.tracer:
ddtrace.trace.Pin(service=service).onto(template)
else:
ddtrace.trace.Pin(service=service, tracer=tracer).onto(template)
pin = ddtrace.trace.Pin(service=service)
pin._tracer = tracer
pin.onto(template)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
upgrade:
- |
tracing: Removes support for overriding the global tracer in ``ddtrace.trace.Pin``
2 changes: 1 addition & 1 deletion tests/appsec/contrib_appsec/django_app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def login_user(request):
def new_service(request, service_name: str):
import ddtrace

ddtrace.trace.Pin.override(django, service=service_name, tracer=ddtrace.tracer)
ddtrace.trace.Pin._override(django, service=service_name, tracer=ddtrace.tracer)
return HttpResponse(service_name, status=200)


Expand Down
2 changes: 1 addition & 1 deletion tests/appsec/contrib_appsec/fastapi_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async def multi_view_no_param(request: Request): # noqa: B008
async def new_service(service_name: str, request: Request): # noqa: B008
import ddtrace

ddtrace.trace.Pin.override(app, service=service_name, tracer=ddtrace.tracer)
ddtrace.trace.Pin._override(app, service=service_name, tracer=ddtrace.tracer)
return HTMLResponse(service_name, 200)

async def slow_numbers(minimum, maximum):
Expand Down
2 changes: 1 addition & 1 deletion tests/appsec/contrib_appsec/flask_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def multi_view(param_int=0, param_str=""):
def new_service(service_name: str):
import ddtrace

ddtrace.trace.Pin.override(Flask, service=service_name, tracer=ddtrace.tracer)
ddtrace.trace.Pin._override(Flask, service=service_name, tracer=ddtrace.tracer)
return service_name


Expand Down
2 changes: 1 addition & 1 deletion tests/appsec/contrib_appsec/test_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def setUp(self):
self.app = app
self.app.test_client_class = DDFlaskTestClient
self.client = self.app.test_client()
Pin.override(self.app, tracer=self.tracer)
Pin._override(self.app, tracer=self.tracer)

def tearDown(self):
super(BaseFlaskTestCase, self).tearDown()
Expand Down
4 changes: 2 additions & 2 deletions tests/appsec/contrib_appsec/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1568,7 +1568,7 @@ def test_tracer():
@contextmanager
def post_tracer(interface):
original_tracer = getattr(ddtrace.trace.Pin.get_from(interface.framework), "tracer", None)
ddtrace.trace.Pin.override(interface.framework, tracer=interface.tracer)
ddtrace.trace.Pin._override(interface.framework, tracer=interface.tracer)
yield
if original_tracer is not None:
ddtrace.trace.Pin.override(interface.framework, tracer=original_tracer)
ddtrace.trace.Pin._override(interface.framework, tracer=original_tracer)
4 changes: 2 additions & 2 deletions tests/appsec/integrations/django_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def tracer():
# Patch Django and override tracer to be our test tracer
pin = Pin.get_from(django)
original_tracer = pin.tracer
Pin.override(django, tracer=tracer)
Pin._override(django, tracer=tracer)

# Yield to our test
yield tracer
Expand All @@ -59,7 +59,7 @@ def tracer():
# Reset the tracer pinned to Django and unpatch
# DEV: unable to properly unpatch and reload django app with each test
# unpatch()
Pin.override(django, tracer=original_tracer)
Pin._override(django, tracer=original_tracer)


@pytest.fixture
Expand Down
4 changes: 2 additions & 2 deletions tests/contrib/aiobotocore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ async def aiobotocore_client(service, tracer):
client, aiobotocore.session.ClientCreatorContext
):
async with client as client:
Pin.override(client, tracer=tracer)
Pin._override(client, tracer=tracer)
await yield_(client)

else:
Pin.override(client, tracer=tracer)
Pin._override(client, tracer=tracer)
try:
await yield_(client)
finally:
Expand Down
2 changes: 1 addition & 1 deletion tests/contrib/aiohttp/test_aiohttp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def test_configure_service_name_pin(ddtrace_run_python_code_in_subprocess):

async def test():
async with aiohttp.ClientSession() as session:
Pin.override(session, service="pin-custom-svc")
Pin._override(session, service="pin-custom-svc")
async with session.get(URL_200) as resp:
pass

Expand Down
4 changes: 2 additions & 2 deletions tests/contrib/aiohttp_jinja2/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
def patched_app_tracer_jinja(patched_app_tracer): # noqa: F811
app, tracer = patched_app_tracer
patch()
Pin.override(aiohttp_jinja2, tracer=tracer)
Pin._override(aiohttp_jinja2, tracer=tracer)
yield app, tracer
unpatch()

Expand All @@ -22,6 +22,6 @@ def patched_app_tracer_jinja(patched_app_tracer): # noqa: F811
def untraced_app_tracer_jinja(untraced_app_tracer): # noqa: F811
patch()
app, tracer = untraced_app_tracer
Pin.override(aiohttp_jinja2, tracer=tracer)
Pin._override(aiohttp_jinja2, tracer=tracer)
yield app, tracer
unpatch()
4 changes: 2 additions & 2 deletions tests/contrib/aiohttp_jinja2/test_aiohttp_jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async def test_template_rendering(untraced_app_tracer_jinja, aiohttp_client):

async def test_template_rendering_snapshot(untraced_app_tracer_jinja, aiohttp_client, snapshot_context):
app, _ = untraced_app_tracer_jinja
Pin.override(aiohttp_jinja2, tracer=tracer)
Pin._override(aiohttp_jinja2, tracer=tracer)
with snapshot_context():
client = await aiohttp_client(app)
# it should trace a template rendering
Expand All @@ -51,7 +51,7 @@ async def test_template_rendering_snapshot_patched_server(
use_global_tracer,
):
app, _ = patched_app_tracer_jinja
Pin.override(aiohttp_jinja2, tracer=tracer)
Pin._override(aiohttp_jinja2, tracer=tracer)
# Ignore meta.http.url tag as the port is not fixed on the server
with snapshot_context(ignores=["meta.http.url", "meta.http.useragent"]):
client = await aiohttp_client(app)
Expand Down
Loading
Loading