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,176 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import types
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
import sentry_sdk
|
|
6
|
+
from sentry_sdk.integrations import Integration
|
|
7
|
+
from sentry_sdk.integrations.logging import ignore_logger
|
|
8
|
+
from sentry_sdk.utils import (
|
|
9
|
+
capture_internal_exceptions,
|
|
10
|
+
ensure_integration_enabled,
|
|
11
|
+
event_from_exception,
|
|
12
|
+
reraise,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from typing import Any
|
|
19
|
+
from typing import Iterator
|
|
20
|
+
from typing import TypeVar
|
|
21
|
+
from typing import Callable
|
|
22
|
+
|
|
23
|
+
from sentry_sdk._types import ExcInfo
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
WRAPPED_FUNC = "_wrapped_{}_"
|
|
30
|
+
INSPECT_FUNC = "_inspect_{}" # Required format per apache_beam/transforms/core.py
|
|
31
|
+
USED_FUNC = "_sentry_used_"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BeamIntegration(Integration):
|
|
35
|
+
identifier = "beam"
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def setup_once():
|
|
39
|
+
# type: () -> None
|
|
40
|
+
from apache_beam.transforms.core import DoFn, ParDo # type: ignore
|
|
41
|
+
|
|
42
|
+
ignore_logger("root")
|
|
43
|
+
ignore_logger("bundle_processor.create")
|
|
44
|
+
|
|
45
|
+
function_patches = ["process", "start_bundle", "finish_bundle", "setup"]
|
|
46
|
+
for func_name in function_patches:
|
|
47
|
+
setattr(
|
|
48
|
+
DoFn,
|
|
49
|
+
INSPECT_FUNC.format(func_name),
|
|
50
|
+
_wrap_inspect_call(DoFn, func_name),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
old_init = ParDo.__init__
|
|
54
|
+
|
|
55
|
+
def sentry_init_pardo(self, fn, *args, **kwargs):
|
|
56
|
+
# type: (ParDo, Any, *Any, **Any) -> Any
|
|
57
|
+
# Do not monkey patch init twice
|
|
58
|
+
if not getattr(self, "_sentry_is_patched", False):
|
|
59
|
+
for func_name in function_patches:
|
|
60
|
+
if not hasattr(fn, func_name):
|
|
61
|
+
continue
|
|
62
|
+
wrapped_func = WRAPPED_FUNC.format(func_name)
|
|
63
|
+
|
|
64
|
+
# Check to see if inspect is set and process is not
|
|
65
|
+
# to avoid monkey patching process twice.
|
|
66
|
+
# Check to see if function is part of object for
|
|
67
|
+
# backwards compatibility.
|
|
68
|
+
process_func = getattr(fn, func_name)
|
|
69
|
+
inspect_func = getattr(fn, INSPECT_FUNC.format(func_name))
|
|
70
|
+
if not getattr(inspect_func, USED_FUNC, False) and not getattr(
|
|
71
|
+
process_func, USED_FUNC, False
|
|
72
|
+
):
|
|
73
|
+
setattr(fn, wrapped_func, process_func)
|
|
74
|
+
setattr(fn, func_name, _wrap_task_call(process_func))
|
|
75
|
+
|
|
76
|
+
self._sentry_is_patched = True
|
|
77
|
+
old_init(self, fn, *args, **kwargs)
|
|
78
|
+
|
|
79
|
+
ParDo.__init__ = sentry_init_pardo
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _wrap_inspect_call(cls, func_name):
|
|
83
|
+
# type: (Any, Any) -> Any
|
|
84
|
+
|
|
85
|
+
if not hasattr(cls, func_name):
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def _inspect(self):
|
|
89
|
+
# type: (Any) -> Any
|
|
90
|
+
"""
|
|
91
|
+
Inspect function overrides the way Beam gets argspec.
|
|
92
|
+
"""
|
|
93
|
+
wrapped_func = WRAPPED_FUNC.format(func_name)
|
|
94
|
+
if hasattr(self, wrapped_func):
|
|
95
|
+
process_func = getattr(self, wrapped_func)
|
|
96
|
+
else:
|
|
97
|
+
process_func = getattr(self, func_name)
|
|
98
|
+
setattr(self, func_name, _wrap_task_call(process_func))
|
|
99
|
+
setattr(self, wrapped_func, process_func)
|
|
100
|
+
|
|
101
|
+
# getfullargspec is deprecated in more recent beam versions and get_function_args_defaults
|
|
102
|
+
# (which uses Signatures internally) should be used instead.
|
|
103
|
+
try:
|
|
104
|
+
from apache_beam.transforms.core import get_function_args_defaults
|
|
105
|
+
|
|
106
|
+
return get_function_args_defaults(process_func)
|
|
107
|
+
except ImportError:
|
|
108
|
+
from apache_beam.typehints.decorators import getfullargspec # type: ignore
|
|
109
|
+
|
|
110
|
+
return getfullargspec(process_func)
|
|
111
|
+
|
|
112
|
+
setattr(_inspect, USED_FUNC, True)
|
|
113
|
+
return _inspect
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _wrap_task_call(func):
|
|
117
|
+
# type: (F) -> F
|
|
118
|
+
"""
|
|
119
|
+
Wrap task call with a try catch to get exceptions.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
@wraps(func)
|
|
123
|
+
def _inner(*args, **kwargs):
|
|
124
|
+
# type: (*Any, **Any) -> Any
|
|
125
|
+
try:
|
|
126
|
+
gen = func(*args, **kwargs)
|
|
127
|
+
except Exception:
|
|
128
|
+
raise_exception()
|
|
129
|
+
|
|
130
|
+
if not isinstance(gen, types.GeneratorType):
|
|
131
|
+
return gen
|
|
132
|
+
return _wrap_generator_call(gen)
|
|
133
|
+
|
|
134
|
+
setattr(_inner, USED_FUNC, True)
|
|
135
|
+
return _inner # type: ignore
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@ensure_integration_enabled(BeamIntegration)
|
|
139
|
+
def _capture_exception(exc_info):
|
|
140
|
+
# type: (ExcInfo) -> None
|
|
141
|
+
"""
|
|
142
|
+
Send Beam exception to Sentry.
|
|
143
|
+
"""
|
|
144
|
+
client = sentry_sdk.get_client()
|
|
145
|
+
|
|
146
|
+
event, hint = event_from_exception(
|
|
147
|
+
exc_info,
|
|
148
|
+
client_options=client.options,
|
|
149
|
+
mechanism={"type": "beam", "handled": False},
|
|
150
|
+
)
|
|
151
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def raise_exception():
|
|
155
|
+
# type: () -> None
|
|
156
|
+
"""
|
|
157
|
+
Raise an exception.
|
|
158
|
+
"""
|
|
159
|
+
exc_info = sys.exc_info()
|
|
160
|
+
with capture_internal_exceptions():
|
|
161
|
+
_capture_exception(exc_info)
|
|
162
|
+
reraise(*exc_info)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _wrap_generator_call(gen):
|
|
166
|
+
# type: (Iterator[T]) -> Iterator[T]
|
|
167
|
+
"""
|
|
168
|
+
Wrap the generator to handle any failures.
|
|
169
|
+
"""
|
|
170
|
+
while True:
|
|
171
|
+
try:
|
|
172
|
+
yield next(gen)
|
|
173
|
+
except StopIteration:
|
|
174
|
+
break
|
|
175
|
+
except Exception:
|
|
176
|
+
raise_exception()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.consts import OP, SPANDATA
|
|
5
|
+
from sentry_sdk.integrations import _check_minimum_version, Integration, DidNotEnable
|
|
6
|
+
from sentry_sdk.tracing import Span
|
|
7
|
+
from sentry_sdk.utils import (
|
|
8
|
+
capture_internal_exceptions,
|
|
9
|
+
ensure_integration_enabled,
|
|
10
|
+
parse_url,
|
|
11
|
+
parse_version,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from typing import Any
|
|
18
|
+
from typing import Dict
|
|
19
|
+
from typing import Optional
|
|
20
|
+
from typing import Type
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from botocore import __version__ as BOTOCORE_VERSION # type: ignore
|
|
24
|
+
from botocore.client import BaseClient # type: ignore
|
|
25
|
+
from botocore.response import StreamingBody # type: ignore
|
|
26
|
+
from botocore.awsrequest import AWSRequest # type: ignore
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise DidNotEnable("botocore is not installed")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Boto3Integration(Integration):
|
|
32
|
+
identifier = "boto3"
|
|
33
|
+
origin = f"auto.http.{identifier}"
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def setup_once():
|
|
37
|
+
# type: () -> None
|
|
38
|
+
version = parse_version(BOTOCORE_VERSION)
|
|
39
|
+
_check_minimum_version(Boto3Integration, version, "botocore")
|
|
40
|
+
|
|
41
|
+
orig_init = BaseClient.__init__
|
|
42
|
+
|
|
43
|
+
def sentry_patched_init(self, *args, **kwargs):
|
|
44
|
+
# type: (Type[BaseClient], *Any, **Any) -> None
|
|
45
|
+
orig_init(self, *args, **kwargs)
|
|
46
|
+
meta = self.meta
|
|
47
|
+
service_id = meta.service_model.service_id.hyphenize()
|
|
48
|
+
meta.events.register(
|
|
49
|
+
"request-created",
|
|
50
|
+
partial(_sentry_request_created, service_id=service_id),
|
|
51
|
+
)
|
|
52
|
+
meta.events.register("after-call", _sentry_after_call)
|
|
53
|
+
meta.events.register("after-call-error", _sentry_after_call_error)
|
|
54
|
+
|
|
55
|
+
BaseClient.__init__ = sentry_patched_init
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@ensure_integration_enabled(Boto3Integration)
|
|
59
|
+
def _sentry_request_created(service_id, request, operation_name, **kwargs):
|
|
60
|
+
# type: (str, AWSRequest, str, **Any) -> None
|
|
61
|
+
description = "aws.%s.%s" % (service_id, operation_name)
|
|
62
|
+
span = sentry_sdk.start_span(
|
|
63
|
+
op=OP.HTTP_CLIENT,
|
|
64
|
+
name=description,
|
|
65
|
+
origin=Boto3Integration.origin,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
with capture_internal_exceptions():
|
|
69
|
+
parsed_url = parse_url(request.url, sanitize=False)
|
|
70
|
+
span.set_data("aws.request.url", parsed_url.url)
|
|
71
|
+
span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
|
|
72
|
+
span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
|
|
73
|
+
|
|
74
|
+
span.set_tag("aws.service_id", service_id)
|
|
75
|
+
span.set_tag("aws.operation_name", operation_name)
|
|
76
|
+
span.set_data(SPANDATA.HTTP_METHOD, request.method)
|
|
77
|
+
|
|
78
|
+
# We do it in order for subsequent http calls/retries be
|
|
79
|
+
# attached to this span.
|
|
80
|
+
span.__enter__()
|
|
81
|
+
|
|
82
|
+
# request.context is an open-ended data-structure
|
|
83
|
+
# where we can add anything useful in request life cycle.
|
|
84
|
+
request.context["_sentrysdk_span"] = span
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _sentry_after_call(context, parsed, **kwargs):
|
|
88
|
+
# type: (Dict[str, Any], Dict[str, Any], **Any) -> None
|
|
89
|
+
span = context.pop("_sentrysdk_span", None) # type: Optional[Span]
|
|
90
|
+
|
|
91
|
+
# Span could be absent if the integration is disabled.
|
|
92
|
+
if span is None:
|
|
93
|
+
return
|
|
94
|
+
span.__exit__(None, None, None)
|
|
95
|
+
|
|
96
|
+
body = parsed.get("Body")
|
|
97
|
+
if not isinstance(body, StreamingBody):
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
streaming_span = span.start_child(
|
|
101
|
+
op=OP.HTTP_CLIENT_STREAM,
|
|
102
|
+
name=span.description,
|
|
103
|
+
origin=Boto3Integration.origin,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
orig_read = body.read
|
|
107
|
+
orig_close = body.close
|
|
108
|
+
|
|
109
|
+
def sentry_streaming_body_read(*args, **kwargs):
|
|
110
|
+
# type: (*Any, **Any) -> bytes
|
|
111
|
+
try:
|
|
112
|
+
ret = orig_read(*args, **kwargs)
|
|
113
|
+
if not ret:
|
|
114
|
+
streaming_span.finish()
|
|
115
|
+
return ret
|
|
116
|
+
except Exception:
|
|
117
|
+
streaming_span.finish()
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
body.read = sentry_streaming_body_read
|
|
121
|
+
|
|
122
|
+
def sentry_streaming_body_close(*args, **kwargs):
|
|
123
|
+
# type: (*Any, **Any) -> None
|
|
124
|
+
streaming_span.finish()
|
|
125
|
+
orig_close(*args, **kwargs)
|
|
126
|
+
|
|
127
|
+
body.close = sentry_streaming_body_close
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _sentry_after_call_error(context, exception, **kwargs):
|
|
131
|
+
# type: (Dict[str, Any], Type[BaseException], **Any) -> None
|
|
132
|
+
span = context.pop("_sentrysdk_span", None) # type: Optional[Span]
|
|
133
|
+
|
|
134
|
+
# Span could be absent if the integration is disabled.
|
|
135
|
+
if span is None:
|
|
136
|
+
return
|
|
137
|
+
span.__exit__(type(exception), exception, None)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
from sentry_sdk.tracing import SOURCE_FOR_STYLE
|
|
5
|
+
from sentry_sdk.utils import (
|
|
6
|
+
capture_internal_exceptions,
|
|
7
|
+
ensure_integration_enabled,
|
|
8
|
+
event_from_exception,
|
|
9
|
+
parse_version,
|
|
10
|
+
transaction_from_function,
|
|
11
|
+
)
|
|
12
|
+
from sentry_sdk.integrations import (
|
|
13
|
+
Integration,
|
|
14
|
+
DidNotEnable,
|
|
15
|
+
_DEFAULT_FAILED_REQUEST_STATUS_CODES,
|
|
16
|
+
_check_minimum_version,
|
|
17
|
+
)
|
|
18
|
+
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
|
19
|
+
from sentry_sdk.integrations._wsgi_common import RequestExtractor
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Set
|
|
25
|
+
|
|
26
|
+
from sentry_sdk.integrations.wsgi import _ScopedResponse
|
|
27
|
+
from typing import Any
|
|
28
|
+
from typing import Dict
|
|
29
|
+
from typing import Callable
|
|
30
|
+
from typing import Optional
|
|
31
|
+
from bottle import FileUpload, FormsDict, LocalRequest # type: ignore
|
|
32
|
+
|
|
33
|
+
from sentry_sdk._types import EventProcessor, Event
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from bottle import (
|
|
37
|
+
Bottle,
|
|
38
|
+
HTTPResponse,
|
|
39
|
+
Route,
|
|
40
|
+
request as bottle_request,
|
|
41
|
+
__version__ as BOTTLE_VERSION,
|
|
42
|
+
)
|
|
43
|
+
except ImportError:
|
|
44
|
+
raise DidNotEnable("Bottle not installed")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
TRANSACTION_STYLE_VALUES = ("endpoint", "url")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BottleIntegration(Integration):
|
|
51
|
+
identifier = "bottle"
|
|
52
|
+
origin = f"auto.http.{identifier}"
|
|
53
|
+
|
|
54
|
+
transaction_style = ""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
transaction_style="endpoint", # type: str
|
|
59
|
+
*,
|
|
60
|
+
failed_request_status_codes=_DEFAULT_FAILED_REQUEST_STATUS_CODES, # type: Set[int]
|
|
61
|
+
):
|
|
62
|
+
# type: (...) -> None
|
|
63
|
+
|
|
64
|
+
if transaction_style not in TRANSACTION_STYLE_VALUES:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
"Invalid value for transaction_style: %s (must be in %s)"
|
|
67
|
+
% (transaction_style, TRANSACTION_STYLE_VALUES)
|
|
68
|
+
)
|
|
69
|
+
self.transaction_style = transaction_style
|
|
70
|
+
self.failed_request_status_codes = failed_request_status_codes
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def setup_once():
|
|
74
|
+
# type: () -> None
|
|
75
|
+
version = parse_version(BOTTLE_VERSION)
|
|
76
|
+
_check_minimum_version(BottleIntegration, version)
|
|
77
|
+
|
|
78
|
+
old_app = Bottle.__call__
|
|
79
|
+
|
|
80
|
+
@ensure_integration_enabled(BottleIntegration, old_app)
|
|
81
|
+
def sentry_patched_wsgi_app(self, environ, start_response):
|
|
82
|
+
# type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
|
|
83
|
+
middleware = SentryWsgiMiddleware(
|
|
84
|
+
lambda *a, **kw: old_app(self, *a, **kw),
|
|
85
|
+
span_origin=BottleIntegration.origin,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return middleware(environ, start_response)
|
|
89
|
+
|
|
90
|
+
Bottle.__call__ = sentry_patched_wsgi_app
|
|
91
|
+
|
|
92
|
+
old_handle = Bottle._handle
|
|
93
|
+
|
|
94
|
+
@functools.wraps(old_handle)
|
|
95
|
+
def _patched_handle(self, environ):
|
|
96
|
+
# type: (Bottle, Dict[str, Any]) -> Any
|
|
97
|
+
integration = sentry_sdk.get_client().get_integration(BottleIntegration)
|
|
98
|
+
if integration is None:
|
|
99
|
+
return old_handle(self, environ)
|
|
100
|
+
|
|
101
|
+
scope = sentry_sdk.get_isolation_scope()
|
|
102
|
+
scope._name = "bottle"
|
|
103
|
+
scope.add_event_processor(
|
|
104
|
+
_make_request_event_processor(self, bottle_request, integration)
|
|
105
|
+
)
|
|
106
|
+
res = old_handle(self, environ)
|
|
107
|
+
|
|
108
|
+
return res
|
|
109
|
+
|
|
110
|
+
Bottle._handle = _patched_handle
|
|
111
|
+
|
|
112
|
+
old_make_callback = Route._make_callback
|
|
113
|
+
|
|
114
|
+
@functools.wraps(old_make_callback)
|
|
115
|
+
def patched_make_callback(self, *args, **kwargs):
|
|
116
|
+
# type: (Route, *object, **object) -> Any
|
|
117
|
+
prepared_callback = old_make_callback(self, *args, **kwargs)
|
|
118
|
+
|
|
119
|
+
integration = sentry_sdk.get_client().get_integration(BottleIntegration)
|
|
120
|
+
if integration is None:
|
|
121
|
+
return prepared_callback
|
|
122
|
+
|
|
123
|
+
def wrapped_callback(*args, **kwargs):
|
|
124
|
+
# type: (*object, **object) -> Any
|
|
125
|
+
try:
|
|
126
|
+
res = prepared_callback(*args, **kwargs)
|
|
127
|
+
except Exception as exception:
|
|
128
|
+
_capture_exception(exception, handled=False)
|
|
129
|
+
raise exception
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
isinstance(res, HTTPResponse)
|
|
133
|
+
and res.status_code in integration.failed_request_status_codes
|
|
134
|
+
):
|
|
135
|
+
_capture_exception(res, handled=True)
|
|
136
|
+
|
|
137
|
+
return res
|
|
138
|
+
|
|
139
|
+
return wrapped_callback
|
|
140
|
+
|
|
141
|
+
Route._make_callback = patched_make_callback
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class BottleRequestExtractor(RequestExtractor):
|
|
145
|
+
def env(self):
|
|
146
|
+
# type: () -> Dict[str, str]
|
|
147
|
+
return self.request.environ
|
|
148
|
+
|
|
149
|
+
def cookies(self):
|
|
150
|
+
# type: () -> Dict[str, str]
|
|
151
|
+
return self.request.cookies
|
|
152
|
+
|
|
153
|
+
def raw_data(self):
|
|
154
|
+
# type: () -> bytes
|
|
155
|
+
return self.request.body.read()
|
|
156
|
+
|
|
157
|
+
def form(self):
|
|
158
|
+
# type: () -> FormsDict
|
|
159
|
+
if self.is_json():
|
|
160
|
+
return None
|
|
161
|
+
return self.request.forms.decode()
|
|
162
|
+
|
|
163
|
+
def files(self):
|
|
164
|
+
# type: () -> Optional[Dict[str, str]]
|
|
165
|
+
if self.is_json():
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
return self.request.files
|
|
169
|
+
|
|
170
|
+
def size_of_file(self, file):
|
|
171
|
+
# type: (FileUpload) -> int
|
|
172
|
+
return file.content_length
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _set_transaction_name_and_source(event, transaction_style, request):
|
|
176
|
+
# type: (Event, str, Any) -> None
|
|
177
|
+
name = ""
|
|
178
|
+
|
|
179
|
+
if transaction_style == "url":
|
|
180
|
+
try:
|
|
181
|
+
name = request.route.rule or ""
|
|
182
|
+
except RuntimeError:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
elif transaction_style == "endpoint":
|
|
186
|
+
try:
|
|
187
|
+
name = (
|
|
188
|
+
request.route.name
|
|
189
|
+
or transaction_from_function(request.route.callback)
|
|
190
|
+
or ""
|
|
191
|
+
)
|
|
192
|
+
except RuntimeError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
event["transaction"] = name
|
|
196
|
+
event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _make_request_event_processor(app, request, integration):
|
|
200
|
+
# type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
|
|
201
|
+
|
|
202
|
+
def event_processor(event, hint):
|
|
203
|
+
# type: (Event, dict[str, Any]) -> Event
|
|
204
|
+
_set_transaction_name_and_source(event, integration.transaction_style, request)
|
|
205
|
+
|
|
206
|
+
with capture_internal_exceptions():
|
|
207
|
+
BottleRequestExtractor(request).extract_into_event(event)
|
|
208
|
+
|
|
209
|
+
return event
|
|
210
|
+
|
|
211
|
+
return event_processor
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _capture_exception(exception, handled):
|
|
215
|
+
# type: (BaseException, bool) -> None
|
|
216
|
+
event, hint = event_from_exception(
|
|
217
|
+
exception,
|
|
218
|
+
client_options=sentry_sdk.get_client().options,
|
|
219
|
+
mechanism={"type": "bottle", "handled": handled},
|
|
220
|
+
)
|
|
221
|
+
sentry_sdk.capture_event(event, hint=hint)
|