sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +81 -19
  8. sentry_sdk/_types.py +311 -11
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +35 -28
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +20 -11
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +71 -60
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -1,48 +1,77 @@
1
- # -*- coding: utf-8 -*-
2
- from __future__ import absolute_import
3
-
1
+ import inspect
4
2
  import sys
5
3
  import threading
6
4
  import weakref
7
-
8
- from sentry_sdk._types import MYPY
9
- from sentry_sdk.hub import Hub, _should_send_default_pii
10
- from sentry_sdk.scope import add_global_event_processor
11
- from sentry_sdk.serializer import add_global_repr_processor
12
- from sentry_sdk.tracing import record_sql_queries
5
+ from importlib import import_module
6
+
7
+ import sentry_sdk
8
+ from sentry_sdk.consts import OP, SPANDATA
9
+ from sentry_sdk.scope import add_global_event_processor, should_send_default_pii
10
+ from sentry_sdk.serializer import add_global_repr_processor, add_repr_sequence_type
11
+ from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
12
+ from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
13
13
  from sentry_sdk.utils import (
14
+ AnnotatedValue,
14
15
  HAS_REAL_CONTEXTVARS,
15
16
  CONTEXTVARS_ERROR_MESSAGE,
17
+ SENSITIVE_DATA_SUBSTITUTE,
16
18
  logger,
17
19
  capture_internal_exceptions,
20
+ ensure_integration_enabled,
18
21
  event_from_exception,
19
22
  transaction_from_function,
20
23
  walk_exception_chain,
21
24
  )
22
- from sentry_sdk.integrations import Integration, DidNotEnable
25
+ from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
23
26
  from sentry_sdk.integrations.logging import ignore_logger
24
27
  from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
25
- from sentry_sdk.integrations._wsgi_common import RequestExtractor
28
+ from sentry_sdk.integrations._wsgi_common import (
29
+ DEFAULT_HTTP_METHODS_TO_CAPTURE,
30
+ RequestExtractor,
31
+ )
26
32
 
27
33
  try:
28
34
  from django import VERSION as DJANGO_VERSION
35
+ from django.conf import settings as django_settings
29
36
  from django.core import signals
37
+ from django.conf import settings
30
38
 
31
39
  try:
32
40
  from django.urls import resolve
33
41
  except ImportError:
34
42
  from django.core.urlresolvers import resolve
43
+
44
+ try:
45
+ from django.urls import Resolver404
46
+ except ImportError:
47
+ from django.core.urlresolvers import Resolver404
48
+
49
+ # Only available in Django 3.0+
50
+ try:
51
+ from django.core.handlers.asgi import ASGIRequest
52
+ except Exception:
53
+ ASGIRequest = None
54
+
35
55
  except ImportError:
36
56
  raise DidNotEnable("Django not installed")
37
57
 
38
-
39
58
  from sentry_sdk.integrations.django.transactions import LEGACY_RESOLVER
40
- from sentry_sdk.integrations.django.templates import get_template_frame_from_exception
59
+ from sentry_sdk.integrations.django.templates import (
60
+ get_template_frame_from_exception,
61
+ patch_templates,
62
+ )
41
63
  from sentry_sdk.integrations.django.middleware import patch_django_middlewares
64
+ from sentry_sdk.integrations.django.signals_handlers import patch_signals
42
65
  from sentry_sdk.integrations.django.views import patch_views
43
66
 
67
+ if DJANGO_VERSION[:2] > (1, 8):
68
+ from sentry_sdk.integrations.django.caching import patch_caching
69
+ else:
70
+ patch_caching = None # type: ignore
71
+
72
+ from typing import TYPE_CHECKING
44
73
 
45
- if MYPY:
74
+ if TYPE_CHECKING:
46
75
  from typing import Any
47
76
  from typing import Callable
48
77
  from typing import Dict
@@ -55,6 +84,7 @@ if MYPY:
55
84
  from django.http.request import QueryDict
56
85
  from django.utils.datastructures import MultiValueDict
57
86
 
87
+ from sentry_sdk.tracing import Span
58
88
  from sentry_sdk.integrations.wsgi import _ScopedResponse
59
89
  from sentry_sdk._types import Event, Hint, EventProcessor, NotImplementedType
60
90
 
@@ -65,7 +95,6 @@ if DJANGO_VERSION < (1, 10):
65
95
  # type: (Any) -> bool
66
96
  return request_user.is_authenticated()
67
97
 
68
-
69
98
  else:
70
99
 
71
100
  def is_authenticated(request_user):
@@ -77,13 +106,36 @@ TRANSACTION_STYLE_VALUES = ("function_name", "url")
77
106
 
78
107
 
79
108
  class DjangoIntegration(Integration):
109
+ """
110
+ Auto instrument a Django application.
111
+
112
+ :param transaction_style: How to derive transaction names. Either `"function_name"` or `"url"`. Defaults to `"url"`.
113
+ :param middleware_spans: Whether to create spans for middleware. Defaults to `True`.
114
+ :param signals_spans: Whether to create spans for signals. Defaults to `True`.
115
+ :param signals_denylist: A list of signals to ignore when creating spans.
116
+ :param cache_spans: Whether to create spans for cache operations. Defaults to `False`.
117
+ """
118
+
80
119
  identifier = "django"
120
+ origin = f"auto.http.{identifier}"
121
+ origin_db = f"auto.db.{identifier}"
81
122
 
82
- transaction_style = None
123
+ transaction_style = ""
83
124
  middleware_spans = None
84
-
85
- def __init__(self, transaction_style="url", middleware_spans=True):
86
- # type: (str, bool) -> None
125
+ signals_spans = None
126
+ cache_spans = None
127
+ signals_denylist = [] # type: list[signals.Signal]
128
+
129
+ def __init__(
130
+ self,
131
+ transaction_style="url", # type: str
132
+ middleware_spans=True, # type: bool
133
+ signals_spans=True, # type: bool
134
+ cache_spans=False, # type: bool
135
+ signals_denylist=None, # type: Optional[list[signals.Signal]]
136
+ http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: tuple[str, ...]
137
+ ):
138
+ # type: (...) -> None
87
139
  if transaction_style not in TRANSACTION_STYLE_VALUES:
88
140
  raise ValueError(
89
141
  "Invalid value for transaction_style: %s (must be in %s)"
@@ -92,12 +144,17 @@ class DjangoIntegration(Integration):
92
144
  self.transaction_style = transaction_style
93
145
  self.middleware_spans = middleware_spans
94
146
 
147
+ self.signals_spans = signals_spans
148
+ self.signals_denylist = signals_denylist or []
149
+
150
+ self.cache_spans = cache_spans
151
+
152
+ self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture))
153
+
95
154
  @staticmethod
96
155
  def setup_once():
97
156
  # type: () -> None
98
-
99
- if DJANGO_VERSION < (1, 6):
100
- raise DidNotEnable("Django 1.6 or newer is required.")
157
+ _check_minimum_version(DjangoIntegration, DJANGO_VERSION)
101
158
 
102
159
  install_sql_hook()
103
160
  # Patch in our custom middleware.
@@ -110,14 +167,28 @@ class DjangoIntegration(Integration):
110
167
 
111
168
  old_app = WSGIHandler.__call__
112
169
 
170
+ @ensure_integration_enabled(DjangoIntegration, old_app)
113
171
  def sentry_patched_wsgi_handler(self, environ, start_response):
114
172
  # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
115
- if Hub.current.get_integration(DjangoIntegration) is None:
116
- return old_app(self, environ, start_response)
117
-
118
173
  bound_old_app = old_app.__get__(self, WSGIHandler)
119
174
 
120
- return SentryWsgiMiddleware(bound_old_app)(environ, start_response)
175
+ from django.conf import settings
176
+
177
+ use_x_forwarded_for = settings.USE_X_FORWARDED_HOST
178
+
179
+ integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
180
+
181
+ middleware = SentryWsgiMiddleware(
182
+ bound_old_app,
183
+ use_x_forwarded_for,
184
+ span_origin=DjangoIntegration.origin,
185
+ http_methods_to_capture=(
186
+ integration.http_methods_to_capture
187
+ if integration
188
+ else DEFAULT_HTTP_METHODS_TO_CAPTURE
189
+ ),
190
+ )
191
+ return middleware(environ, start_response)
121
192
 
122
193
  WSGIHandler.__call__ = sentry_patched_wsgi_handler
123
194
 
@@ -187,12 +258,7 @@ class DjangoIntegration(Integration):
187
258
  if not isinstance(value, QuerySet) or value._result_cache:
188
259
  return NotImplemented
189
260
 
190
- # Do not call Hub.get_integration here. It is intentional that
191
- # running under a new hub does not suddenly start executing
192
- # querysets. This might be surprising to the user but it's likely
193
- # less annoying.
194
-
195
- return u"<%s from %s at 0x%x>" % (
261
+ return "<%s from %s at 0x%x>" % (
196
262
  value.__class__.__name__,
197
263
  value.__module__,
198
264
  id(value),
@@ -201,6 +267,12 @@ class DjangoIntegration(Integration):
201
267
  _patch_channels()
202
268
  patch_django_middlewares()
203
269
  patch_views()
270
+ patch_templates()
271
+ patch_signals()
272
+ add_template_context_repr_sequence()
273
+
274
+ if patch_caching is not None:
275
+ patch_caching()
204
276
 
205
277
 
206
278
  _DRF_PATCHED = False
@@ -309,30 +381,86 @@ def _patch_django_asgi_handler():
309
381
  patch_django_asgi_handler_impl(ASGIHandler)
310
382
 
311
383
 
384
+ def _set_transaction_name_and_source(scope, transaction_style, request):
385
+ # type: (sentry_sdk.Scope, str, WSGIRequest) -> None
386
+ try:
387
+ transaction_name = None
388
+ if transaction_style == "function_name":
389
+ fn = resolve(request.path).func
390
+ transaction_name = transaction_from_function(getattr(fn, "view_class", fn))
391
+
392
+ elif transaction_style == "url":
393
+ if hasattr(request, "urlconf"):
394
+ transaction_name = LEGACY_RESOLVER.resolve(
395
+ request.path_info, urlconf=request.urlconf
396
+ )
397
+ else:
398
+ transaction_name = LEGACY_RESOLVER.resolve(request.path_info)
399
+
400
+ if transaction_name is None:
401
+ transaction_name = request.path_info
402
+ source = TransactionSource.URL
403
+ else:
404
+ source = SOURCE_FOR_STYLE[transaction_style]
405
+
406
+ scope.set_transaction_name(
407
+ transaction_name,
408
+ source=source,
409
+ )
410
+ except Resolver404:
411
+ urlconf = import_module(settings.ROOT_URLCONF)
412
+ # This exception only gets thrown when transaction_style is `function_name`
413
+ # So we don't check here what style is configured
414
+ if hasattr(urlconf, "handler404"):
415
+ handler = urlconf.handler404
416
+ if isinstance(handler, str):
417
+ scope.transaction = handler
418
+ else:
419
+ scope.transaction = transaction_from_function(
420
+ getattr(handler, "view_class", handler)
421
+ )
422
+ except Exception:
423
+ pass
424
+
425
+
312
426
  def _before_get_response(request):
313
427
  # type: (WSGIRequest) -> None
314
- hub = Hub.current
315
- integration = hub.get_integration(DjangoIntegration)
428
+ integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
316
429
  if integration is None:
317
430
  return
318
431
 
319
432
  _patch_drf()
320
433
 
321
- with hub.configure_scope() as scope:
322
- # Rely on WSGI middleware to start a trace
323
- try:
324
- if integration.transaction_style == "function_name":
325
- scope.transaction = transaction_from_function(
326
- resolve(request.path).func
327
- )
328
- elif integration.transaction_style == "url":
329
- scope.transaction = LEGACY_RESOLVER.resolve(request.path)
330
- except Exception:
331
- pass
434
+ scope = sentry_sdk.get_current_scope()
435
+ # Rely on WSGI middleware to start a trace
436
+ _set_transaction_name_and_source(scope, integration.transaction_style, request)
332
437
 
333
- scope.add_event_processor(
334
- _make_event_processor(weakref.ref(request), integration)
335
- )
438
+ scope.add_event_processor(
439
+ _make_wsgi_request_event_processor(weakref.ref(request), integration)
440
+ )
441
+
442
+
443
+ def _attempt_resolve_again(request, scope, transaction_style):
444
+ # type: (WSGIRequest, sentry_sdk.Scope, str) -> None
445
+ """
446
+ Some django middlewares overwrite request.urlconf
447
+ so we need to respect that contract,
448
+ so we try to resolve the url again.
449
+ """
450
+ if not hasattr(request, "urlconf"):
451
+ return
452
+
453
+ _set_transaction_name_and_source(scope, transaction_style, request)
454
+
455
+
456
+ def _after_get_response(request):
457
+ # type: (WSGIRequest) -> None
458
+ integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
459
+ if integration is None or integration.transaction_style != "url":
460
+ return
461
+
462
+ scope = sentry_sdk.get_current_scope()
463
+ _attempt_resolve_again(request, scope, integration.transaction_style)
336
464
 
337
465
 
338
466
  def _patch_get_response():
@@ -347,7 +475,9 @@ def _patch_get_response():
347
475
  def sentry_patched_get_response(self, request):
348
476
  # type: (Any, WSGIRequest) -> Union[HttpResponse, BaseException]
349
477
  _before_get_response(request)
350
- return old_get_response(self, request)
478
+ rv = old_get_response(self, request)
479
+ _after_get_response(request)
480
+ return rv
351
481
 
352
482
  BaseHandler.get_response = sentry_patched_get_response
353
483
 
@@ -357,10 +487,10 @@ def _patch_get_response():
357
487
  patch_get_response_async(BaseHandler, _before_get_response)
358
488
 
359
489
 
360
- def _make_event_processor(weak_request, integration):
490
+ def _make_wsgi_request_event_processor(weak_request, integration):
361
491
  # type: (Callable[[], WSGIRequest], DjangoIntegration) -> EventProcessor
362
- def event_processor(event, hint):
363
- # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
492
+ def wsgi_request_event_processor(event, hint):
493
+ # type: (Event, dict[str, Any]) -> Event
364
494
  # if the request is gone we are fine not logging the data from
365
495
  # it. This might happen if the processor is pushed away to
366
496
  # another thread.
@@ -368,50 +498,72 @@ def _make_event_processor(weak_request, integration):
368
498
  if request is None:
369
499
  return event
370
500
 
371
- try:
372
- drf_request = request._sentry_drf_request_backref()
373
- if drf_request is not None:
374
- request = drf_request
375
- except AttributeError:
376
- pass
501
+ django_3 = ASGIRequest is not None
502
+ if django_3 and type(request) == ASGIRequest:
503
+ # We have a `asgi_request_event_processor` for this.
504
+ return event
377
505
 
378
506
  with capture_internal_exceptions():
379
507
  DjangoRequestExtractor(request).extract_into_event(event)
380
508
 
381
- if _should_send_default_pii():
509
+ if should_send_default_pii():
382
510
  with capture_internal_exceptions():
383
511
  _set_user_info(request, event)
384
512
 
385
513
  return event
386
514
 
387
- return event_processor
515
+ return wsgi_request_event_processor
388
516
 
389
517
 
390
518
  def _got_request_exception(request=None, **kwargs):
391
519
  # type: (WSGIRequest, **Any) -> None
392
- hub = Hub.current
393
- integration = hub.get_integration(DjangoIntegration)
394
- if integration is not None:
520
+ client = sentry_sdk.get_client()
521
+ integration = client.get_integration(DjangoIntegration)
522
+ if integration is None:
523
+ return
395
524
 
396
- # If an integration is there, a client has to be there.
397
- client = hub.client # type: Any
525
+ if request is not None and integration.transaction_style == "url":
526
+ scope = sentry_sdk.get_current_scope()
527
+ _attempt_resolve_again(request, scope, integration.transaction_style)
398
528
 
399
- event, hint = event_from_exception(
400
- sys.exc_info(),
401
- client_options=client.options,
402
- mechanism={"type": "django", "handled": False},
403
- )
404
- hub.capture_event(event, hint=hint)
529
+ event, hint = event_from_exception(
530
+ sys.exc_info(),
531
+ client_options=client.options,
532
+ mechanism={"type": "django", "handled": False},
533
+ )
534
+ sentry_sdk.capture_event(event, hint=hint)
405
535
 
406
536
 
407
537
  class DjangoRequestExtractor(RequestExtractor):
538
+ def __init__(self, request):
539
+ # type: (Union[WSGIRequest, ASGIRequest]) -> None
540
+ try:
541
+ drf_request = request._sentry_drf_request_backref()
542
+ if drf_request is not None:
543
+ request = drf_request
544
+ except AttributeError:
545
+ pass
546
+ self.request = request
547
+
408
548
  def env(self):
409
549
  # type: () -> Dict[str, str]
410
550
  return self.request.META
411
551
 
412
552
  def cookies(self):
413
- # type: () -> Dict[str, str]
414
- return self.request.COOKIES
553
+ # type: () -> Dict[str, Union[str, AnnotatedValue]]
554
+ privacy_cookies = [
555
+ django_settings.CSRF_COOKIE_NAME,
556
+ django_settings.SESSION_COOKIE_NAME,
557
+ ]
558
+
559
+ clean_cookies = {} # type: Dict[str, Union[str, AnnotatedValue]]
560
+ for key, val in self.request.COOKIES.items():
561
+ if key in privacy_cookies:
562
+ clean_cookies[key] = SENSITIVE_DATA_SUBSTITUTE
563
+ else:
564
+ clean_cookies[key] = val
565
+
566
+ return clean_cookies
415
567
 
416
568
  def raw_data(self):
417
569
  # type: () -> bytes
@@ -433,12 +585,12 @@ class DjangoRequestExtractor(RequestExtractor):
433
585
  # type: () -> Optional[Dict[str, Any]]
434
586
  try:
435
587
  return self.request.data
436
- except AttributeError:
588
+ except Exception:
437
589
  return RequestExtractor.parsed_body(self)
438
590
 
439
591
 
440
592
  def _set_user_info(request, event):
441
- # type: (WSGIRequest, Dict[str, Any]) -> None
593
+ # type: (WSGIRequest, Event) -> None
442
594
  user_info = event.setdefault("user", {})
443
595
 
444
596
  user = getattr(request, "user", None)
@@ -470,35 +622,137 @@ def install_sql_hook():
470
622
  except ImportError:
471
623
  from django.db.backends.util import CursorWrapper
472
624
 
625
+ try:
626
+ # django 1.6 and 1.7 compatability
627
+ from django.db.backends import BaseDatabaseWrapper
628
+ except ImportError:
629
+ # django 1.8 or later
630
+ from django.db.backends.base.base import BaseDatabaseWrapper
631
+
473
632
  try:
474
633
  real_execute = CursorWrapper.execute
475
634
  real_executemany = CursorWrapper.executemany
635
+ real_connect = BaseDatabaseWrapper.connect
476
636
  except AttributeError:
477
637
  # This won't work on Django versions < 1.6
478
638
  return
479
639
 
640
+ @ensure_integration_enabled(DjangoIntegration, real_execute)
480
641
  def execute(self, sql, params=None):
481
642
  # type: (CursorWrapper, Any, Optional[Any]) -> Any
482
- hub = Hub.current
483
- if hub.get_integration(DjangoIntegration) is None:
484
- return real_execute(self, sql, params)
485
-
486
643
  with record_sql_queries(
487
- hub, self.cursor, sql, params, paramstyle="format", executemany=False
488
- ):
489
- return real_execute(self, sql, params)
644
+ cursor=self.cursor,
645
+ query=sql,
646
+ params_list=params,
647
+ paramstyle="format",
648
+ executemany=False,
649
+ span_origin=DjangoIntegration.origin_db,
650
+ ) as span:
651
+ _set_db_data(span, self)
652
+ result = real_execute(self, sql, params)
653
+
654
+ with capture_internal_exceptions():
655
+ add_query_source(span)
490
656
 
657
+ return result
658
+
659
+ @ensure_integration_enabled(DjangoIntegration, real_executemany)
491
660
  def executemany(self, sql, param_list):
492
661
  # type: (CursorWrapper, Any, List[Any]) -> Any
493
- hub = Hub.current
494
- if hub.get_integration(DjangoIntegration) is None:
495
- return real_executemany(self, sql, param_list)
496
-
497
662
  with record_sql_queries(
498
- hub, self.cursor, sql, param_list, paramstyle="format", executemany=True
499
- ):
500
- return real_executemany(self, sql, param_list)
663
+ cursor=self.cursor,
664
+ query=sql,
665
+ params_list=param_list,
666
+ paramstyle="format",
667
+ executemany=True,
668
+ span_origin=DjangoIntegration.origin_db,
669
+ ) as span:
670
+ _set_db_data(span, self)
671
+
672
+ result = real_executemany(self, sql, param_list)
673
+
674
+ with capture_internal_exceptions():
675
+ add_query_source(span)
676
+
677
+ return result
678
+
679
+ @ensure_integration_enabled(DjangoIntegration, real_connect)
680
+ def connect(self):
681
+ # type: (BaseDatabaseWrapper) -> None
682
+ with capture_internal_exceptions():
683
+ sentry_sdk.add_breadcrumb(message="connect", category="query")
684
+
685
+ with sentry_sdk.start_span(
686
+ op=OP.DB,
687
+ name="connect",
688
+ origin=DjangoIntegration.origin_db,
689
+ ) as span:
690
+ _set_db_data(span, self)
691
+ return real_connect(self)
501
692
 
502
693
  CursorWrapper.execute = execute
503
694
  CursorWrapper.executemany = executemany
695
+ BaseDatabaseWrapper.connect = connect
504
696
  ignore_logger("django.db.backends")
697
+
698
+
699
+ def _set_db_data(span, cursor_or_db):
700
+ # type: (Span, Any) -> None
701
+ db = cursor_or_db.db if hasattr(cursor_or_db, "db") else cursor_or_db
702
+ vendor = db.vendor
703
+ span.set_data(SPANDATA.DB_SYSTEM, vendor)
704
+
705
+ # Some custom backends override `__getattr__`, making it look like `cursor_or_db`
706
+ # actually has a `connection` and the `connection` has a `get_dsn_parameters`
707
+ # attribute, only to throw an error once you actually want to call it.
708
+ # Hence the `inspect` check whether `get_dsn_parameters` is an actual callable
709
+ # function.
710
+ is_psycopg2 = (
711
+ hasattr(cursor_or_db, "connection")
712
+ and hasattr(cursor_or_db.connection, "get_dsn_parameters")
713
+ and inspect.isroutine(cursor_or_db.connection.get_dsn_parameters)
714
+ )
715
+ if is_psycopg2:
716
+ connection_params = cursor_or_db.connection.get_dsn_parameters()
717
+ else:
718
+ try:
719
+ # psycopg3, only extract needed params as get_parameters
720
+ # can be slow because of the additional logic to filter out default
721
+ # values
722
+ connection_params = {
723
+ "dbname": cursor_or_db.connection.info.dbname,
724
+ "port": cursor_or_db.connection.info.port,
725
+ }
726
+ # PGhost returns host or base dir of UNIX socket as an absolute path
727
+ # starting with /, use it only when it contains host
728
+ pg_host = cursor_or_db.connection.info.host
729
+ if pg_host and not pg_host.startswith("/"):
730
+ connection_params["host"] = pg_host
731
+ except Exception:
732
+ connection_params = db.get_connection_params()
733
+
734
+ db_name = connection_params.get("dbname") or connection_params.get("database")
735
+ if db_name is not None:
736
+ span.set_data(SPANDATA.DB_NAME, db_name)
737
+
738
+ server_address = connection_params.get("host")
739
+ if server_address is not None:
740
+ span.set_data(SPANDATA.SERVER_ADDRESS, server_address)
741
+
742
+ server_port = connection_params.get("port")
743
+ if server_port is not None:
744
+ span.set_data(SPANDATA.SERVER_PORT, str(server_port))
745
+
746
+ server_socket_address = connection_params.get("unix_socket")
747
+ if server_socket_address is not None:
748
+ span.set_data(SPANDATA.SERVER_SOCKET_ADDRESS, server_socket_address)
749
+
750
+
751
+ def add_template_context_repr_sequence():
752
+ # type: () -> None
753
+ try:
754
+ from django.template.context import BaseContext
755
+
756
+ add_repr_sequence_type(BaseContext)
757
+ except Exception:
758
+ pass