Skip to content

Use new scopes API in Django, SQLAlchemy, and asyncpg integration. #2845

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

Merged
merged 6 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 20 additions & 34 deletions sentry_sdk/integrations/asyncpg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
import contextlib
from typing import Any, TypeVar, Callable, Awaitable, Iterator

from sentry_sdk import Hub
import sentry_sdk
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.tracing import Span
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
from sentry_sdk.utils import parse_version, capture_internal_exceptions
from sentry_sdk.utils import (
ensure_integration_enabled,
ensure_integration_enabled_async,
parse_version,
capture_internal_exceptions,
)

try:
import asyncpg # type: ignore[import-not-found]
Expand Down Expand Up @@ -54,8 +59,7 @@ def setup_once() -> None:

def _wrap_execute(f: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
async def _inner(*args: Any, **kwargs: Any) -> T:
hub = Hub.current
integration = hub.get_integration(AsyncPGIntegration)
integration = sentry_sdk.get_client().get_integration(AsyncPGIntegration)

# Avoid recording calls to _execute twice.
# Calls to Connection.execute with args also call
Expand All @@ -65,13 +69,11 @@ async def _inner(*args: Any, **kwargs: Any) -> T:
return await f(*args, **kwargs)

query = args[1]
with record_sql_queries(
hub, None, query, None, None, executemany=False
) as span:
with record_sql_queries(None, query, None, None, executemany=False) as span:
res = await f(*args, **kwargs)

with capture_internal_exceptions():
add_query_source(hub, span)
add_query_source(span)

return res

Expand All @@ -83,21 +85,19 @@ async def _inner(*args: Any, **kwargs: Any) -> T:

@contextlib.contextmanager
def _record(
hub: Hub,
cursor: SubCursor | None,
query: str,
params_list: tuple[Any, ...] | None,
*,
executemany: bool = False,
) -> Iterator[Span]:
integration = hub.get_integration(AsyncPGIntegration)
if not integration._record_params:
integration = sentry_sdk.get_client().get_integration(AsyncPGIntegration)
if integration is not None and not integration._record_params:
params_list = None

param_style = "pyformat" if params_list else None

with record_sql_queries(
hub,
cursor,
query,
params_list,
Expand All @@ -111,16 +111,11 @@ def _record(
def _wrap_connection_method(
f: Callable[..., Awaitable[T]], *, executemany: bool = False
) -> Callable[..., Awaitable[T]]:
@ensure_integration_enabled_async(AsyncPGIntegration, f)
async def _inner(*args: Any, **kwargs: Any) -> T:
hub = Hub.current
integration = hub.get_integration(AsyncPGIntegration)

if integration is None:
return await f(*args, **kwargs)

query = args[1]
params_list = args[2] if len(args) > 2 else None
with _record(hub, None, query, params_list, executemany=executemany) as span:
with _record(None, query, params_list, executemany=executemany) as span:
_set_db_data(span, args[0])
res = await f(*args, **kwargs)

Expand All @@ -130,18 +125,12 @@ async def _inner(*args: Any, **kwargs: Any) -> T:


def _wrap_cursor_creation(f: Callable[..., T]) -> Callable[..., T]:
@ensure_integration_enabled(AsyncPGIntegration, f)
def _inner(*args: Any, **kwargs: Any) -> T: # noqa: N807
hub = Hub.current
integration = hub.get_integration(AsyncPGIntegration)

if integration is None:
return f(*args, **kwargs)

query = args[1]
params_list = args[2] if len(args) > 2 else None

with _record(
hub,
None,
query,
params_list,
Expand All @@ -157,17 +146,12 @@ def _inner(*args: Any, **kwargs: Any) -> T: # noqa: N807


def _wrap_connect_addr(f: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
@ensure_integration_enabled_async(AsyncPGIntegration, f)
async def _inner(*args: Any, **kwargs: Any) -> T:
hub = Hub.current
integration = hub.get_integration(AsyncPGIntegration)

if integration is None:
return await f(*args, **kwargs)

user = kwargs["params"].user
database = kwargs["params"].database

with hub.start_span(op=OP.DB, description="connect") as span:
with sentry_sdk.start_span(op=OP.DB, description="connect") as span:
span.set_data(SPANDATA.DB_SYSTEM, "postgresql")
addr = kwargs.get("addr")
if addr:
Expand All @@ -180,7 +164,9 @@ async def _inner(*args: Any, **kwargs: Any) -> T:
span.set_data(SPANDATA.DB_USER, user)

with capture_internal_exceptions():
hub.add_breadcrumb(message="connect", category="query", data=span._data)
sentry_sdk.add_breadcrumb(
message="connect", category="query", data=span._data
)
res = await f(*args, **kwargs)

return res
Expand Down
85 changes: 34 additions & 51 deletions sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import weakref
from importlib import import_module

import sentry_sdk
from sentry_sdk._types import TYPE_CHECKING
from sentry_sdk.consts import OP, SPANDATA
from sentry_sdk.db.explain_plan.django import attach_explain_plan_to_span
from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.scope import Scope, add_global_event_processor
from sentry_sdk.scope import Scope, add_global_event_processor, should_send_default_pii
from sentry_sdk.serializer import add_global_repr_processor
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TRANSACTION_SOURCE_URL
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
Expand All @@ -19,6 +19,7 @@
SENSITIVE_DATA_SUBSTITUTE,
logger,
capture_internal_exceptions,
ensure_integration_enabled,
event_from_exception,
transaction_from_function,
walk_exception_chain,
Expand Down Expand Up @@ -146,11 +147,9 @@ def setup_once():

old_app = WSGIHandler.__call__

@ensure_integration_enabled(DjangoIntegration, old_app)
def sentry_patched_wsgi_handler(self, environ, start_response):
# type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
if Hub.current.get_integration(DjangoIntegration) is None:
return old_app(self, environ, start_response)

bound_old_app = old_app.__get__(self, WSGIHandler)

from django.conf import settings
Expand Down Expand Up @@ -229,11 +228,6 @@ def _django_queryset_repr(value, hint):
if not isinstance(value, QuerySet) or value._result_cache:
return NotImplemented

# Do not call Hub.get_integration here. It is intentional that
# running under a new hub does not suddenly start executing
# querysets. This might be surprising to the user but it's likely
# less annoying.

return "<%s from %s at 0x%x>" % (
value.__class__.__name__,
value.__module__,
Expand Down Expand Up @@ -400,8 +394,8 @@ def _set_transaction_name_and_source(scope, transaction_style, request):

def _before_get_response(request):
# type: (WSGIRequest) -> None
hub = Hub.current
integration = hub.get_integration(DjangoIntegration)
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)

if integration is None:
return

Expand Down Expand Up @@ -431,8 +425,7 @@ def _attempt_resolve_again(request, scope, transaction_style):

def _after_get_response(request):
# type: (WSGIRequest) -> None
hub = Hub.current
integration = hub.get_integration(DjangoIntegration)
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
if integration is None or integration.transaction_style != "url":
return

Expand Down Expand Up @@ -490,7 +483,7 @@ def wsgi_request_event_processor(event, hint):
with capture_internal_exceptions():
DjangoRequestExtractor(request).extract_into_event(event)

if _should_send_default_pii():
if should_send_default_pii():
with capture_internal_exceptions():
_set_user_info(request, event)

Expand All @@ -501,22 +494,19 @@ def wsgi_request_event_processor(event, hint):

def _got_request_exception(request=None, **kwargs):
# type: (WSGIRequest, **Any) -> None
hub = Hub.current
integration = hub.get_integration(DjangoIntegration)
client = sentry_sdk.get_client()
integration = client.get_integration(DjangoIntegration)
if integration is not None:
if request is not None and integration.transaction_style == "url":
scope = Scope.get_current_scope()
_attempt_resolve_again(request, scope, integration.transaction_style)

# If an integration is there, a client has to be there.
client = hub.client # type: Any

event, hint = event_from_exception(
sys.exc_info(),
client_options=client.options,
mechanism={"type": "django", "handled": False},
)
hub.capture_event(event, hint=hint)
sentry_sdk.capture_event(event, hint=hint)


class DjangoRequestExtractor(RequestExtractor):
Expand Down Expand Up @@ -612,62 +602,56 @@ def install_sql_hook():
# This won't work on Django versions < 1.6
return

@ensure_integration_enabled(DjangoIntegration, real_execute)
def execute(self, sql, params=None):
# type: (CursorWrapper, Any, Optional[Any]) -> Any
hub = Hub.current
if hub.get_integration(DjangoIntegration) is None:
return real_execute(self, sql, params)

with record_sql_queries(
hub, self.cursor, sql, params, paramstyle="format", executemany=False
self.cursor, sql, params, paramstyle="format", executemany=False
) as span:
_set_db_data(span, self)
if hub.client:
options = hub.client.options["_experiments"].get("attach_explain_plans")
if options is not None:
attach_explain_plan_to_span(
span,
self.cursor.connection,
sql,
params,
self.mogrify,
options,
)
options = (
sentry_sdk.get_client()
.options["_experiments"]
.get("attach_explain_plans")
)
if options is not None:
attach_explain_plan_to_span(
span,
self.cursor.connection,
sql,
params,
self.mogrify,
options,
)
result = real_execute(self, sql, params)

with capture_internal_exceptions():
add_query_source(hub, span)
add_query_source(span)

return result

@ensure_integration_enabled(DjangoIntegration, real_executemany)
def executemany(self, sql, param_list):
# type: (CursorWrapper, Any, List[Any]) -> Any
hub = Hub.current
if hub.get_integration(DjangoIntegration) is None:
return real_executemany(self, sql, param_list)

with record_sql_queries(
hub, self.cursor, sql, param_list, paramstyle="format", executemany=True
self.cursor, sql, param_list, paramstyle="format", executemany=True
) as span:
_set_db_data(span, self)

result = real_executemany(self, sql, param_list)

with capture_internal_exceptions():
add_query_source(hub, span)
add_query_source(span)

return result

@ensure_integration_enabled(DjangoIntegration, real_connect)
def connect(self):
# type: (BaseDatabaseWrapper) -> None
hub = Hub.current
if hub.get_integration(DjangoIntegration) is None:
return real_connect(self)

with capture_internal_exceptions():
hub.add_breadcrumb(message="connect", category="query")
sentry_sdk.add_breadcrumb(message="connect", category="query")

with hub.start_span(op=OP.DB, description="connect") as span:
with sentry_sdk.start_span(op=OP.DB, description="connect") as span:
_set_db_data(span, self)
return real_connect(self)

Expand All @@ -679,7 +663,6 @@ def connect(self):

def _set_db_data(span, cursor_or_db):
# type: (Span, Any) -> None

db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db
vendor = db.vendor
span.set_data(SPANDATA.DB_SYSTEM, vendor)
Expand Down
Loading