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.
- sentry_sdk/__init__.py +48 -6
- sentry_sdk/_compat.py +64 -56
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +81 -19
- sentry_sdk/_types.py +311 -11
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +35 -28
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +20 -11
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +71 -60
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +126 -125
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -1,48 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
from __future__ import absolute_import
|
|
3
|
-
|
|
1
|
+
import inspect
|
|
4
2
|
import sys
|
|
5
3
|
import threading
|
|
6
4
|
import weakref
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from sentry_sdk.
|
|
11
|
-
from sentry_sdk.
|
|
12
|
-
from sentry_sdk.
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
123
|
+
transaction_style = ""
|
|
83
124
|
middleware_spans = None
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
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
|
|
490
|
+
def _make_wsgi_request_event_processor(weak_request, integration):
|
|
361
491
|
# type: (Callable[[], WSGIRequest], DjangoIntegration) -> EventProcessor
|
|
362
|
-
def
|
|
363
|
-
# type: (
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
393
|
-
integration =
|
|
394
|
-
if integration is
|
|
520
|
+
client = sentry_sdk.get_client()
|
|
521
|
+
integration = client.get_integration(DjangoIntegration)
|
|
522
|
+
if integration is None:
|
|
523
|
+
return
|
|
395
524
|
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|