sentry-sdk 0.18.0__py2.py3-none-any.whl → 2.46.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sentry_sdk/__init__.py +48 -6
- sentry_sdk/_compat.py +64 -56
- sentry_sdk/_init_implementation.py +84 -0
- sentry_sdk/_log_batcher.py +172 -0
- sentry_sdk/_lru_cache.py +47 -0
- sentry_sdk/_metrics_batcher.py +167 -0
- sentry_sdk/_queue.py +81 -19
- sentry_sdk/_types.py +311 -11
- sentry_sdk/_werkzeug.py +98 -0
- sentry_sdk/ai/__init__.py +7 -0
- sentry_sdk/ai/monitoring.py +137 -0
- sentry_sdk/ai/utils.py +144 -0
- sentry_sdk/api.py +409 -67
- sentry_sdk/attachments.py +75 -0
- sentry_sdk/client.py +849 -103
- sentry_sdk/consts.py +1389 -34
- sentry_sdk/crons/__init__.py +10 -0
- sentry_sdk/crons/api.py +62 -0
- sentry_sdk/crons/consts.py +4 -0
- sentry_sdk/crons/decorator.py +135 -0
- sentry_sdk/debug.py +12 -15
- sentry_sdk/envelope.py +112 -61
- sentry_sdk/feature_flags.py +71 -0
- sentry_sdk/hub.py +442 -386
- sentry_sdk/integrations/__init__.py +228 -58
- sentry_sdk/integrations/_asgi_common.py +108 -0
- sentry_sdk/integrations/_wsgi_common.py +131 -40
- sentry_sdk/integrations/aiohttp.py +221 -72
- sentry_sdk/integrations/anthropic.py +439 -0
- sentry_sdk/integrations/argv.py +4 -6
- sentry_sdk/integrations/ariadne.py +161 -0
- sentry_sdk/integrations/arq.py +247 -0
- sentry_sdk/integrations/asgi.py +237 -135
- sentry_sdk/integrations/asyncio.py +144 -0
- sentry_sdk/integrations/asyncpg.py +208 -0
- sentry_sdk/integrations/atexit.py +13 -18
- sentry_sdk/integrations/aws_lambda.py +233 -80
- sentry_sdk/integrations/beam.py +27 -35
- sentry_sdk/integrations/boto3.py +137 -0
- sentry_sdk/integrations/bottle.py +91 -69
- sentry_sdk/integrations/celery/__init__.py +529 -0
- sentry_sdk/integrations/celery/beat.py +293 -0
- sentry_sdk/integrations/celery/utils.py +43 -0
- sentry_sdk/integrations/chalice.py +35 -28
- sentry_sdk/integrations/clickhouse_driver.py +177 -0
- sentry_sdk/integrations/cloud_resource_context.py +280 -0
- sentry_sdk/integrations/cohere.py +274 -0
- sentry_sdk/integrations/dedupe.py +32 -8
- sentry_sdk/integrations/django/__init__.py +343 -89
- sentry_sdk/integrations/django/asgi.py +201 -22
- sentry_sdk/integrations/django/caching.py +204 -0
- sentry_sdk/integrations/django/middleware.py +80 -32
- sentry_sdk/integrations/django/signals_handlers.py +91 -0
- sentry_sdk/integrations/django/templates.py +69 -2
- sentry_sdk/integrations/django/transactions.py +39 -14
- sentry_sdk/integrations/django/views.py +69 -16
- sentry_sdk/integrations/dramatiq.py +226 -0
- sentry_sdk/integrations/excepthook.py +19 -13
- sentry_sdk/integrations/executing.py +5 -6
- sentry_sdk/integrations/falcon.py +128 -65
- sentry_sdk/integrations/fastapi.py +141 -0
- sentry_sdk/integrations/flask.py +114 -75
- sentry_sdk/integrations/gcp.py +67 -36
- sentry_sdk/integrations/gnu_backtrace.py +14 -22
- sentry_sdk/integrations/google_genai/__init__.py +301 -0
- sentry_sdk/integrations/google_genai/consts.py +16 -0
- sentry_sdk/integrations/google_genai/streaming.py +155 -0
- sentry_sdk/integrations/google_genai/utils.py +576 -0
- sentry_sdk/integrations/gql.py +162 -0
- sentry_sdk/integrations/graphene.py +151 -0
- sentry_sdk/integrations/grpc/__init__.py +168 -0
- sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
- sentry_sdk/integrations/grpc/aio/client.py +95 -0
- sentry_sdk/integrations/grpc/aio/server.py +100 -0
- sentry_sdk/integrations/grpc/client.py +91 -0
- sentry_sdk/integrations/grpc/consts.py +1 -0
- sentry_sdk/integrations/grpc/server.py +66 -0
- sentry_sdk/integrations/httpx.py +178 -0
- sentry_sdk/integrations/huey.py +174 -0
- sentry_sdk/integrations/huggingface_hub.py +378 -0
- sentry_sdk/integrations/langchain.py +1132 -0
- sentry_sdk/integrations/langgraph.py +337 -0
- sentry_sdk/integrations/launchdarkly.py +61 -0
- sentry_sdk/integrations/litellm.py +287 -0
- sentry_sdk/integrations/litestar.py +315 -0
- sentry_sdk/integrations/logging.py +261 -85
- sentry_sdk/integrations/loguru.py +213 -0
- sentry_sdk/integrations/mcp.py +566 -0
- sentry_sdk/integrations/modules.py +6 -33
- sentry_sdk/integrations/openai.py +725 -0
- sentry_sdk/integrations/openai_agents/__init__.py +61 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
- sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
- sentry_sdk/integrations/openai_agents/utils.py +199 -0
- sentry_sdk/integrations/openfeature.py +35 -0
- sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
- sentry_sdk/integrations/opentelemetry/consts.py +5 -0
- sentry_sdk/integrations/opentelemetry/integration.py +58 -0
- sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
- sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
- sentry_sdk/integrations/otlp.py +82 -0
- sentry_sdk/integrations/pure_eval.py +20 -11
- sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
- sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
- sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
- sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
- sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
- sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
- sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
- sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
- sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
- sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
- sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
- sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
- sentry_sdk/integrations/pymongo.py +214 -0
- sentry_sdk/integrations/pyramid.py +71 -60
- sentry_sdk/integrations/quart.py +237 -0
- sentry_sdk/integrations/ray.py +165 -0
- sentry_sdk/integrations/redis/__init__.py +48 -0
- sentry_sdk/integrations/redis/_async_common.py +116 -0
- sentry_sdk/integrations/redis/_sync_common.py +119 -0
- sentry_sdk/integrations/redis/consts.py +19 -0
- sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- sentry_sdk/integrations/redis/modules/caches.py +118 -0
- sentry_sdk/integrations/redis/modules/queries.py +65 -0
- sentry_sdk/integrations/redis/rb.py +32 -0
- sentry_sdk/integrations/redis/redis.py +69 -0
- sentry_sdk/integrations/redis/redis_cluster.py +107 -0
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
- sentry_sdk/integrations/redis/utils.py +148 -0
- sentry_sdk/integrations/rq.py +62 -52
- sentry_sdk/integrations/rust_tracing.py +284 -0
- sentry_sdk/integrations/sanic.py +248 -114
- sentry_sdk/integrations/serverless.py +13 -22
- sentry_sdk/integrations/socket.py +96 -0
- sentry_sdk/integrations/spark/spark_driver.py +115 -62
- sentry_sdk/integrations/spark/spark_worker.py +42 -50
- sentry_sdk/integrations/sqlalchemy.py +82 -37
- sentry_sdk/integrations/starlette.py +737 -0
- sentry_sdk/integrations/starlite.py +292 -0
- sentry_sdk/integrations/statsig.py +37 -0
- sentry_sdk/integrations/stdlib.py +100 -58
- sentry_sdk/integrations/strawberry.py +394 -0
- sentry_sdk/integrations/sys_exit.py +70 -0
- sentry_sdk/integrations/threading.py +142 -38
- sentry_sdk/integrations/tornado.py +68 -53
- sentry_sdk/integrations/trytond.py +15 -20
- sentry_sdk/integrations/typer.py +60 -0
- sentry_sdk/integrations/unleash.py +33 -0
- sentry_sdk/integrations/unraisablehook.py +53 -0
- sentry_sdk/integrations/wsgi.py +126 -125
- sentry_sdk/logger.py +96 -0
- sentry_sdk/metrics.py +81 -0
- sentry_sdk/monitor.py +120 -0
- sentry_sdk/profiler/__init__.py +49 -0
- sentry_sdk/profiler/continuous_profiler.py +730 -0
- sentry_sdk/profiler/transaction_profiler.py +839 -0
- sentry_sdk/profiler/utils.py +195 -0
- sentry_sdk/scope.py +1542 -112
- sentry_sdk/scrubber.py +177 -0
- sentry_sdk/serializer.py +152 -210
- sentry_sdk/session.py +177 -0
- sentry_sdk/sessions.py +202 -179
- sentry_sdk/spotlight.py +242 -0
- sentry_sdk/tracing.py +1202 -294
- sentry_sdk/tracing_utils.py +1236 -0
- sentry_sdk/transport.py +693 -189
- sentry_sdk/types.py +52 -0
- sentry_sdk/utils.py +1395 -228
- sentry_sdk/worker.py +30 -17
- sentry_sdk-2.46.0.dist-info/METADATA +268 -0
- sentry_sdk-2.46.0.dist-info/RECORD +189 -0
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
- sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
- sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
- sentry_sdk/_functools.py +0 -66
- sentry_sdk/integrations/celery.py +0 -275
- sentry_sdk/integrations/redis.py +0 -103
- sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
- sentry_sdk-0.18.0.dist-info/METADATA +0 -66
- sentry_sdk-0.18.0.dist-info/RECORD +0 -65
- {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
3
4
|
import sys
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from os import environ
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
from sentry_sdk.
|
|
7
|
-
from sentry_sdk.
|
|
9
|
+
import sentry_sdk
|
|
10
|
+
from sentry_sdk.api import continue_trace
|
|
11
|
+
from sentry_sdk.consts import OP
|
|
12
|
+
from sentry_sdk.scope import should_send_default_pii
|
|
13
|
+
from sentry_sdk.tracing import TransactionSource
|
|
8
14
|
from sentry_sdk.utils import (
|
|
9
15
|
AnnotatedValue,
|
|
10
16
|
capture_internal_exceptions,
|
|
17
|
+
ensure_integration_enabled,
|
|
11
18
|
event_from_exception,
|
|
12
19
|
logger,
|
|
13
20
|
TimeoutThread,
|
|
21
|
+
reraise,
|
|
14
22
|
)
|
|
15
23
|
from sentry_sdk.integrations import Integration
|
|
16
24
|
from sentry_sdk.integrations._wsgi_common import _filter_headers
|
|
17
25
|
|
|
18
|
-
from
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
19
27
|
|
|
20
|
-
if
|
|
28
|
+
if TYPE_CHECKING:
|
|
21
29
|
from typing import Any
|
|
22
30
|
from typing import TypeVar
|
|
23
31
|
from typing import Callable
|
|
@@ -34,29 +42,30 @@ MILLIS_TO_SECONDS = 1000.0
|
|
|
34
42
|
|
|
35
43
|
def _wrap_init_error(init_error):
|
|
36
44
|
# type: (F) -> F
|
|
45
|
+
@ensure_integration_enabled(AwsLambdaIntegration, init_error)
|
|
37
46
|
def sentry_init_error(*args, **kwargs):
|
|
38
47
|
# type: (*Any, **Any) -> Any
|
|
39
|
-
|
|
40
|
-
hub = Hub.current
|
|
41
|
-
integration = hub.get_integration(AwsLambdaIntegration)
|
|
42
|
-
if integration is None:
|
|
43
|
-
return init_error(*args, **kwargs)
|
|
44
|
-
|
|
45
|
-
# If an integration is there, a client has to be there.
|
|
46
|
-
client = hub.client # type: Any
|
|
48
|
+
client = sentry_sdk.get_client()
|
|
47
49
|
|
|
48
50
|
with capture_internal_exceptions():
|
|
49
|
-
|
|
50
|
-
scope.clear_breadcrumbs()
|
|
51
|
+
sentry_sdk.get_isolation_scope().clear_breadcrumbs()
|
|
51
52
|
|
|
52
53
|
exc_info = sys.exc_info()
|
|
53
54
|
if exc_info and all(exc_info):
|
|
54
|
-
|
|
55
|
+
sentry_event, hint = event_from_exception(
|
|
55
56
|
exc_info,
|
|
56
57
|
client_options=client.options,
|
|
57
58
|
mechanism={"type": "aws_lambda", "handled": False},
|
|
58
59
|
)
|
|
59
|
-
|
|
60
|
+
sentry_sdk.capture_event(sentry_event, hint=hint)
|
|
61
|
+
|
|
62
|
+
else:
|
|
63
|
+
# Fall back to AWS lambdas JSON representation of the error
|
|
64
|
+
error_info = args[1]
|
|
65
|
+
if isinstance(error_info, str):
|
|
66
|
+
error_info = json.loads(error_info)
|
|
67
|
+
sentry_event = _event_from_error_json(error_info)
|
|
68
|
+
sentry_sdk.capture_event(sentry_event)
|
|
60
69
|
|
|
61
70
|
return init_error(*args, **kwargs)
|
|
62
71
|
|
|
@@ -65,24 +74,57 @@ def _wrap_init_error(init_error):
|
|
|
65
74
|
|
|
66
75
|
def _wrap_handler(handler):
|
|
67
76
|
# type: (F) -> F
|
|
68
|
-
|
|
77
|
+
@functools.wraps(handler)
|
|
78
|
+
def sentry_handler(aws_event, aws_context, *args, **kwargs):
|
|
69
79
|
# type: (Any, Any, *Any, **Any) -> Any
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
|
|
81
|
+
# Per https://docs.aws.amazon.com/lambda/latest/dg/python-handler.html,
|
|
82
|
+
# `event` here is *likely* a dictionary, but also might be a number of
|
|
83
|
+
# other types (str, int, float, None).
|
|
84
|
+
#
|
|
85
|
+
# In some cases, it is a list (if the user is batch-invoking their
|
|
86
|
+
# function, for example), in which case we'll use the first entry as a
|
|
87
|
+
# representative from which to try pulling request data. (Presumably it
|
|
88
|
+
# will be the same for all events in the list, since they're all hitting
|
|
89
|
+
# the lambda in the same request.)
|
|
90
|
+
|
|
91
|
+
client = sentry_sdk.get_client()
|
|
92
|
+
integration = client.get_integration(AwsLambdaIntegration)
|
|
93
|
+
|
|
72
94
|
if integration is None:
|
|
73
|
-
return handler(
|
|
95
|
+
return handler(aws_event, aws_context, *args, **kwargs)
|
|
96
|
+
|
|
97
|
+
if isinstance(aws_event, list) and len(aws_event) >= 1:
|
|
98
|
+
request_data = aws_event[0]
|
|
99
|
+
batch_size = len(aws_event)
|
|
100
|
+
else:
|
|
101
|
+
request_data = aws_event
|
|
102
|
+
batch_size = 1
|
|
103
|
+
|
|
104
|
+
if not isinstance(request_data, dict):
|
|
105
|
+
# If we're not dealing with a dictionary, we won't be able to get
|
|
106
|
+
# headers, path, http method, etc in any case, so it's fine that
|
|
107
|
+
# this is empty
|
|
108
|
+
request_data = {}
|
|
74
109
|
|
|
75
|
-
|
|
76
|
-
client = hub.client # type: Any
|
|
77
|
-
configured_time = context.get_remaining_time_in_millis()
|
|
110
|
+
configured_time = aws_context.get_remaining_time_in_millis()
|
|
78
111
|
|
|
79
|
-
with
|
|
112
|
+
with sentry_sdk.isolation_scope() as scope:
|
|
113
|
+
timeout_thread = None
|
|
80
114
|
with capture_internal_exceptions():
|
|
81
115
|
scope.clear_breadcrumbs()
|
|
82
116
|
scope.add_event_processor(
|
|
83
|
-
_make_request_event_processor(
|
|
117
|
+
_make_request_event_processor(
|
|
118
|
+
request_data, aws_context, configured_time
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
scope.set_tag(
|
|
122
|
+
"aws_region", aws_context.invoked_function_arn.split(":")[3]
|
|
84
123
|
)
|
|
85
|
-
|
|
124
|
+
if batch_size > 1:
|
|
125
|
+
scope.set_tag("batch_request", True)
|
|
126
|
+
scope.set_tag("batch_size", batch_size)
|
|
127
|
+
|
|
86
128
|
# Starting the Timeout thread only if the configured time is greater than Timeout warning
|
|
87
129
|
# buffer and timeout_warning parameter is set True.
|
|
88
130
|
if (
|
|
@@ -94,28 +136,49 @@ def _wrap_handler(handler):
|
|
|
94
136
|
) / MILLIS_TO_SECONDS
|
|
95
137
|
|
|
96
138
|
timeout_thread = TimeoutThread(
|
|
97
|
-
waiting_time,
|
|
139
|
+
waiting_time,
|
|
140
|
+
configured_time / MILLIS_TO_SECONDS,
|
|
141
|
+
isolation_scope=scope,
|
|
142
|
+
current_scope=sentry_sdk.get_current_scope(),
|
|
98
143
|
)
|
|
99
144
|
|
|
100
145
|
# Starting the thread to raise timeout warning exception
|
|
101
146
|
timeout_thread.start()
|
|
102
147
|
|
|
103
|
-
headers =
|
|
104
|
-
|
|
105
|
-
|
|
148
|
+
headers = request_data.get("headers", {})
|
|
149
|
+
# Some AWS Services (ie. EventBridge) set headers as a list
|
|
150
|
+
# or None, so we must ensure it is a dict
|
|
151
|
+
if not isinstance(headers, dict):
|
|
152
|
+
headers = {}
|
|
153
|
+
|
|
154
|
+
transaction = continue_trace(
|
|
155
|
+
headers,
|
|
156
|
+
op=OP.FUNCTION_AWS,
|
|
157
|
+
name=aws_context.function_name,
|
|
158
|
+
source=TransactionSource.COMPONENT,
|
|
159
|
+
origin=AwsLambdaIntegration.origin,
|
|
106
160
|
)
|
|
107
|
-
with
|
|
161
|
+
with sentry_sdk.start_transaction(
|
|
162
|
+
transaction,
|
|
163
|
+
custom_sampling_context={
|
|
164
|
+
"aws_event": aws_event,
|
|
165
|
+
"aws_context": aws_context,
|
|
166
|
+
},
|
|
167
|
+
):
|
|
108
168
|
try:
|
|
109
|
-
return handler(
|
|
169
|
+
return handler(aws_event, aws_context, *args, **kwargs)
|
|
110
170
|
except Exception:
|
|
111
171
|
exc_info = sys.exc_info()
|
|
112
|
-
|
|
172
|
+
sentry_event, hint = event_from_exception(
|
|
113
173
|
exc_info,
|
|
114
174
|
client_options=client.options,
|
|
115
175
|
mechanism={"type": "aws_lambda", "handled": False},
|
|
116
176
|
)
|
|
117
|
-
|
|
177
|
+
sentry_sdk.capture_event(sentry_event, hint=hint)
|
|
118
178
|
reraise(*exc_info)
|
|
179
|
+
finally:
|
|
180
|
+
if timeout_thread:
|
|
181
|
+
timeout_thread.stop()
|
|
119
182
|
|
|
120
183
|
return sentry_handler # type: ignore
|
|
121
184
|
|
|
@@ -123,16 +186,17 @@ def _wrap_handler(handler):
|
|
|
123
186
|
def _drain_queue():
|
|
124
187
|
# type: () -> None
|
|
125
188
|
with capture_internal_exceptions():
|
|
126
|
-
|
|
127
|
-
integration =
|
|
189
|
+
client = sentry_sdk.get_client()
|
|
190
|
+
integration = client.get_integration(AwsLambdaIntegration)
|
|
128
191
|
if integration is not None:
|
|
129
192
|
# Flush out the event queue before AWS kills the
|
|
130
193
|
# process.
|
|
131
|
-
|
|
194
|
+
client.flush()
|
|
132
195
|
|
|
133
196
|
|
|
134
197
|
class AwsLambdaIntegration(Integration):
|
|
135
198
|
identifier = "aws_lambda"
|
|
199
|
+
origin = f"auto.function.{identifier}"
|
|
136
200
|
|
|
137
201
|
def __init__(self, timeout_warning=False):
|
|
138
202
|
# type: (bool) -> None
|
|
@@ -142,23 +206,8 @@ class AwsLambdaIntegration(Integration):
|
|
|
142
206
|
def setup_once():
|
|
143
207
|
# type: () -> None
|
|
144
208
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Python 3.7: If the bootstrap module is *already imported*, it is the
|
|
148
|
-
# one we actually want to use (no idea what's in __main__)
|
|
149
|
-
#
|
|
150
|
-
# On Python 3.8 bootstrap is also importable, but will be the same file
|
|
151
|
-
# as __main__ imported under a different name:
|
|
152
|
-
#
|
|
153
|
-
# sys.modules['__main__'].__file__ == sys.modules['bootstrap'].__file__
|
|
154
|
-
# sys.modules['__main__'] is not sys.modules['bootstrap']
|
|
155
|
-
#
|
|
156
|
-
# Such a setup would then make all monkeypatches useless.
|
|
157
|
-
if "bootstrap" in sys.modules:
|
|
158
|
-
lambda_bootstrap = sys.modules["bootstrap"] # type: Any
|
|
159
|
-
elif "__main__" in sys.modules:
|
|
160
|
-
lambda_bootstrap = sys.modules["__main__"]
|
|
161
|
-
else:
|
|
209
|
+
lambda_bootstrap = get_lambda_bootstrap()
|
|
210
|
+
if not lambda_bootstrap:
|
|
162
211
|
logger.warning(
|
|
163
212
|
"Not running in AWS Lambda environment, "
|
|
164
213
|
"AwsLambdaIntegration disabled (could not find bootstrap module)"
|
|
@@ -172,7 +221,7 @@ class AwsLambdaIntegration(Integration):
|
|
|
172
221
|
)
|
|
173
222
|
return
|
|
174
223
|
|
|
175
|
-
pre_37 = hasattr(lambda_bootstrap, "handle_http_request") # Python 3.6
|
|
224
|
+
pre_37 = hasattr(lambda_bootstrap, "handle_http_request") # Python 3.6
|
|
176
225
|
|
|
177
226
|
if pre_37:
|
|
178
227
|
old_handle_event_request = lambda_bootstrap.handle_event_request
|
|
@@ -245,16 +294,53 @@ class AwsLambdaIntegration(Integration):
|
|
|
245
294
|
)
|
|
246
295
|
|
|
247
296
|
|
|
297
|
+
def get_lambda_bootstrap():
|
|
298
|
+
# type: () -> Optional[Any]
|
|
299
|
+
|
|
300
|
+
# Python 3.7: If the bootstrap module is *already imported*, it is the
|
|
301
|
+
# one we actually want to use (no idea what's in __main__)
|
|
302
|
+
#
|
|
303
|
+
# Python 3.8: bootstrap is also importable, but will be the same file
|
|
304
|
+
# as __main__ imported under a different name:
|
|
305
|
+
#
|
|
306
|
+
# sys.modules['__main__'].__file__ == sys.modules['bootstrap'].__file__
|
|
307
|
+
# sys.modules['__main__'] is not sys.modules['bootstrap']
|
|
308
|
+
#
|
|
309
|
+
# Python 3.9: bootstrap is in __main__.awslambdaricmain
|
|
310
|
+
#
|
|
311
|
+
# On container builds using the `aws-lambda-python-runtime-interface-client`
|
|
312
|
+
# (awslamdaric) module, bootstrap is located in sys.modules['__main__'].bootstrap
|
|
313
|
+
#
|
|
314
|
+
# Such a setup would then make all monkeypatches useless.
|
|
315
|
+
if "bootstrap" in sys.modules:
|
|
316
|
+
return sys.modules["bootstrap"]
|
|
317
|
+
elif "__main__" in sys.modules:
|
|
318
|
+
module = sys.modules["__main__"]
|
|
319
|
+
# python3.9 runtime
|
|
320
|
+
if hasattr(module, "awslambdaricmain") and hasattr(
|
|
321
|
+
module.awslambdaricmain, "bootstrap"
|
|
322
|
+
):
|
|
323
|
+
return module.awslambdaricmain.bootstrap
|
|
324
|
+
elif hasattr(module, "bootstrap"):
|
|
325
|
+
# awslambdaric python module in container builds
|
|
326
|
+
return module.bootstrap
|
|
327
|
+
|
|
328
|
+
# python3.8 runtime
|
|
329
|
+
return module
|
|
330
|
+
else:
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
|
|
248
334
|
def _make_request_event_processor(aws_event, aws_context, configured_timeout):
|
|
249
335
|
# type: (Any, Any, Any) -> EventProcessor
|
|
250
|
-
start_time = datetime.
|
|
336
|
+
start_time = datetime.now(timezone.utc)
|
|
251
337
|
|
|
252
|
-
def event_processor(
|
|
338
|
+
def event_processor(sentry_event, hint, start_time=start_time):
|
|
253
339
|
# type: (Event, Hint, datetime) -> Optional[Event]
|
|
254
340
|
remaining_time_in_milis = aws_context.get_remaining_time_in_millis()
|
|
255
341
|
exec_duration = configured_timeout - remaining_time_in_milis
|
|
256
342
|
|
|
257
|
-
extra =
|
|
343
|
+
extra = sentry_event.setdefault("extra", {})
|
|
258
344
|
extra["lambda"] = {
|
|
259
345
|
"function_name": aws_context.function_name,
|
|
260
346
|
"function_version": aws_context.function_version,
|
|
@@ -270,7 +356,7 @@ def _make_request_event_processor(aws_event, aws_context, configured_timeout):
|
|
|
270
356
|
"log_stream": aws_context.log_stream_name,
|
|
271
357
|
}
|
|
272
358
|
|
|
273
|
-
request =
|
|
359
|
+
request = sentry_event.get("request", {})
|
|
274
360
|
|
|
275
361
|
if "httpMethod" in aws_event:
|
|
276
362
|
request["method"] = aws_event["httpMethod"]
|
|
@@ -283,14 +369,18 @@ def _make_request_event_processor(aws_event, aws_context, configured_timeout):
|
|
|
283
369
|
if "headers" in aws_event:
|
|
284
370
|
request["headers"] = _filter_headers(aws_event["headers"])
|
|
285
371
|
|
|
286
|
-
if
|
|
287
|
-
user_info =
|
|
372
|
+
if should_send_default_pii():
|
|
373
|
+
user_info = sentry_event.setdefault("user", {})
|
|
288
374
|
|
|
289
|
-
|
|
375
|
+
identity = aws_event.get("identity")
|
|
376
|
+
if identity is None:
|
|
377
|
+
identity = {}
|
|
378
|
+
|
|
379
|
+
id = identity.get("userArn")
|
|
290
380
|
if id is not None:
|
|
291
381
|
user_info.setdefault("id", id)
|
|
292
382
|
|
|
293
|
-
ip =
|
|
383
|
+
ip = identity.get("sourceIp")
|
|
294
384
|
if ip is not None:
|
|
295
385
|
user_info.setdefault("ip_address", ip)
|
|
296
386
|
|
|
@@ -300,49 +390,112 @@ def _make_request_event_processor(aws_event, aws_context, configured_timeout):
|
|
|
300
390
|
if aws_event.get("body", None):
|
|
301
391
|
# Unfortunately couldn't find a way to get structured body from AWS
|
|
302
392
|
# event. Meaning every body is unstructured to us.
|
|
303
|
-
request["data"] = AnnotatedValue(
|
|
393
|
+
request["data"] = AnnotatedValue.removed_because_raw_data()
|
|
304
394
|
|
|
305
|
-
|
|
395
|
+
sentry_event["request"] = deepcopy(request)
|
|
306
396
|
|
|
307
|
-
return
|
|
397
|
+
return sentry_event
|
|
308
398
|
|
|
309
399
|
return event_processor
|
|
310
400
|
|
|
311
401
|
|
|
312
|
-
def _get_url(
|
|
402
|
+
def _get_url(aws_event, aws_context):
|
|
313
403
|
# type: (Any, Any) -> str
|
|
314
|
-
path =
|
|
315
|
-
|
|
404
|
+
path = aws_event.get("path", None)
|
|
405
|
+
|
|
406
|
+
headers = aws_event.get("headers")
|
|
407
|
+
if headers is None:
|
|
408
|
+
headers = {}
|
|
409
|
+
|
|
316
410
|
host = headers.get("Host", None)
|
|
317
411
|
proto = headers.get("X-Forwarded-Proto", None)
|
|
318
412
|
if proto and host and path:
|
|
319
413
|
return "{}://{}{}".format(proto, host, path)
|
|
320
|
-
return "awslambda:///{}".format(
|
|
414
|
+
return "awslambda:///{}".format(aws_context.function_name)
|
|
321
415
|
|
|
322
416
|
|
|
323
|
-
def _get_cloudwatch_logs_url(
|
|
417
|
+
def _get_cloudwatch_logs_url(aws_context, start_time):
|
|
324
418
|
# type: (Any, datetime) -> str
|
|
325
419
|
"""
|
|
326
420
|
Generates a CloudWatchLogs console URL based on the context object
|
|
327
421
|
|
|
328
422
|
Arguments:
|
|
329
|
-
|
|
423
|
+
aws_context {Any} -- context from lambda handler
|
|
330
424
|
|
|
331
425
|
Returns:
|
|
332
426
|
str -- AWS Console URL to logs.
|
|
333
427
|
"""
|
|
334
428
|
formatstring = "%Y-%m-%dT%H:%M:%SZ"
|
|
429
|
+
region = environ.get("AWS_REGION", "")
|
|
335
430
|
|
|
336
431
|
url = (
|
|
337
|
-
"https://console.
|
|
432
|
+
"https://console.{domain}/cloudwatch/home?region={region}"
|
|
338
433
|
"#logEventViewer:group={log_group};stream={log_stream}"
|
|
339
434
|
";start={start_time};end={end_time}"
|
|
340
435
|
).format(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
436
|
+
domain="amazonaws.cn" if region.startswith("cn-") else "aws.amazon.com",
|
|
437
|
+
region=region,
|
|
438
|
+
log_group=aws_context.log_group_name,
|
|
439
|
+
log_stream=aws_context.log_stream_name,
|
|
344
440
|
start_time=(start_time - timedelta(seconds=1)).strftime(formatstring),
|
|
345
|
-
end_time=(datetime.
|
|
441
|
+
end_time=(datetime.now(timezone.utc) + timedelta(seconds=2)).strftime(
|
|
442
|
+
formatstring
|
|
443
|
+
),
|
|
346
444
|
)
|
|
347
445
|
|
|
348
446
|
return url
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def _parse_formatted_traceback(formatted_tb):
|
|
450
|
+
# type: (list[str]) -> list[dict[str, Any]]
|
|
451
|
+
frames = []
|
|
452
|
+
for frame in formatted_tb:
|
|
453
|
+
match = re.match(r'File "(.+)", line (\d+), in (.+)', frame.strip())
|
|
454
|
+
if match:
|
|
455
|
+
file_name, line_number, func_name = match.groups()
|
|
456
|
+
line_number = int(line_number)
|
|
457
|
+
frames.append(
|
|
458
|
+
{
|
|
459
|
+
"filename": file_name,
|
|
460
|
+
"function": func_name,
|
|
461
|
+
"lineno": line_number,
|
|
462
|
+
"vars": None,
|
|
463
|
+
"pre_context": None,
|
|
464
|
+
"context_line": None,
|
|
465
|
+
"post_context": None,
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
return frames
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _event_from_error_json(error_json):
|
|
472
|
+
# type: (dict[str, Any]) -> Event
|
|
473
|
+
"""
|
|
474
|
+
Converts the error JSON from AWS Lambda into a Sentry error event.
|
|
475
|
+
This is not a full fletched event, but better than nothing.
|
|
476
|
+
|
|
477
|
+
This is an example of where AWS creates the error JSON:
|
|
478
|
+
https://github.com/aws/aws-lambda-python-runtime-interface-client/blob/2.2.1/awslambdaric/bootstrap.py#L479
|
|
479
|
+
"""
|
|
480
|
+
event = {
|
|
481
|
+
"level": "error",
|
|
482
|
+
"exception": {
|
|
483
|
+
"values": [
|
|
484
|
+
{
|
|
485
|
+
"type": error_json.get("errorType"),
|
|
486
|
+
"value": error_json.get("errorMessage"),
|
|
487
|
+
"stacktrace": {
|
|
488
|
+
"frames": _parse_formatted_traceback(
|
|
489
|
+
error_json.get("stackTrace", [])
|
|
490
|
+
),
|
|
491
|
+
},
|
|
492
|
+
"mechanism": {
|
|
493
|
+
"type": "aws_lambda",
|
|
494
|
+
"handled": False,
|
|
495
|
+
},
|
|
496
|
+
}
|
|
497
|
+
],
|
|
498
|
+
},
|
|
499
|
+
} # type: Event
|
|
500
|
+
|
|
501
|
+
return event
|
sentry_sdk/integrations/beam.py
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
from __future__ import absolute_import
|
|
2
|
-
|
|
3
1
|
import sys
|
|
4
2
|
import types
|
|
5
|
-
from
|
|
3
|
+
from functools import wraps
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
from sentry_sdk._compat import reraise
|
|
9
|
-
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
|
|
5
|
+
import sentry_sdk
|
|
10
6
|
from sentry_sdk.integrations import Integration
|
|
11
7
|
from sentry_sdk.integrations.logging import ignore_logger
|
|
12
|
-
from sentry_sdk.
|
|
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
|
|
13
16
|
|
|
14
|
-
if
|
|
17
|
+
if TYPE_CHECKING:
|
|
15
18
|
from typing import Any
|
|
16
19
|
from typing import Iterator
|
|
17
20
|
from typing import TypeVar
|
|
18
|
-
from typing import Optional
|
|
19
21
|
from typing import Callable
|
|
20
22
|
|
|
21
|
-
from sentry_sdk.client import Client
|
|
22
23
|
from sentry_sdk._types import ExcInfo
|
|
23
24
|
|
|
24
25
|
T = TypeVar("T")
|
|
@@ -80,7 +81,6 @@ class BeamIntegration(Integration):
|
|
|
80
81
|
|
|
81
82
|
def _wrap_inspect_call(cls, func_name):
|
|
82
83
|
# type: (Any, Any) -> Any
|
|
83
|
-
from apache_beam.typehints.decorators import getfullargspec # type: ignore
|
|
84
84
|
|
|
85
85
|
if not hasattr(cls, func_name):
|
|
86
86
|
return None
|
|
@@ -105,6 +105,8 @@ def _wrap_inspect_call(cls, func_name):
|
|
|
105
105
|
|
|
106
106
|
return get_function_args_defaults(process_func)
|
|
107
107
|
except ImportError:
|
|
108
|
+
from apache_beam.typehints.decorators import getfullargspec # type: ignore
|
|
109
|
+
|
|
108
110
|
return getfullargspec(process_func)
|
|
109
111
|
|
|
110
112
|
setattr(_inspect, USED_FUNC, True)
|
|
@@ -115,9 +117,7 @@ def _wrap_task_call(func):
|
|
|
115
117
|
# type: (F) -> F
|
|
116
118
|
"""
|
|
117
119
|
Wrap task call with a try catch to get exceptions.
|
|
118
|
-
Pass the client on to raise_exception so it can get rebinded.
|
|
119
120
|
"""
|
|
120
|
-
client = Hub.current.client
|
|
121
121
|
|
|
122
122
|
@wraps(func)
|
|
123
123
|
def _inner(*args, **kwargs):
|
|
@@ -125,53 +125,45 @@ def _wrap_task_call(func):
|
|
|
125
125
|
try:
|
|
126
126
|
gen = func(*args, **kwargs)
|
|
127
127
|
except Exception:
|
|
128
|
-
raise_exception(
|
|
128
|
+
raise_exception()
|
|
129
129
|
|
|
130
130
|
if not isinstance(gen, types.GeneratorType):
|
|
131
131
|
return gen
|
|
132
|
-
return _wrap_generator_call(gen
|
|
132
|
+
return _wrap_generator_call(gen)
|
|
133
133
|
|
|
134
134
|
setattr(_inner, USED_FUNC, True)
|
|
135
135
|
return _inner # type: ignore
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
@ensure_integration_enabled(BeamIntegration)
|
|
139
|
+
def _capture_exception(exc_info):
|
|
140
|
+
# type: (ExcInfo) -> None
|
|
140
141
|
"""
|
|
141
142
|
Send Beam exception to Sentry.
|
|
142
143
|
"""
|
|
143
|
-
|
|
144
|
-
if integration is None:
|
|
145
|
-
return
|
|
146
|
-
|
|
147
|
-
client = hub.client
|
|
148
|
-
if client is None:
|
|
149
|
-
return
|
|
144
|
+
client = sentry_sdk.get_client()
|
|
150
145
|
|
|
151
146
|
event, hint = event_from_exception(
|
|
152
147
|
exc_info,
|
|
153
148
|
client_options=client.options,
|
|
154
149
|
mechanism={"type": "beam", "handled": False},
|
|
155
150
|
)
|
|
156
|
-
|
|
151
|
+
sentry_sdk.capture_event(event, hint=hint)
|
|
157
152
|
|
|
158
153
|
|
|
159
|
-
def raise_exception(
|
|
160
|
-
# type: (
|
|
154
|
+
def raise_exception():
|
|
155
|
+
# type: () -> None
|
|
161
156
|
"""
|
|
162
|
-
Raise an exception.
|
|
157
|
+
Raise an exception.
|
|
163
158
|
"""
|
|
164
|
-
hub = Hub.current
|
|
165
|
-
if hub.client is None:
|
|
166
|
-
hub.bind_client(client)
|
|
167
159
|
exc_info = sys.exc_info()
|
|
168
160
|
with capture_internal_exceptions():
|
|
169
|
-
_capture_exception(exc_info
|
|
161
|
+
_capture_exception(exc_info)
|
|
170
162
|
reraise(*exc_info)
|
|
171
163
|
|
|
172
164
|
|
|
173
|
-
def _wrap_generator_call(gen
|
|
174
|
-
# type: (Iterator[T]
|
|
165
|
+
def _wrap_generator_call(gen):
|
|
166
|
+
# type: (Iterator[T]) -> Iterator[T]
|
|
175
167
|
"""
|
|
176
168
|
Wrap the generator to handle any failures.
|
|
177
169
|
"""
|
|
@@ -181,4 +173,4 @@ def _wrap_generator_call(gen, client):
|
|
|
181
173
|
except StopIteration:
|
|
182
174
|
break
|
|
183
175
|
except Exception:
|
|
184
|
-
raise_exception(
|
|
176
|
+
raise_exception()
|