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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
|
|
3
|
+
from django.dispatch import Signal
|
|
4
|
+
|
|
5
|
+
import sentry_sdk
|
|
6
|
+
from sentry_sdk.consts import OP
|
|
7
|
+
from sentry_sdk.integrations.django import DJANGO_VERSION
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import Any, Union
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_receiver_name(receiver):
|
|
17
|
+
# type: (Callable[..., Any]) -> str
|
|
18
|
+
name = ""
|
|
19
|
+
|
|
20
|
+
if hasattr(receiver, "__qualname__"):
|
|
21
|
+
name = receiver.__qualname__
|
|
22
|
+
elif hasattr(receiver, "__name__"): # Python 2.7 has no __qualname__
|
|
23
|
+
name = receiver.__name__
|
|
24
|
+
elif hasattr(
|
|
25
|
+
receiver, "func"
|
|
26
|
+
): # certain functions (like partials) dont have a name
|
|
27
|
+
if hasattr(receiver, "func") and hasattr(receiver.func, "__name__"):
|
|
28
|
+
name = "partial(<function " + receiver.func.__name__ + ">)"
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
name == ""
|
|
32
|
+
): # In case nothing was found, return the string representation (this is the slowest case)
|
|
33
|
+
return str(receiver)
|
|
34
|
+
|
|
35
|
+
if hasattr(receiver, "__module__"): # prepend with module, if there is one
|
|
36
|
+
name = receiver.__module__ + "." + name
|
|
37
|
+
|
|
38
|
+
return name
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def patch_signals():
|
|
42
|
+
# type: () -> None
|
|
43
|
+
"""
|
|
44
|
+
Patch django signal receivers to create a span.
|
|
45
|
+
|
|
46
|
+
This only wraps sync receivers. Django>=5.0 introduced async receivers, but
|
|
47
|
+
since we don't create transactions for ASGI Django, we don't wrap them.
|
|
48
|
+
"""
|
|
49
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
50
|
+
|
|
51
|
+
old_live_receivers = Signal._live_receivers
|
|
52
|
+
|
|
53
|
+
def _sentry_live_receivers(self, sender):
|
|
54
|
+
# type: (Signal, Any) -> Union[tuple[list[Callable[..., Any]], list[Callable[..., Any]]], list[Callable[..., Any]]]
|
|
55
|
+
if DJANGO_VERSION >= (5, 0):
|
|
56
|
+
sync_receivers, async_receivers = old_live_receivers(self, sender)
|
|
57
|
+
else:
|
|
58
|
+
sync_receivers = old_live_receivers(self, sender)
|
|
59
|
+
async_receivers = []
|
|
60
|
+
|
|
61
|
+
def sentry_sync_receiver_wrapper(receiver):
|
|
62
|
+
# type: (Callable[..., Any]) -> Callable[..., Any]
|
|
63
|
+
@wraps(receiver)
|
|
64
|
+
def wrapper(*args, **kwargs):
|
|
65
|
+
# type: (Any, Any) -> Any
|
|
66
|
+
signal_name = _get_receiver_name(receiver)
|
|
67
|
+
with sentry_sdk.start_span(
|
|
68
|
+
op=OP.EVENT_DJANGO,
|
|
69
|
+
name=signal_name,
|
|
70
|
+
origin=DjangoIntegration.origin,
|
|
71
|
+
) as span:
|
|
72
|
+
span.set_data("signal", signal_name)
|
|
73
|
+
return receiver(*args, **kwargs)
|
|
74
|
+
|
|
75
|
+
return wrapper
|
|
76
|
+
|
|
77
|
+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
|
|
78
|
+
if (
|
|
79
|
+
integration
|
|
80
|
+
and integration.signals_spans
|
|
81
|
+
and self not in integration.signals_denylist
|
|
82
|
+
):
|
|
83
|
+
for idx, receiver in enumerate(sync_receivers):
|
|
84
|
+
sync_receivers[idx] = sentry_sync_receiver_wrapper(receiver)
|
|
85
|
+
|
|
86
|
+
if DJANGO_VERSION >= (5, 0):
|
|
87
|
+
return sync_receivers, async_receivers
|
|
88
|
+
else:
|
|
89
|
+
return sync_receivers
|
|
90
|
+
|
|
91
|
+
Signal._live_receivers = _sentry_live_receivers
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
1
3
|
from django.template import TemplateSyntaxError
|
|
4
|
+
from django.utils.safestring import mark_safe
|
|
5
|
+
from django import VERSION as DJANGO_VERSION
|
|
6
|
+
|
|
7
|
+
import sentry_sdk
|
|
8
|
+
from sentry_sdk.consts import OP
|
|
9
|
+
from sentry_sdk.utils import ensure_integration_enabled
|
|
2
10
|
|
|
3
|
-
from
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
4
12
|
|
|
5
|
-
if
|
|
13
|
+
if TYPE_CHECKING:
|
|
6
14
|
from typing import Any
|
|
7
15
|
from typing import Dict
|
|
8
16
|
from typing import Optional
|
|
@@ -40,6 +48,65 @@ def get_template_frame_from_exception(exc_value):
|
|
|
40
48
|
return None
|
|
41
49
|
|
|
42
50
|
|
|
51
|
+
def _get_template_name_description(template_name):
|
|
52
|
+
# type: (str) -> str
|
|
53
|
+
if isinstance(template_name, (list, tuple)):
|
|
54
|
+
if template_name:
|
|
55
|
+
return "[{}, ...]".format(template_name[0])
|
|
56
|
+
else:
|
|
57
|
+
return template_name
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def patch_templates():
|
|
61
|
+
# type: () -> None
|
|
62
|
+
from django.template.response import SimpleTemplateResponse
|
|
63
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
64
|
+
|
|
65
|
+
real_rendered_content = SimpleTemplateResponse.rendered_content
|
|
66
|
+
|
|
67
|
+
@property # type: ignore
|
|
68
|
+
@ensure_integration_enabled(DjangoIntegration, real_rendered_content.fget)
|
|
69
|
+
def rendered_content(self):
|
|
70
|
+
# type: (SimpleTemplateResponse) -> str
|
|
71
|
+
with sentry_sdk.start_span(
|
|
72
|
+
op=OP.TEMPLATE_RENDER,
|
|
73
|
+
name=_get_template_name_description(self.template_name),
|
|
74
|
+
origin=DjangoIntegration.origin,
|
|
75
|
+
) as span:
|
|
76
|
+
span.set_data("context", self.context_data)
|
|
77
|
+
return real_rendered_content.fget(self)
|
|
78
|
+
|
|
79
|
+
SimpleTemplateResponse.rendered_content = rendered_content
|
|
80
|
+
|
|
81
|
+
if DJANGO_VERSION < (1, 7):
|
|
82
|
+
return
|
|
83
|
+
import django.shortcuts
|
|
84
|
+
|
|
85
|
+
real_render = django.shortcuts.render
|
|
86
|
+
|
|
87
|
+
@functools.wraps(real_render)
|
|
88
|
+
@ensure_integration_enabled(DjangoIntegration, real_render)
|
|
89
|
+
def render(request, template_name, context=None, *args, **kwargs):
|
|
90
|
+
# type: (django.http.HttpRequest, str, Optional[Dict[str, Any]], *Any, **Any) -> django.http.HttpResponse
|
|
91
|
+
|
|
92
|
+
# Inject trace meta tags into template context
|
|
93
|
+
context = context or {}
|
|
94
|
+
if "sentry_trace_meta" not in context:
|
|
95
|
+
context["sentry_trace_meta"] = mark_safe(
|
|
96
|
+
sentry_sdk.get_current_scope().trace_propagation_meta()
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
with sentry_sdk.start_span(
|
|
100
|
+
op=OP.TEMPLATE_RENDER,
|
|
101
|
+
name=_get_template_name_description(template_name),
|
|
102
|
+
origin=DjangoIntegration.origin,
|
|
103
|
+
) as span:
|
|
104
|
+
span.set_data("context", context)
|
|
105
|
+
return real_render(request, template_name, context, *args, **kwargs)
|
|
106
|
+
|
|
107
|
+
django.shortcuts.render = render
|
|
108
|
+
|
|
109
|
+
|
|
43
110
|
def _get_template_frame_from_debug(debug):
|
|
44
111
|
# type: (Dict[str, Any]) -> Dict[str, Any]
|
|
45
112
|
if debug is None:
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Copied from raven-python.
|
|
3
|
-
`DjangoIntegration(transaction_fron="raven_legacy")`.
|
|
4
|
-
"""
|
|
2
|
+
Copied from raven-python.
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
Despite being called "legacy" in some places this resolver is very much still
|
|
5
|
+
in use.
|
|
6
|
+
"""
|
|
7
7
|
|
|
8
8
|
import re
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
11
|
|
|
12
|
-
if
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
13
|
from django.urls.resolvers import URLResolver
|
|
14
14
|
from typing import Dict
|
|
15
15
|
from typing import List
|
|
@@ -19,6 +19,13 @@ if MYPY:
|
|
|
19
19
|
from typing import Union
|
|
20
20
|
from re import Pattern
|
|
21
21
|
|
|
22
|
+
from django import VERSION as DJANGO_VERSION
|
|
23
|
+
|
|
24
|
+
if DJANGO_VERSION >= (2, 0):
|
|
25
|
+
from django.urls.resolvers import RoutePattern
|
|
26
|
+
else:
|
|
27
|
+
RoutePattern = None
|
|
28
|
+
|
|
22
29
|
try:
|
|
23
30
|
from django.urls import get_resolver
|
|
24
31
|
except ImportError:
|
|
@@ -35,9 +42,12 @@ def get_regex(resolver_or_pattern):
|
|
|
35
42
|
return regex
|
|
36
43
|
|
|
37
44
|
|
|
38
|
-
class RavenResolver
|
|
45
|
+
class RavenResolver:
|
|
46
|
+
_new_style_group_matcher = re.compile(
|
|
47
|
+
r"<(?:([^>:]+):)?([^>]+)>"
|
|
48
|
+
) # https://github.com/django/django/blob/21382e2743d06efbf5623e7c9b6dccf2a325669b/django/urls/resolvers.py#L245-L247
|
|
39
49
|
_optional_group_matcher = re.compile(r"\(\?\:([^\)]+)\)")
|
|
40
|
-
_named_group_matcher = re.compile(r"\(\?P<(\w+)>[^\)]+\)")
|
|
50
|
+
_named_group_matcher = re.compile(r"\(\?P<(\w+)>[^\)]+\)+")
|
|
41
51
|
_non_named_group_matcher = re.compile(r"\([^\)]+\)")
|
|
42
52
|
# [foo|bar|baz]
|
|
43
53
|
_either_option_matcher = re.compile(r"\[([^\]]+)\|([^\]]+)\]")
|
|
@@ -46,7 +56,7 @@ class RavenResolver(object):
|
|
|
46
56
|
_cache = {} # type: Dict[URLPattern, str]
|
|
47
57
|
|
|
48
58
|
def _simplify(self, pattern):
|
|
49
|
-
# type: (
|
|
59
|
+
# type: (Union[URLPattern, URLResolver]) -> str
|
|
50
60
|
r"""
|
|
51
61
|
Clean up urlpattern regexes into something readable by humans:
|
|
52
62
|
|
|
@@ -56,11 +66,24 @@ class RavenResolver(object):
|
|
|
56
66
|
To:
|
|
57
67
|
> "{sport_slug}/athletes/{athlete_slug}/"
|
|
58
68
|
"""
|
|
69
|
+
# "new-style" path patterns can be parsed directly without turning them
|
|
70
|
+
# into regexes first
|
|
71
|
+
if (
|
|
72
|
+
RoutePattern is not None
|
|
73
|
+
and hasattr(pattern, "pattern")
|
|
74
|
+
and isinstance(pattern.pattern, RoutePattern)
|
|
75
|
+
):
|
|
76
|
+
return self._new_style_group_matcher.sub(
|
|
77
|
+
lambda m: "{%s}" % m.group(2), str(pattern.pattern._route)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
result = get_regex(pattern).pattern
|
|
81
|
+
|
|
59
82
|
# remove optional params
|
|
60
83
|
# TODO(dcramer): it'd be nice to change these into [%s] but it currently
|
|
61
84
|
# conflicts with the other rules because we're doing regexp matches
|
|
62
85
|
# rather than parsing tokens
|
|
63
|
-
result = self._optional_group_matcher.sub(lambda m: "%s" % m.group(1),
|
|
86
|
+
result = self._optional_group_matcher.sub(lambda m: "%s" % m.group(1), result)
|
|
64
87
|
|
|
65
88
|
# handle named groups first
|
|
66
89
|
result = self._named_group_matcher.sub(lambda m: "{%s}" % m.group(1), result)
|
|
@@ -76,6 +99,8 @@ class RavenResolver(object):
|
|
|
76
99
|
result.replace("^", "")
|
|
77
100
|
.replace("$", "")
|
|
78
101
|
.replace("?", "")
|
|
102
|
+
.replace("\\A", "")
|
|
103
|
+
.replace("\\Z", "")
|
|
79
104
|
.replace("//", "/")
|
|
80
105
|
.replace("\\", "")
|
|
81
106
|
)
|
|
@@ -111,8 +136,8 @@ class RavenResolver(object):
|
|
|
111
136
|
except KeyError:
|
|
112
137
|
pass
|
|
113
138
|
|
|
114
|
-
prefix = "".join(self._simplify(
|
|
115
|
-
result = prefix + self._simplify(
|
|
139
|
+
prefix = "".join(self._simplify(p) for p in parents)
|
|
140
|
+
result = prefix + self._simplify(pattern)
|
|
116
141
|
if not result.startswith("/"):
|
|
117
142
|
result = "/" + result
|
|
118
143
|
self._cache[pattern] = result
|
|
@@ -125,10 +150,10 @@ class RavenResolver(object):
|
|
|
125
150
|
path, # type: str
|
|
126
151
|
urlconf=None, # type: Union[None, Tuple[URLPattern, URLPattern, URLResolver], Tuple[URLPattern]]
|
|
127
152
|
):
|
|
128
|
-
# type: (...) -> str
|
|
153
|
+
# type: (...) -> Optional[str]
|
|
129
154
|
resolver = get_resolver(urlconf)
|
|
130
155
|
match = self._resolve(resolver, path)
|
|
131
|
-
return match
|
|
156
|
+
return match
|
|
132
157
|
|
|
133
158
|
|
|
134
159
|
LEGACY_RESOLVER = RavenResolver()
|
|
@@ -1,20 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
from sentry_sdk._types import MYPY
|
|
3
|
-
from sentry_sdk import _functools
|
|
1
|
+
import functools
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.consts import OP
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
6
9
|
from typing import Any
|
|
7
10
|
|
|
8
11
|
|
|
12
|
+
try:
|
|
13
|
+
from asyncio import iscoroutinefunction
|
|
14
|
+
except ImportError:
|
|
15
|
+
iscoroutinefunction = None # type: ignore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from sentry_sdk.integrations.django.asgi import wrap_async_view
|
|
20
|
+
except (ImportError, SyntaxError):
|
|
21
|
+
wrap_async_view = None # type: ignore
|
|
22
|
+
|
|
23
|
+
|
|
9
24
|
def patch_views():
|
|
10
25
|
# type: () -> None
|
|
11
26
|
|
|
12
27
|
from django.core.handlers.base import BaseHandler
|
|
28
|
+
from django.template.response import SimpleTemplateResponse
|
|
13
29
|
from sentry_sdk.integrations.django import DjangoIntegration
|
|
14
30
|
|
|
15
31
|
old_make_view_atomic = BaseHandler.make_view_atomic
|
|
32
|
+
old_render = SimpleTemplateResponse.render
|
|
33
|
+
|
|
34
|
+
def sentry_patched_render(self):
|
|
35
|
+
# type: (SimpleTemplateResponse) -> Any
|
|
36
|
+
with sentry_sdk.start_span(
|
|
37
|
+
op=OP.VIEW_RESPONSE_RENDER,
|
|
38
|
+
name="serialize response",
|
|
39
|
+
origin=DjangoIntegration.origin,
|
|
40
|
+
):
|
|
41
|
+
return old_render(self)
|
|
16
42
|
|
|
17
|
-
@
|
|
43
|
+
@functools.wraps(old_make_view_atomic)
|
|
18
44
|
def sentry_patched_make_view_atomic(self, *args, **kwargs):
|
|
19
45
|
# type: (Any, *Any, **Any) -> Any
|
|
20
46
|
callback = old_make_view_atomic(self, *args, **kwargs)
|
|
@@ -22,22 +48,49 @@ def patch_views():
|
|
|
22
48
|
# XXX: The wrapper function is created for every request. Find more
|
|
23
49
|
# efficient way to wrap views (or build a cache?)
|
|
24
50
|
|
|
25
|
-
|
|
26
|
-
integration = hub.get_integration(DjangoIntegration)
|
|
27
|
-
|
|
51
|
+
integration = sentry_sdk.get_client().get_integration(DjangoIntegration)
|
|
28
52
|
if integration is not None and integration.middleware_spans:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
|
|
53
|
+
is_async_view = (
|
|
54
|
+
iscoroutinefunction is not None
|
|
55
|
+
and wrap_async_view is not None
|
|
56
|
+
and iscoroutinefunction(callback)
|
|
57
|
+
)
|
|
58
|
+
if is_async_view:
|
|
59
|
+
sentry_wrapped_callback = wrap_async_view(callback)
|
|
60
|
+
else:
|
|
61
|
+
sentry_wrapped_callback = _wrap_sync_view(callback)
|
|
37
62
|
|
|
38
63
|
else:
|
|
39
64
|
sentry_wrapped_callback = callback
|
|
40
65
|
|
|
41
66
|
return sentry_wrapped_callback
|
|
42
67
|
|
|
68
|
+
SimpleTemplateResponse.render = sentry_patched_render
|
|
43
69
|
BaseHandler.make_view_atomic = sentry_patched_make_view_atomic
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _wrap_sync_view(callback):
|
|
73
|
+
# type: (Any) -> Any
|
|
74
|
+
from sentry_sdk.integrations.django import DjangoIntegration
|
|
75
|
+
|
|
76
|
+
@functools.wraps(callback)
|
|
77
|
+
def sentry_wrapped_callback(request, *args, **kwargs):
|
|
78
|
+
# type: (Any, *Any, **Any) -> Any
|
|
79
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
80
|
+
if current_scope.transaction is not None:
|
|
81
|
+
current_scope.transaction.update_active_thread()
|
|
82
|
+
|
|
83
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
84
|
+
# set the active thread id to the handler thread for sync views
|
|
85
|
+
# this isn't necessary for async views since that runs on main
|
|
86
|
+
if sentry_scope.profile is not None:
|
|
87
|
+
sentry_scope.profile.update_active_thread_id()
|
|
88
|
+
|
|
89
|
+
with sentry_sdk.start_span(
|
|
90
|
+
op=OP.VIEW_RENDER,
|
|
91
|
+
name=request.resolver_match.view_name,
|
|
92
|
+
origin=DjangoIntegration.origin,
|
|
93
|
+
):
|
|
94
|
+
return callback(request, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
return sentry_wrapped_callback
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.consts import OP, SPANSTATUS
|
|
5
|
+
from sentry_sdk.api import continue_trace, get_baggage, get_traceparent
|
|
6
|
+
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
7
|
+
from sentry_sdk.integrations._wsgi_common import request_body_within_bounds
|
|
8
|
+
from sentry_sdk.tracing import (
|
|
9
|
+
BAGGAGE_HEADER_NAME,
|
|
10
|
+
SENTRY_TRACE_HEADER_NAME,
|
|
11
|
+
TransactionSource,
|
|
12
|
+
)
|
|
13
|
+
from sentry_sdk.utils import (
|
|
14
|
+
AnnotatedValue,
|
|
15
|
+
capture_internal_exceptions,
|
|
16
|
+
event_from_exception,
|
|
17
|
+
)
|
|
18
|
+
from typing import TypeVar
|
|
19
|
+
|
|
20
|
+
R = TypeVar("R")
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from dramatiq.broker import Broker
|
|
24
|
+
from dramatiq.middleware import Middleware, default_middleware
|
|
25
|
+
from dramatiq.errors import Retry
|
|
26
|
+
from dramatiq.message import Message
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise DidNotEnable("Dramatiq is not installed")
|
|
29
|
+
|
|
30
|
+
from typing import TYPE_CHECKING
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from typing import Any, Callable, Dict, Optional, Union
|
|
34
|
+
from sentry_sdk._types import Event, Hint
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DramatiqIntegration(Integration):
|
|
38
|
+
"""
|
|
39
|
+
Dramatiq integration for Sentry
|
|
40
|
+
|
|
41
|
+
Please make sure that you call `sentry_sdk.init` *before* initializing
|
|
42
|
+
your broker, as it monkey patches `Broker.__init__`.
|
|
43
|
+
|
|
44
|
+
This integration was originally developed and maintained
|
|
45
|
+
by https://github.com/jacobsvante and later donated to the Sentry
|
|
46
|
+
project.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
identifier = "dramatiq"
|
|
50
|
+
origin = f"auto.queue.{identifier}"
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def setup_once():
|
|
54
|
+
# type: () -> None
|
|
55
|
+
|
|
56
|
+
_patch_dramatiq_broker()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _patch_dramatiq_broker():
|
|
60
|
+
# type: () -> None
|
|
61
|
+
original_broker__init__ = Broker.__init__
|
|
62
|
+
|
|
63
|
+
def sentry_patched_broker__init__(self, *args, **kw):
|
|
64
|
+
# type: (Broker, *Any, **Any) -> None
|
|
65
|
+
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
middleware = kw.pop("middleware")
|
|
69
|
+
except KeyError:
|
|
70
|
+
# Unfortunately Broker and StubBroker allows middleware to be
|
|
71
|
+
# passed in as positional arguments, whilst RabbitmqBroker and
|
|
72
|
+
# RedisBroker does not.
|
|
73
|
+
if len(args) == 1:
|
|
74
|
+
middleware = args[0]
|
|
75
|
+
args = [] # type: ignore
|
|
76
|
+
else:
|
|
77
|
+
middleware = None
|
|
78
|
+
|
|
79
|
+
if middleware is None:
|
|
80
|
+
middleware = list(m() for m in default_middleware)
|
|
81
|
+
else:
|
|
82
|
+
middleware = list(middleware)
|
|
83
|
+
|
|
84
|
+
if integration is not None:
|
|
85
|
+
middleware = [m for m in middleware if not isinstance(m, SentryMiddleware)]
|
|
86
|
+
middleware.insert(0, SentryMiddleware())
|
|
87
|
+
|
|
88
|
+
kw["middleware"] = middleware
|
|
89
|
+
original_broker__init__(self, *args, **kw)
|
|
90
|
+
|
|
91
|
+
Broker.__init__ = sentry_patched_broker__init__
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SentryMiddleware(Middleware): # type: ignore[misc]
|
|
95
|
+
"""
|
|
96
|
+
A Dramatiq middleware that automatically captures and sends
|
|
97
|
+
exceptions to Sentry.
|
|
98
|
+
|
|
99
|
+
This is automatically added to every instantiated broker via the
|
|
100
|
+
DramatiqIntegration.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
SENTRY_HEADERS_NAME = "_sentry_headers"
|
|
104
|
+
|
|
105
|
+
def before_enqueue(self, broker, message, delay):
|
|
106
|
+
# type: (Broker, Message[R], int) -> None
|
|
107
|
+
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
108
|
+
if integration is None:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
message.options[self.SENTRY_HEADERS_NAME] = {
|
|
112
|
+
BAGGAGE_HEADER_NAME: get_baggage(),
|
|
113
|
+
SENTRY_TRACE_HEADER_NAME: get_traceparent(),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
def before_process_message(self, broker, message):
|
|
117
|
+
# type: (Broker, Message[R]) -> None
|
|
118
|
+
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
119
|
+
if integration is None:
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
message._scope_manager = sentry_sdk.isolation_scope()
|
|
123
|
+
scope = message._scope_manager.__enter__()
|
|
124
|
+
scope.clear_breadcrumbs()
|
|
125
|
+
scope.set_extra("dramatiq_message_id", message.message_id)
|
|
126
|
+
scope.add_event_processor(_make_message_event_processor(message, integration))
|
|
127
|
+
|
|
128
|
+
sentry_headers = message.options.get(self.SENTRY_HEADERS_NAME) or {}
|
|
129
|
+
if "retries" in message.options:
|
|
130
|
+
# start new trace in case of retrying
|
|
131
|
+
sentry_headers = {}
|
|
132
|
+
|
|
133
|
+
transaction = continue_trace(
|
|
134
|
+
sentry_headers,
|
|
135
|
+
name=message.actor_name,
|
|
136
|
+
op=OP.QUEUE_TASK_DRAMATIQ,
|
|
137
|
+
source=TransactionSource.TASK,
|
|
138
|
+
origin=DramatiqIntegration.origin,
|
|
139
|
+
)
|
|
140
|
+
transaction.set_status(SPANSTATUS.OK)
|
|
141
|
+
sentry_sdk.start_transaction(
|
|
142
|
+
transaction,
|
|
143
|
+
name=message.actor_name,
|
|
144
|
+
op=OP.QUEUE_TASK_DRAMATIQ,
|
|
145
|
+
source=TransactionSource.TASK,
|
|
146
|
+
)
|
|
147
|
+
transaction.__enter__()
|
|
148
|
+
|
|
149
|
+
def after_process_message(self, broker, message, *, result=None, exception=None):
|
|
150
|
+
# type: (Broker, Message[R], Optional[Any], Optional[Exception]) -> None
|
|
151
|
+
integration = sentry_sdk.get_client().get_integration(DramatiqIntegration)
|
|
152
|
+
if integration is None:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
actor = broker.get_actor(message.actor_name)
|
|
156
|
+
throws = message.options.get("throws") or actor.options.get("throws")
|
|
157
|
+
|
|
158
|
+
scope_manager = message._scope_manager
|
|
159
|
+
transaction = sentry_sdk.get_current_scope().transaction
|
|
160
|
+
if not transaction:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
is_event_capture_required = (
|
|
164
|
+
exception is not None
|
|
165
|
+
and not (throws and isinstance(exception, throws))
|
|
166
|
+
and not isinstance(exception, Retry)
|
|
167
|
+
)
|
|
168
|
+
if not is_event_capture_required:
|
|
169
|
+
# normal transaction finish
|
|
170
|
+
transaction.__exit__(None, None, None)
|
|
171
|
+
scope_manager.__exit__(None, None, None)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
event, hint = event_from_exception(
|
|
175
|
+
exception, # type: ignore[arg-type]
|
|
176
|
+
client_options=sentry_sdk.get_client().options,
|
|
177
|
+
mechanism={
|
|
178
|
+
"type": DramatiqIntegration.identifier,
|
|
179
|
+
"handled": False,
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
183
|
+
# transaction error
|
|
184
|
+
transaction.__exit__(type(exception), exception, None)
|
|
185
|
+
scope_manager.__exit__(type(exception), exception, None)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _make_message_event_processor(message, integration):
|
|
189
|
+
# type: (Message[R], DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]]
|
|
190
|
+
|
|
191
|
+
def inner(event, hint):
|
|
192
|
+
# type: (Event, Hint) -> Optional[Event]
|
|
193
|
+
with capture_internal_exceptions():
|
|
194
|
+
DramatiqMessageExtractor(message).extract_into_event(event)
|
|
195
|
+
|
|
196
|
+
return event
|
|
197
|
+
|
|
198
|
+
return inner
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class DramatiqMessageExtractor:
|
|
202
|
+
def __init__(self, message):
|
|
203
|
+
# type: (Message[R]) -> None
|
|
204
|
+
self.message_data = dict(message.asdict())
|
|
205
|
+
|
|
206
|
+
def content_length(self):
|
|
207
|
+
# type: () -> int
|
|
208
|
+
return len(json.dumps(self.message_data))
|
|
209
|
+
|
|
210
|
+
def extract_into_event(self, event):
|
|
211
|
+
# type: (Event) -> None
|
|
212
|
+
client = sentry_sdk.get_client()
|
|
213
|
+
if not client.is_active():
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
contexts = event.setdefault("contexts", {})
|
|
217
|
+
request_info = contexts.setdefault("dramatiq", {})
|
|
218
|
+
request_info["type"] = "dramatiq"
|
|
219
|
+
|
|
220
|
+
data = None # type: Optional[Union[AnnotatedValue, Dict[str, Any]]]
|
|
221
|
+
if not request_body_within_bounds(client, self.content_length()):
|
|
222
|
+
data = AnnotatedValue.removed_because_over_size_limit()
|
|
223
|
+
else:
|
|
224
|
+
data = self.message_data
|
|
225
|
+
|
|
226
|
+
request_info["data"] = data
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
from sentry_sdk.utils import
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.utils import (
|
|
5
|
+
capture_internal_exceptions,
|
|
6
|
+
event_from_exception,
|
|
7
|
+
)
|
|
5
8
|
from sentry_sdk.integrations import Integration
|
|
6
9
|
|
|
7
|
-
from
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
8
11
|
|
|
9
|
-
if
|
|
12
|
+
if TYPE_CHECKING:
|
|
10
13
|
from typing import Callable
|
|
11
14
|
from typing import Any
|
|
12
15
|
from typing import Type
|
|
16
|
+
from typing import Optional
|
|
13
17
|
|
|
14
18
|
from types import TracebackType
|
|
15
19
|
|
|
16
20
|
Excepthook = Callable[
|
|
17
|
-
[Type[BaseException], BaseException, TracebackType],
|
|
21
|
+
[Type[BaseException], BaseException, Optional[TracebackType]],
|
|
18
22
|
Any,
|
|
19
23
|
]
|
|
20
24
|
|
|
@@ -43,21 +47,23 @@ class ExcepthookIntegration(Integration):
|
|
|
43
47
|
def _make_excepthook(old_excepthook):
|
|
44
48
|
# type: (Excepthook) -> Excepthook
|
|
45
49
|
def sentry_sdk_excepthook(type_, value, traceback):
|
|
46
|
-
# type: (Type[BaseException], BaseException, TracebackType) -> None
|
|
47
|
-
|
|
48
|
-
integration = hub.get_integration(ExcepthookIntegration)
|
|
50
|
+
# type: (Type[BaseException], BaseException, Optional[TracebackType]) -> None
|
|
51
|
+
integration = sentry_sdk.get_client().get_integration(ExcepthookIntegration)
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
# Note: If we replace this with ensure_integration_enabled then
|
|
54
|
+
# we break the exceptiongroup backport;
|
|
55
|
+
# See: https://github.com/getsentry/sentry-python/issues/3097
|
|
56
|
+
if integration is None:
|
|
57
|
+
return old_excepthook(type_, value, traceback)
|
|
53
58
|
|
|
59
|
+
if _should_send(integration.always_run):
|
|
54
60
|
with capture_internal_exceptions():
|
|
55
61
|
event, hint = event_from_exception(
|
|
56
62
|
(type_, value, traceback),
|
|
57
|
-
client_options=
|
|
63
|
+
client_options=sentry_sdk.get_client().options,
|
|
58
64
|
mechanism={"type": "excepthook", "handled": False},
|
|
59
65
|
)
|
|
60
|
-
|
|
66
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
61
67
|
|
|
62
68
|
return old_excepthook(type_, value, traceback)
|
|
63
69
|
|