sentry-sdk 0.7.5__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 -30
- sentry_sdk/_compat.py +74 -61
- 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 +289 -0
- sentry_sdk/_types.py +338 -0
- 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 +496 -80
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +1023 -103
- sentry_sdk/consts.py +1438 -66
- 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 +15 -14
- sentry_sdk/envelope.py +369 -0
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +611 -280
- sentry_sdk/integrations/__init__.py +276 -49
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +180 -44
- sentry_sdk/integrations/aiohttp.py +291 -42
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +9 -8
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +341 -0
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +17 -10
- sentry_sdk/integrations/aws_lambda.py +377 -62
- sentry_sdk/integrations/beam.py +176 -0
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +221 -0
- 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 +134 -0
- 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 +48 -14
- sentry_sdk/integrations/django/__init__.py +584 -191
- sentry_sdk/integrations/django/asgi.py +245 -0
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +187 -0
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +79 -5
- sentry_sdk/integrations/django/transactions.py +49 -22
- sentry_sdk/integrations/django/views.py +96 -0
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +50 -13
- sentry_sdk/integrations/executing.py +67 -0
- sentry_sdk/integrations/falcon.py +272 -0
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +142 -88
- sentry_sdk/integrations/gcp.py +239 -0
- sentry_sdk/integrations/gnu_backtrace.py +99 -0
- 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 +307 -96
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +14 -31
- 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 +141 -0
- 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 +112 -68
- 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 +95 -37
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +294 -123
- sentry_sdk/integrations/serverless.py +48 -19
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/__init__.py +4 -0
- sentry_sdk/integrations/spark/spark_driver.py +316 -0
- sentry_sdk/integrations/spark/spark_worker.py +116 -0
- sentry_sdk/integrations/sqlalchemy.py +142 -0
- 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 +235 -29
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +158 -28
- sentry_sdk/integrations/tornado.py +84 -52
- sentry_sdk/integrations/trytond.py +50 -0
- 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 +201 -119
- 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/py.typed +0 -0
- sentry_sdk/scope.py +1713 -85
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +405 -0
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +275 -0
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1486 -0
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +806 -134
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1625 -465
- sentry_sdk/worker.py +54 -25
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.7.5.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/integrations/celery.py +0 -119
- sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
- sentry_sdk-0.7.5.dist-info/METADATA +0 -36
- sentry_sdk-0.7.5.dist-info/RECORD +0 -39
- {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.consts import OP
|
|
5
|
+
from sentry_sdk.integrations import DidNotEnable, Integration
|
|
6
|
+
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
|
|
7
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
8
|
+
from sentry_sdk.tracing import SOURCE_FOR_STYLE, TransactionSource
|
|
9
|
+
from sentry_sdk.utils import (
|
|
10
|
+
ensure_integration_enabled,
|
|
11
|
+
event_from_exception,
|
|
12
|
+
transaction_from_function,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from starlite import Request, Starlite, State # type: ignore
|
|
17
|
+
from starlite.handlers.base import BaseRouteHandler # type: ignore
|
|
18
|
+
from starlite.middleware import DefineMiddleware # type: ignore
|
|
19
|
+
from starlite.plugins.base import get_plugin_for_value # type: ignore
|
|
20
|
+
from starlite.routes.http import HTTPRoute # type: ignore
|
|
21
|
+
from starlite.utils import ConnectionDataExtractor, is_async_callable, Ref # type: ignore
|
|
22
|
+
from pydantic import BaseModel # type: ignore
|
|
23
|
+
except ImportError:
|
|
24
|
+
raise DidNotEnable("Starlite is not installed")
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from typing import Any, Optional, Union
|
|
30
|
+
from starlite.types import ( # type: ignore
|
|
31
|
+
ASGIApp,
|
|
32
|
+
Hint,
|
|
33
|
+
HTTPReceiveMessage,
|
|
34
|
+
HTTPScope,
|
|
35
|
+
Message,
|
|
36
|
+
Middleware,
|
|
37
|
+
Receive,
|
|
38
|
+
Scope as StarliteScope,
|
|
39
|
+
Send,
|
|
40
|
+
WebSocketReceiveMessage,
|
|
41
|
+
)
|
|
42
|
+
from starlite import MiddlewareProtocol
|
|
43
|
+
from sentry_sdk._types import Event
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_DEFAULT_TRANSACTION_NAME = "generic Starlite request"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class StarliteIntegration(Integration):
|
|
50
|
+
identifier = "starlite"
|
|
51
|
+
origin = f"auto.http.{identifier}"
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def setup_once():
|
|
55
|
+
# type: () -> None
|
|
56
|
+
patch_app_init()
|
|
57
|
+
patch_middlewares()
|
|
58
|
+
patch_http_route_handle()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SentryStarliteASGIMiddleware(SentryAsgiMiddleware):
|
|
62
|
+
def __init__(self, app, span_origin=StarliteIntegration.origin):
|
|
63
|
+
# type: (ASGIApp, str) -> None
|
|
64
|
+
super().__init__(
|
|
65
|
+
app=app,
|
|
66
|
+
unsafe_context_data=False,
|
|
67
|
+
transaction_style="endpoint",
|
|
68
|
+
mechanism_type="asgi",
|
|
69
|
+
span_origin=span_origin,
|
|
70
|
+
asgi_version=3,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def patch_app_init():
|
|
75
|
+
# type: () -> None
|
|
76
|
+
"""
|
|
77
|
+
Replaces the Starlite class's `__init__` function in order to inject `after_exception` handlers and set the
|
|
78
|
+
`SentryStarliteASGIMiddleware` as the outmost middleware in the stack.
|
|
79
|
+
See:
|
|
80
|
+
- https://starlite-api.github.io/starlite/usage/0-the-starlite-app/5-application-hooks/#after-exception
|
|
81
|
+
- https://starlite-api.github.io/starlite/usage/7-middleware/0-middleware-intro/
|
|
82
|
+
"""
|
|
83
|
+
old__init__ = Starlite.__init__
|
|
84
|
+
|
|
85
|
+
@ensure_integration_enabled(StarliteIntegration, old__init__)
|
|
86
|
+
def injection_wrapper(self, *args, **kwargs):
|
|
87
|
+
# type: (Starlite, *Any, **Any) -> None
|
|
88
|
+
after_exception = kwargs.pop("after_exception", [])
|
|
89
|
+
kwargs.update(
|
|
90
|
+
after_exception=[
|
|
91
|
+
exception_handler,
|
|
92
|
+
*(
|
|
93
|
+
after_exception
|
|
94
|
+
if isinstance(after_exception, list)
|
|
95
|
+
else [after_exception]
|
|
96
|
+
),
|
|
97
|
+
]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
middleware = kwargs.get("middleware") or []
|
|
101
|
+
kwargs["middleware"] = [SentryStarliteASGIMiddleware, *middleware]
|
|
102
|
+
old__init__(self, *args, **kwargs)
|
|
103
|
+
|
|
104
|
+
Starlite.__init__ = injection_wrapper
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def patch_middlewares():
|
|
108
|
+
# type: () -> None
|
|
109
|
+
old_resolve_middleware_stack = BaseRouteHandler.resolve_middleware
|
|
110
|
+
|
|
111
|
+
@ensure_integration_enabled(StarliteIntegration, old_resolve_middleware_stack)
|
|
112
|
+
def resolve_middleware_wrapper(self):
|
|
113
|
+
# type: (BaseRouteHandler) -> list[Middleware]
|
|
114
|
+
return [
|
|
115
|
+
enable_span_for_middleware(middleware)
|
|
116
|
+
for middleware in old_resolve_middleware_stack(self)
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
BaseRouteHandler.resolve_middleware = resolve_middleware_wrapper
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def enable_span_for_middleware(middleware):
|
|
123
|
+
# type: (Middleware) -> Middleware
|
|
124
|
+
if (
|
|
125
|
+
not hasattr(middleware, "__call__") # noqa: B004
|
|
126
|
+
or middleware is SentryStarliteASGIMiddleware
|
|
127
|
+
):
|
|
128
|
+
return middleware
|
|
129
|
+
|
|
130
|
+
if isinstance(middleware, DefineMiddleware):
|
|
131
|
+
old_call = middleware.middleware.__call__ # type: ASGIApp
|
|
132
|
+
else:
|
|
133
|
+
old_call = middleware.__call__
|
|
134
|
+
|
|
135
|
+
async def _create_span_call(self, scope, receive, send):
|
|
136
|
+
# type: (MiddlewareProtocol, StarliteScope, Receive, Send) -> None
|
|
137
|
+
if sentry_sdk.get_client().get_integration(StarliteIntegration) is None:
|
|
138
|
+
return await old_call(self, scope, receive, send)
|
|
139
|
+
|
|
140
|
+
middleware_name = self.__class__.__name__
|
|
141
|
+
with sentry_sdk.start_span(
|
|
142
|
+
op=OP.MIDDLEWARE_STARLITE,
|
|
143
|
+
name=middleware_name,
|
|
144
|
+
origin=StarliteIntegration.origin,
|
|
145
|
+
) as middleware_span:
|
|
146
|
+
middleware_span.set_tag("starlite.middleware_name", middleware_name)
|
|
147
|
+
|
|
148
|
+
# Creating spans for the "receive" callback
|
|
149
|
+
async def _sentry_receive(*args, **kwargs):
|
|
150
|
+
# type: (*Any, **Any) -> Union[HTTPReceiveMessage, WebSocketReceiveMessage]
|
|
151
|
+
if sentry_sdk.get_client().get_integration(StarliteIntegration) is None:
|
|
152
|
+
return await receive(*args, **kwargs)
|
|
153
|
+
with sentry_sdk.start_span(
|
|
154
|
+
op=OP.MIDDLEWARE_STARLITE_RECEIVE,
|
|
155
|
+
name=getattr(receive, "__qualname__", str(receive)),
|
|
156
|
+
origin=StarliteIntegration.origin,
|
|
157
|
+
) as span:
|
|
158
|
+
span.set_tag("starlite.middleware_name", middleware_name)
|
|
159
|
+
return await receive(*args, **kwargs)
|
|
160
|
+
|
|
161
|
+
receive_name = getattr(receive, "__name__", str(receive))
|
|
162
|
+
receive_patched = receive_name == "_sentry_receive"
|
|
163
|
+
new_receive = _sentry_receive if not receive_patched else receive
|
|
164
|
+
|
|
165
|
+
# Creating spans for the "send" callback
|
|
166
|
+
async def _sentry_send(message):
|
|
167
|
+
# type: (Message) -> None
|
|
168
|
+
if sentry_sdk.get_client().get_integration(StarliteIntegration) is None:
|
|
169
|
+
return await send(message)
|
|
170
|
+
with sentry_sdk.start_span(
|
|
171
|
+
op=OP.MIDDLEWARE_STARLITE_SEND,
|
|
172
|
+
name=getattr(send, "__qualname__", str(send)),
|
|
173
|
+
origin=StarliteIntegration.origin,
|
|
174
|
+
) as span:
|
|
175
|
+
span.set_tag("starlite.middleware_name", middleware_name)
|
|
176
|
+
return await send(message)
|
|
177
|
+
|
|
178
|
+
send_name = getattr(send, "__name__", str(send))
|
|
179
|
+
send_patched = send_name == "_sentry_send"
|
|
180
|
+
new_send = _sentry_send if not send_patched else send
|
|
181
|
+
|
|
182
|
+
return await old_call(self, scope, new_receive, new_send)
|
|
183
|
+
|
|
184
|
+
not_yet_patched = old_call.__name__ not in ["_create_span_call"]
|
|
185
|
+
|
|
186
|
+
if not_yet_patched:
|
|
187
|
+
if isinstance(middleware, DefineMiddleware):
|
|
188
|
+
middleware.middleware.__call__ = _create_span_call
|
|
189
|
+
else:
|
|
190
|
+
middleware.__call__ = _create_span_call
|
|
191
|
+
|
|
192
|
+
return middleware
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def patch_http_route_handle():
|
|
196
|
+
# type: () -> None
|
|
197
|
+
old_handle = HTTPRoute.handle
|
|
198
|
+
|
|
199
|
+
async def handle_wrapper(self, scope, receive, send):
|
|
200
|
+
# type: (HTTPRoute, HTTPScope, Receive, Send) -> None
|
|
201
|
+
if sentry_sdk.get_client().get_integration(StarliteIntegration) is None:
|
|
202
|
+
return await old_handle(self, scope, receive, send)
|
|
203
|
+
|
|
204
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
205
|
+
request = scope["app"].request_class(scope=scope, receive=receive, send=send) # type: Request[Any, Any]
|
|
206
|
+
extracted_request_data = ConnectionDataExtractor(
|
|
207
|
+
parse_body=True, parse_query=True
|
|
208
|
+
)(request)
|
|
209
|
+
body = extracted_request_data.pop("body")
|
|
210
|
+
|
|
211
|
+
request_data = await body
|
|
212
|
+
|
|
213
|
+
def event_processor(event, _):
|
|
214
|
+
# type: (Event, Hint) -> Event
|
|
215
|
+
route_handler = scope.get("route_handler")
|
|
216
|
+
|
|
217
|
+
request_info = event.get("request", {})
|
|
218
|
+
request_info["content_length"] = len(scope.get("_body", b""))
|
|
219
|
+
if should_send_default_pii():
|
|
220
|
+
request_info["cookies"] = extracted_request_data["cookies"]
|
|
221
|
+
if request_data is not None:
|
|
222
|
+
request_info["data"] = request_data
|
|
223
|
+
|
|
224
|
+
func = None
|
|
225
|
+
if route_handler.name is not None:
|
|
226
|
+
tx_name = route_handler.name
|
|
227
|
+
elif isinstance(route_handler.fn, Ref):
|
|
228
|
+
func = route_handler.fn.value
|
|
229
|
+
else:
|
|
230
|
+
func = route_handler.fn
|
|
231
|
+
if func is not None:
|
|
232
|
+
tx_name = transaction_from_function(func)
|
|
233
|
+
|
|
234
|
+
tx_info = {"source": SOURCE_FOR_STYLE["endpoint"]}
|
|
235
|
+
|
|
236
|
+
if not tx_name:
|
|
237
|
+
tx_name = _DEFAULT_TRANSACTION_NAME
|
|
238
|
+
tx_info = {"source": TransactionSource.ROUTE}
|
|
239
|
+
|
|
240
|
+
event.update(
|
|
241
|
+
{
|
|
242
|
+
"request": deepcopy(request_info),
|
|
243
|
+
"transaction": tx_name,
|
|
244
|
+
"transaction_info": tx_info,
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
return event
|
|
248
|
+
|
|
249
|
+
sentry_scope._name = StarliteIntegration.identifier
|
|
250
|
+
sentry_scope.add_event_processor(event_processor)
|
|
251
|
+
|
|
252
|
+
return await old_handle(self, scope, receive, send)
|
|
253
|
+
|
|
254
|
+
HTTPRoute.handle = handle_wrapper
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def retrieve_user_from_scope(scope):
|
|
258
|
+
# type: (StarliteScope) -> Optional[dict[str, Any]]
|
|
259
|
+
scope_user = scope.get("user")
|
|
260
|
+
if not scope_user:
|
|
261
|
+
return None
|
|
262
|
+
if isinstance(scope_user, dict):
|
|
263
|
+
return scope_user
|
|
264
|
+
if isinstance(scope_user, BaseModel):
|
|
265
|
+
return scope_user.dict()
|
|
266
|
+
if hasattr(scope_user, "asdict"): # dataclasses
|
|
267
|
+
return scope_user.asdict()
|
|
268
|
+
|
|
269
|
+
plugin = get_plugin_for_value(scope_user)
|
|
270
|
+
if plugin and not is_async_callable(plugin.to_dict):
|
|
271
|
+
return plugin.to_dict(scope_user)
|
|
272
|
+
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@ensure_integration_enabled(StarliteIntegration)
|
|
277
|
+
def exception_handler(exc, scope, _):
|
|
278
|
+
# type: (Exception, StarliteScope, State) -> None
|
|
279
|
+
user_info = None # type: Optional[dict[str, Any]]
|
|
280
|
+
if should_send_default_pii():
|
|
281
|
+
user_info = retrieve_user_from_scope(scope)
|
|
282
|
+
if user_info and isinstance(user_info, dict):
|
|
283
|
+
sentry_scope = sentry_sdk.get_isolation_scope()
|
|
284
|
+
sentry_scope.set_user(user_info)
|
|
285
|
+
|
|
286
|
+
event, hint = event_from_exception(
|
|
287
|
+
exc,
|
|
288
|
+
client_options=sentry_sdk.get_client().options,
|
|
289
|
+
mechanism={"type": StarliteIntegration.identifier, "handled": False},
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from sentry_sdk.feature_flags import add_feature_flag
|
|
5
|
+
from sentry_sdk.integrations import Integration, DidNotEnable, _check_minimum_version
|
|
6
|
+
from sentry_sdk.utils import parse_version
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from statsig import statsig as statsig_module
|
|
10
|
+
from statsig.version import __version__ as STATSIG_VERSION
|
|
11
|
+
except ImportError:
|
|
12
|
+
raise DidNotEnable("statsig is not installed")
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from statsig.statsig_user import StatsigUser
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StatsigIntegration(Integration):
|
|
19
|
+
identifier = "statsig"
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def setup_once():
|
|
23
|
+
# type: () -> None
|
|
24
|
+
version = parse_version(STATSIG_VERSION)
|
|
25
|
+
_check_minimum_version(StatsigIntegration, version, "statsig")
|
|
26
|
+
|
|
27
|
+
# Wrap and patch evaluation method(s) in the statsig module
|
|
28
|
+
old_check_gate = statsig_module.check_gate
|
|
29
|
+
|
|
30
|
+
@wraps(old_check_gate)
|
|
31
|
+
def sentry_check_gate(user, gate, *args, **kwargs):
|
|
32
|
+
# type: (StatsigUser, str, *Any, **Any) -> Any
|
|
33
|
+
enabled = old_check_gate(user, gate, *args, **kwargs)
|
|
34
|
+
add_feature_flag(gate, enabled)
|
|
35
|
+
return enabled
|
|
36
|
+
|
|
37
|
+
statsig_module.check_gate = sentry_check_gate
|
|
@@ -1,11 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
import platform
|
|
5
|
+
from http.client import HTTPConnection
|
|
6
|
+
|
|
7
|
+
import sentry_sdk
|
|
8
|
+
from sentry_sdk.consts import OP, SPANDATA
|
|
2
9
|
from sentry_sdk.integrations import Integration
|
|
10
|
+
from sentry_sdk.scope import add_global_event_processor
|
|
11
|
+
from sentry_sdk.tracing_utils import (
|
|
12
|
+
EnvironHeaders,
|
|
13
|
+
should_propagate_trace,
|
|
14
|
+
add_http_request_source,
|
|
15
|
+
)
|
|
16
|
+
from sentry_sdk.utils import (
|
|
17
|
+
SENSITIVE_DATA_SUBSTITUTE,
|
|
18
|
+
capture_internal_exceptions,
|
|
19
|
+
ensure_integration_enabled,
|
|
20
|
+
is_sentry_url,
|
|
21
|
+
logger,
|
|
22
|
+
safe_repr,
|
|
23
|
+
parse_url,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from typing import Any
|
|
30
|
+
from typing import Callable
|
|
31
|
+
from typing import Dict
|
|
32
|
+
from typing import Optional
|
|
33
|
+
from typing import List
|
|
34
|
+
|
|
35
|
+
from sentry_sdk._types import Event, Hint
|
|
3
36
|
|
|
4
37
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
38
|
+
_RUNTIME_CONTEXT = {
|
|
39
|
+
"name": platform.python_implementation(),
|
|
40
|
+
"version": "%s.%s.%s" % (sys.version_info[:3]),
|
|
41
|
+
"build": sys.version,
|
|
42
|
+
} # type: dict[str, object]
|
|
9
43
|
|
|
10
44
|
|
|
11
45
|
class StdlibIntegration(Integration):
|
|
@@ -14,27 +48,39 @@ class StdlibIntegration(Integration):
|
|
|
14
48
|
@staticmethod
|
|
15
49
|
def setup_once():
|
|
16
50
|
# type: () -> None
|
|
17
|
-
|
|
51
|
+
_install_httplib()
|
|
52
|
+
_install_subprocess()
|
|
18
53
|
|
|
54
|
+
@add_global_event_processor
|
|
55
|
+
def add_python_runtime_context(event, hint):
|
|
56
|
+
# type: (Event, Hint) -> Optional[Event]
|
|
57
|
+
if sentry_sdk.get_client().get_integration(StdlibIntegration) is not None:
|
|
58
|
+
contexts = event.setdefault("contexts", {})
|
|
59
|
+
if isinstance(contexts, dict) and "runtime" not in contexts:
|
|
60
|
+
contexts["runtime"] = _RUNTIME_CONTEXT
|
|
19
61
|
|
|
20
|
-
|
|
62
|
+
return event
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _install_httplib():
|
|
21
66
|
# type: () -> None
|
|
22
67
|
real_putrequest = HTTPConnection.putrequest
|
|
23
68
|
real_getresponse = HTTPConnection.getresponse
|
|
24
69
|
|
|
25
70
|
def putrequest(self, method, url, *args, **kwargs):
|
|
26
|
-
|
|
27
|
-
if Hub.current.get_integration(StdlibIntegration) is None:
|
|
28
|
-
return rv
|
|
29
|
-
|
|
30
|
-
self._sentrysdk_data_dict = data = {}
|
|
31
|
-
|
|
71
|
+
# type: (HTTPConnection, str, str, *Any, **Any) -> Any
|
|
32
72
|
host = self.host
|
|
33
73
|
port = self.port
|
|
34
74
|
default_port = self.default_port
|
|
35
75
|
|
|
76
|
+
client = sentry_sdk.get_client()
|
|
77
|
+
if client.get_integration(StdlibIntegration) is None or is_sentry_url(
|
|
78
|
+
client, host
|
|
79
|
+
):
|
|
80
|
+
return real_putrequest(self, method, url, *args, **kwargs)
|
|
81
|
+
|
|
36
82
|
real_url = url
|
|
37
|
-
if not real_url.startswith(("http://", "https://")):
|
|
83
|
+
if real_url is None or not real_url.startswith(("http://", "https://")):
|
|
38
84
|
real_url = "%s://%s%s%s" % (
|
|
39
85
|
default_port == 443 and "https" or "http",
|
|
40
86
|
host,
|
|
@@ -42,25 +88,185 @@ def install_httplib():
|
|
|
42
88
|
url,
|
|
43
89
|
)
|
|
44
90
|
|
|
45
|
-
|
|
46
|
-
|
|
91
|
+
parsed_url = None
|
|
92
|
+
with capture_internal_exceptions():
|
|
93
|
+
parsed_url = parse_url(real_url, sanitize=False)
|
|
94
|
+
|
|
95
|
+
span = sentry_sdk.start_span(
|
|
96
|
+
op=OP.HTTP_CLIENT,
|
|
97
|
+
name="%s %s"
|
|
98
|
+
% (method, parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE),
|
|
99
|
+
origin="auto.http.stdlib.httplib",
|
|
100
|
+
)
|
|
101
|
+
span.set_data(SPANDATA.HTTP_METHOD, method)
|
|
102
|
+
if parsed_url is not None:
|
|
103
|
+
span.set_data("url", parsed_url.url)
|
|
104
|
+
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
|
|
105
|
+
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
|
|
106
|
+
|
|
107
|
+
rv = real_putrequest(self, method, url, *args, **kwargs)
|
|
108
|
+
|
|
109
|
+
if should_propagate_trace(client, real_url):
|
|
110
|
+
for (
|
|
111
|
+
key,
|
|
112
|
+
value,
|
|
113
|
+
) in sentry_sdk.get_current_scope().iter_trace_propagation_headers(
|
|
114
|
+
span=span
|
|
115
|
+
):
|
|
116
|
+
logger.debug(
|
|
117
|
+
"[Tracing] Adding `{key}` header {value} to outgoing request to {real_url}.".format(
|
|
118
|
+
key=key, value=value, real_url=real_url
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
self.putheader(key, value)
|
|
122
|
+
|
|
123
|
+
self._sentrysdk_span = span # type: ignore[attr-defined]
|
|
124
|
+
|
|
47
125
|
return rv
|
|
48
126
|
|
|
49
127
|
def getresponse(self, *args, **kwargs):
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if hub.get_integration(StdlibIntegration) is None:
|
|
53
|
-
return rv
|
|
128
|
+
# type: (HTTPConnection, *Any, **Any) -> Any
|
|
129
|
+
span = getattr(self, "_sentrysdk_span", None)
|
|
54
130
|
|
|
55
|
-
|
|
131
|
+
if span is None:
|
|
132
|
+
return real_getresponse(self, *args, **kwargs)
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
rv = real_getresponse(self, *args, **kwargs)
|
|
136
|
+
|
|
137
|
+
span.set_http_status(int(rv.status))
|
|
138
|
+
span.set_data("reason", rv.reason)
|
|
139
|
+
finally:
|
|
140
|
+
span.finish()
|
|
141
|
+
|
|
142
|
+
with capture_internal_exceptions():
|
|
143
|
+
add_http_request_source(span)
|
|
56
144
|
|
|
57
|
-
if "status_code" not in data:
|
|
58
|
-
data["status_code"] = rv.status
|
|
59
|
-
data["reason"] = rv.reason
|
|
60
|
-
hub.add_breadcrumb(
|
|
61
|
-
type="http", category="httplib", data=data, hint={"httplib_response": rv}
|
|
62
|
-
)
|
|
63
145
|
return rv
|
|
64
146
|
|
|
65
|
-
HTTPConnection.putrequest = putrequest
|
|
66
|
-
HTTPConnection.getresponse = getresponse
|
|
147
|
+
HTTPConnection.putrequest = putrequest # type: ignore[method-assign]
|
|
148
|
+
HTTPConnection.getresponse = getresponse # type: ignore[method-assign]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _init_argument(args, kwargs, name, position, setdefault_callback=None):
|
|
152
|
+
# type: (List[Any], Dict[Any, Any], str, int, Optional[Callable[[Any], Any]]) -> Any
|
|
153
|
+
"""
|
|
154
|
+
given (*args, **kwargs) of a function call, retrieve (and optionally set a
|
|
155
|
+
default for) an argument by either name or position.
|
|
156
|
+
|
|
157
|
+
This is useful for wrapping functions with complex type signatures and
|
|
158
|
+
extracting a few arguments without needing to redefine that function's
|
|
159
|
+
entire type signature.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
if name in kwargs:
|
|
163
|
+
rv = kwargs[name]
|
|
164
|
+
if setdefault_callback is not None:
|
|
165
|
+
rv = setdefault_callback(rv)
|
|
166
|
+
if rv is not None:
|
|
167
|
+
kwargs[name] = rv
|
|
168
|
+
elif position < len(args):
|
|
169
|
+
rv = args[position]
|
|
170
|
+
if setdefault_callback is not None:
|
|
171
|
+
rv = setdefault_callback(rv)
|
|
172
|
+
if rv is not None:
|
|
173
|
+
args[position] = rv
|
|
174
|
+
else:
|
|
175
|
+
rv = setdefault_callback and setdefault_callback(None)
|
|
176
|
+
if rv is not None:
|
|
177
|
+
kwargs[name] = rv
|
|
178
|
+
|
|
179
|
+
return rv
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _install_subprocess():
|
|
183
|
+
# type: () -> None
|
|
184
|
+
old_popen_init = subprocess.Popen.__init__
|
|
185
|
+
|
|
186
|
+
@ensure_integration_enabled(StdlibIntegration, old_popen_init)
|
|
187
|
+
def sentry_patched_popen_init(self, *a, **kw):
|
|
188
|
+
# type: (subprocess.Popen[Any], *Any, **Any) -> None
|
|
189
|
+
# Convert from tuple to list to be able to set values.
|
|
190
|
+
a = list(a)
|
|
191
|
+
|
|
192
|
+
args = _init_argument(a, kw, "args", 0) or []
|
|
193
|
+
cwd = _init_argument(a, kw, "cwd", 9)
|
|
194
|
+
|
|
195
|
+
# if args is not a list or tuple (and e.g. some iterator instead),
|
|
196
|
+
# let's not use it at all. There are too many things that can go wrong
|
|
197
|
+
# when trying to collect an iterator into a list and setting that list
|
|
198
|
+
# into `a` again.
|
|
199
|
+
#
|
|
200
|
+
# Also invocations where `args` is not a sequence are not actually
|
|
201
|
+
# legal. They just happen to work under CPython.
|
|
202
|
+
description = None
|
|
203
|
+
|
|
204
|
+
if isinstance(args, (list, tuple)) and len(args) < 100:
|
|
205
|
+
with capture_internal_exceptions():
|
|
206
|
+
description = " ".join(map(str, args))
|
|
207
|
+
|
|
208
|
+
if description is None:
|
|
209
|
+
description = safe_repr(args)
|
|
210
|
+
|
|
211
|
+
env = None
|
|
212
|
+
|
|
213
|
+
with sentry_sdk.start_span(
|
|
214
|
+
op=OP.SUBPROCESS,
|
|
215
|
+
name=description,
|
|
216
|
+
origin="auto.subprocess.stdlib.subprocess",
|
|
217
|
+
) as span:
|
|
218
|
+
for k, v in sentry_sdk.get_current_scope().iter_trace_propagation_headers(
|
|
219
|
+
span=span
|
|
220
|
+
):
|
|
221
|
+
if env is None:
|
|
222
|
+
env = _init_argument(
|
|
223
|
+
a,
|
|
224
|
+
kw,
|
|
225
|
+
"env",
|
|
226
|
+
10,
|
|
227
|
+
lambda x: dict(x if x is not None else os.environ),
|
|
228
|
+
)
|
|
229
|
+
env["SUBPROCESS_" + k.upper().replace("-", "_")] = v
|
|
230
|
+
|
|
231
|
+
if cwd:
|
|
232
|
+
span.set_data("subprocess.cwd", cwd)
|
|
233
|
+
|
|
234
|
+
rv = old_popen_init(self, *a, **kw)
|
|
235
|
+
|
|
236
|
+
span.set_tag("subprocess.pid", self.pid)
|
|
237
|
+
return rv
|
|
238
|
+
|
|
239
|
+
subprocess.Popen.__init__ = sentry_patched_popen_init # type: ignore
|
|
240
|
+
|
|
241
|
+
old_popen_wait = subprocess.Popen.wait
|
|
242
|
+
|
|
243
|
+
@ensure_integration_enabled(StdlibIntegration, old_popen_wait)
|
|
244
|
+
def sentry_patched_popen_wait(self, *a, **kw):
|
|
245
|
+
# type: (subprocess.Popen[Any], *Any, **Any) -> Any
|
|
246
|
+
with sentry_sdk.start_span(
|
|
247
|
+
op=OP.SUBPROCESS_WAIT,
|
|
248
|
+
origin="auto.subprocess.stdlib.subprocess",
|
|
249
|
+
) as span:
|
|
250
|
+
span.set_tag("subprocess.pid", self.pid)
|
|
251
|
+
return old_popen_wait(self, *a, **kw)
|
|
252
|
+
|
|
253
|
+
subprocess.Popen.wait = sentry_patched_popen_wait # type: ignore
|
|
254
|
+
|
|
255
|
+
old_popen_communicate = subprocess.Popen.communicate
|
|
256
|
+
|
|
257
|
+
@ensure_integration_enabled(StdlibIntegration, old_popen_communicate)
|
|
258
|
+
def sentry_patched_popen_communicate(self, *a, **kw):
|
|
259
|
+
# type: (subprocess.Popen[Any], *Any, **Any) -> Any
|
|
260
|
+
with sentry_sdk.start_span(
|
|
261
|
+
op=OP.SUBPROCESS_COMMUNICATE,
|
|
262
|
+
origin="auto.subprocess.stdlib.subprocess",
|
|
263
|
+
) as span:
|
|
264
|
+
span.set_tag("subprocess.pid", self.pid)
|
|
265
|
+
return old_popen_communicate(self, *a, **kw)
|
|
266
|
+
|
|
267
|
+
subprocess.Popen.communicate = sentry_patched_popen_communicate # type: ignore
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def get_subprocess_traceparent_headers():
|
|
271
|
+
# type: () -> EnvironHeaders
|
|
272
|
+
return EnvironHeaders(os.environ, prefix="SUBPROCESS_")
|