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
|
@@ -1,82 +1,233 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
1
4
|
import sys
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from os import environ
|
|
8
|
+
|
|
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
|
|
5
14
|
from sentry_sdk.utils import (
|
|
6
15
|
AnnotatedValue,
|
|
7
16
|
capture_internal_exceptions,
|
|
17
|
+
ensure_integration_enabled,
|
|
8
18
|
event_from_exception,
|
|
9
19
|
logger,
|
|
20
|
+
TimeoutThread,
|
|
21
|
+
reraise,
|
|
10
22
|
)
|
|
11
23
|
from sentry_sdk.integrations import Integration
|
|
12
24
|
from sentry_sdk.integrations._wsgi_common import _filter_headers
|
|
13
25
|
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return handler(event, context, *args, **kwargs)
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from typing import Any
|
|
30
|
+
from typing import TypeVar
|
|
31
|
+
from typing import Callable
|
|
32
|
+
from typing import Optional
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
from sentry_sdk._types import EventProcessor, Event, Hint
|
|
35
|
+
|
|
36
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
37
|
+
|
|
38
|
+
# Constants
|
|
39
|
+
TIMEOUT_WARNING_BUFFER = 1500 # Buffer time required to send timeout warning to Sentry
|
|
40
|
+
MILLIS_TO_SECONDS = 1000.0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _wrap_init_error(init_error):
|
|
44
|
+
# type: (F) -> F
|
|
45
|
+
@ensure_integration_enabled(AwsLambdaIntegration, init_error)
|
|
46
|
+
def sentry_init_error(*args, **kwargs):
|
|
47
|
+
# type: (*Any, **Any) -> Any
|
|
48
|
+
client = sentry_sdk.get_client()
|
|
49
|
+
|
|
50
|
+
with capture_internal_exceptions():
|
|
51
|
+
sentry_sdk.get_isolation_scope().clear_breadcrumbs()
|
|
52
|
+
|
|
53
|
+
exc_info = sys.exc_info()
|
|
54
|
+
if exc_info and all(exc_info):
|
|
55
|
+
sentry_event, hint = event_from_exception(
|
|
32
56
|
exc_info,
|
|
33
|
-
client_options=
|
|
57
|
+
client_options=client.options,
|
|
34
58
|
mechanism={"type": "aws_lambda", "handled": False},
|
|
35
59
|
)
|
|
36
|
-
|
|
37
|
-
|
|
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)
|
|
69
|
+
|
|
70
|
+
return init_error(*args, **kwargs)
|
|
38
71
|
|
|
39
|
-
return
|
|
72
|
+
return sentry_init_error # type: ignore
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _wrap_handler(handler):
|
|
76
|
+
# type: (F) -> F
|
|
77
|
+
@functools.wraps(handler)
|
|
78
|
+
def sentry_handler(aws_event, aws_context, *args, **kwargs):
|
|
79
|
+
# type: (Any, Any, *Any, **Any) -> Any
|
|
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
|
+
|
|
94
|
+
if integration is None:
|
|
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 = {}
|
|
109
|
+
|
|
110
|
+
configured_time = aws_context.get_remaining_time_in_millis()
|
|
111
|
+
|
|
112
|
+
with sentry_sdk.isolation_scope() as scope:
|
|
113
|
+
timeout_thread = None
|
|
114
|
+
with capture_internal_exceptions():
|
|
115
|
+
scope.clear_breadcrumbs()
|
|
116
|
+
scope.add_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]
|
|
123
|
+
)
|
|
124
|
+
if batch_size > 1:
|
|
125
|
+
scope.set_tag("batch_request", True)
|
|
126
|
+
scope.set_tag("batch_size", batch_size)
|
|
127
|
+
|
|
128
|
+
# Starting the Timeout thread only if the configured time is greater than Timeout warning
|
|
129
|
+
# buffer and timeout_warning parameter is set True.
|
|
130
|
+
if (
|
|
131
|
+
integration.timeout_warning
|
|
132
|
+
and configured_time > TIMEOUT_WARNING_BUFFER
|
|
133
|
+
):
|
|
134
|
+
waiting_time = (
|
|
135
|
+
configured_time - TIMEOUT_WARNING_BUFFER
|
|
136
|
+
) / MILLIS_TO_SECONDS
|
|
137
|
+
|
|
138
|
+
timeout_thread = TimeoutThread(
|
|
139
|
+
waiting_time,
|
|
140
|
+
configured_time / MILLIS_TO_SECONDS,
|
|
141
|
+
isolation_scope=scope,
|
|
142
|
+
current_scope=sentry_sdk.get_current_scope(),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Starting the thread to raise timeout warning exception
|
|
146
|
+
timeout_thread.start()
|
|
147
|
+
|
|
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,
|
|
160
|
+
)
|
|
161
|
+
with sentry_sdk.start_transaction(
|
|
162
|
+
transaction,
|
|
163
|
+
custom_sampling_context={
|
|
164
|
+
"aws_event": aws_event,
|
|
165
|
+
"aws_context": aws_context,
|
|
166
|
+
},
|
|
167
|
+
):
|
|
168
|
+
try:
|
|
169
|
+
return handler(aws_event, aws_context, *args, **kwargs)
|
|
170
|
+
except Exception:
|
|
171
|
+
exc_info = sys.exc_info()
|
|
172
|
+
sentry_event, hint = event_from_exception(
|
|
173
|
+
exc_info,
|
|
174
|
+
client_options=client.options,
|
|
175
|
+
mechanism={"type": "aws_lambda", "handled": False},
|
|
176
|
+
)
|
|
177
|
+
sentry_sdk.capture_event(sentry_event, hint=hint)
|
|
178
|
+
reraise(*exc_info)
|
|
179
|
+
finally:
|
|
180
|
+
if timeout_thread:
|
|
181
|
+
timeout_thread.stop()
|
|
182
|
+
|
|
183
|
+
return sentry_handler # type: ignore
|
|
40
184
|
|
|
41
185
|
|
|
42
186
|
def _drain_queue():
|
|
187
|
+
# type: () -> None
|
|
43
188
|
with capture_internal_exceptions():
|
|
44
|
-
|
|
45
|
-
integration =
|
|
189
|
+
client = sentry_sdk.get_client()
|
|
190
|
+
integration = client.get_integration(AwsLambdaIntegration)
|
|
46
191
|
if integration is not None:
|
|
47
192
|
# Flush out the event queue before AWS kills the
|
|
48
193
|
# process.
|
|
49
|
-
|
|
194
|
+
client.flush()
|
|
50
195
|
|
|
51
196
|
|
|
52
197
|
class AwsLambdaIntegration(Integration):
|
|
53
198
|
identifier = "aws_lambda"
|
|
199
|
+
origin = f"auto.function.{identifier}"
|
|
200
|
+
|
|
201
|
+
def __init__(self, timeout_warning=False):
|
|
202
|
+
# type: (bool) -> None
|
|
203
|
+
self.timeout_warning = timeout_warning
|
|
54
204
|
|
|
55
205
|
@staticmethod
|
|
56
206
|
def setup_once():
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
pre_37 = True # Python 3.6 or 2.7
|
|
60
|
-
|
|
61
|
-
if not hasattr(lambda_bootstrap, "handle_http_request"):
|
|
62
|
-
try:
|
|
63
|
-
import bootstrap as lambda_bootstrap # type: ignore
|
|
207
|
+
# type: () -> None
|
|
64
208
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
209
|
+
lambda_bootstrap = get_lambda_bootstrap()
|
|
210
|
+
if not lambda_bootstrap:
|
|
211
|
+
logger.warning(
|
|
212
|
+
"Not running in AWS Lambda environment, "
|
|
213
|
+
"AwsLambdaIntegration disabled (could not find bootstrap module)"
|
|
214
|
+
)
|
|
215
|
+
return
|
|
68
216
|
|
|
69
217
|
if not hasattr(lambda_bootstrap, "handle_event_request"):
|
|
70
218
|
logger.warning(
|
|
71
219
|
"Not running in AWS Lambda environment, "
|
|
72
|
-
"AwsLambdaIntegration disabled"
|
|
220
|
+
"AwsLambdaIntegration disabled (could not find handle_event_request)"
|
|
73
221
|
)
|
|
74
222
|
return
|
|
75
223
|
|
|
224
|
+
pre_37 = hasattr(lambda_bootstrap, "handle_http_request") # Python 3.6
|
|
225
|
+
|
|
76
226
|
if pre_37:
|
|
77
227
|
old_handle_event_request = lambda_bootstrap.handle_event_request
|
|
78
228
|
|
|
79
229
|
def sentry_handle_event_request(request_handler, *args, **kwargs):
|
|
230
|
+
# type: (Any, *Any, **Any) -> Any
|
|
80
231
|
request_handler = _wrap_handler(request_handler)
|
|
81
232
|
return old_handle_event_request(request_handler, *args, **kwargs)
|
|
82
233
|
|
|
@@ -85,6 +236,7 @@ class AwsLambdaIntegration(Integration):
|
|
|
85
236
|
old_handle_http_request = lambda_bootstrap.handle_http_request
|
|
86
237
|
|
|
87
238
|
def sentry_handle_http_request(request_handler, *args, **kwargs):
|
|
239
|
+
# type: (Any, *Any, **Any) -> Any
|
|
88
240
|
request_handler = _wrap_handler(request_handler)
|
|
89
241
|
return old_handle_http_request(request_handler, *args, **kwargs)
|
|
90
242
|
|
|
@@ -96,11 +248,16 @@ class AwsLambdaIntegration(Integration):
|
|
|
96
248
|
old_to_json = lambda_bootstrap.to_json
|
|
97
249
|
|
|
98
250
|
def sentry_to_json(*args, **kwargs):
|
|
251
|
+
# type: (*Any, **Any) -> Any
|
|
99
252
|
_drain_queue()
|
|
100
253
|
return old_to_json(*args, **kwargs)
|
|
101
254
|
|
|
102
255
|
lambda_bootstrap.to_json = sentry_to_json
|
|
103
256
|
else:
|
|
257
|
+
lambda_bootstrap.LambdaRuntimeClient.post_init_error = _wrap_init_error(
|
|
258
|
+
lambda_bootstrap.LambdaRuntimeClient.post_init_error
|
|
259
|
+
)
|
|
260
|
+
|
|
104
261
|
old_handle_event_request = lambda_bootstrap.handle_event_request
|
|
105
262
|
|
|
106
263
|
def sentry_handle_event_request( # type: ignore
|
|
@@ -117,32 +274,89 @@ class AwsLambdaIntegration(Integration):
|
|
|
117
274
|
# even when the SDK is initialized inside of the handler
|
|
118
275
|
|
|
119
276
|
def _wrap_post_function(f):
|
|
277
|
+
# type: (F) -> F
|
|
120
278
|
def inner(*args, **kwargs):
|
|
279
|
+
# type: (*Any, **Any) -> Any
|
|
121
280
|
_drain_queue()
|
|
122
281
|
return f(*args, **kwargs)
|
|
123
282
|
|
|
124
|
-
return inner
|
|
283
|
+
return inner # type: ignore
|
|
125
284
|
|
|
126
|
-
lambda_bootstrap.LambdaRuntimeClient.post_invocation_result =
|
|
127
|
-
|
|
285
|
+
lambda_bootstrap.LambdaRuntimeClient.post_invocation_result = (
|
|
286
|
+
_wrap_post_function(
|
|
287
|
+
lambda_bootstrap.LambdaRuntimeClient.post_invocation_result
|
|
288
|
+
)
|
|
128
289
|
)
|
|
129
|
-
lambda_bootstrap.LambdaRuntimeClient.post_invocation_error =
|
|
130
|
-
|
|
290
|
+
lambda_bootstrap.LambdaRuntimeClient.post_invocation_error = (
|
|
291
|
+
_wrap_post_function(
|
|
292
|
+
lambda_bootstrap.LambdaRuntimeClient.post_invocation_error
|
|
293
|
+
)
|
|
131
294
|
)
|
|
132
295
|
|
|
133
296
|
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
|
|
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
|
+
|
|
334
|
+
def _make_request_event_processor(aws_event, aws_context, configured_timeout):
|
|
335
|
+
# type: (Any, Any, Any) -> EventProcessor
|
|
336
|
+
start_time = datetime.now(timezone.utc)
|
|
337
|
+
|
|
338
|
+
def event_processor(sentry_event, hint, start_time=start_time):
|
|
339
|
+
# type: (Event, Hint, datetime) -> Optional[Event]
|
|
340
|
+
remaining_time_in_milis = aws_context.get_remaining_time_in_millis()
|
|
341
|
+
exec_duration = configured_timeout - remaining_time_in_milis
|
|
342
|
+
|
|
343
|
+
extra = sentry_event.setdefault("extra", {})
|
|
137
344
|
extra["lambda"] = {
|
|
138
|
-
"remaining_time_in_millis": aws_context.get_remaining_time_in_millis(),
|
|
139
345
|
"function_name": aws_context.function_name,
|
|
140
346
|
"function_version": aws_context.function_version,
|
|
141
347
|
"invoked_function_arn": aws_context.invoked_function_arn,
|
|
142
348
|
"aws_request_id": aws_context.aws_request_id,
|
|
349
|
+
"execution_duration_in_millis": exec_duration,
|
|
350
|
+
"remaining_time_in_millis": remaining_time_in_milis,
|
|
143
351
|
}
|
|
144
352
|
|
|
145
|
-
|
|
353
|
+
extra["cloudwatch logs"] = {
|
|
354
|
+
"url": _get_cloudwatch_logs_url(aws_context, start_time),
|
|
355
|
+
"log_group": aws_context.log_group_name,
|
|
356
|
+
"log_stream": aws_context.log_stream_name,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
request = sentry_event.get("request", {})
|
|
146
360
|
|
|
147
361
|
if "httpMethod" in aws_event:
|
|
148
362
|
request["method"] = aws_event["httpMethod"]
|
|
@@ -155,32 +369,133 @@ def _make_request_event_processor(aws_event, aws_context):
|
|
|
155
369
|
if "headers" in aws_event:
|
|
156
370
|
request["headers"] = _filter_headers(aws_event["headers"])
|
|
157
371
|
|
|
158
|
-
if
|
|
159
|
-
|
|
160
|
-
# event. Meaning every body is unstructured to us.
|
|
161
|
-
request["data"] = AnnotatedValue("", {"rem": [["!raw", "x", 0, 0]]})
|
|
372
|
+
if should_send_default_pii():
|
|
373
|
+
user_info = sentry_event.setdefault("user", {})
|
|
162
374
|
|
|
163
|
-
|
|
164
|
-
|
|
375
|
+
identity = aws_event.get("identity")
|
|
376
|
+
if identity is None:
|
|
377
|
+
identity = {}
|
|
165
378
|
|
|
166
|
-
id =
|
|
379
|
+
id = identity.get("userArn")
|
|
167
380
|
if id is not None:
|
|
168
|
-
user_info
|
|
381
|
+
user_info.setdefault("id", id)
|
|
169
382
|
|
|
170
|
-
ip =
|
|
383
|
+
ip = identity.get("sourceIp")
|
|
171
384
|
if ip is not None:
|
|
172
|
-
user_info
|
|
385
|
+
user_info.setdefault("ip_address", ip)
|
|
173
386
|
|
|
174
|
-
|
|
387
|
+
if "body" in aws_event:
|
|
388
|
+
request["data"] = aws_event.get("body", "")
|
|
389
|
+
else:
|
|
390
|
+
if aws_event.get("body", None):
|
|
391
|
+
# Unfortunately couldn't find a way to get structured body from AWS
|
|
392
|
+
# event. Meaning every body is unstructured to us.
|
|
393
|
+
request["data"] = AnnotatedValue.removed_because_raw_data()
|
|
394
|
+
|
|
395
|
+
sentry_event["request"] = deepcopy(request)
|
|
396
|
+
|
|
397
|
+
return sentry_event
|
|
175
398
|
|
|
176
399
|
return event_processor
|
|
177
400
|
|
|
178
401
|
|
|
179
|
-
def _get_url(
|
|
180
|
-
|
|
181
|
-
|
|
402
|
+
def _get_url(aws_event, aws_context):
|
|
403
|
+
# type: (Any, Any) -> str
|
|
404
|
+
path = aws_event.get("path", None)
|
|
405
|
+
|
|
406
|
+
headers = aws_event.get("headers")
|
|
407
|
+
if headers is None:
|
|
408
|
+
headers = {}
|
|
409
|
+
|
|
182
410
|
host = headers.get("Host", None)
|
|
183
411
|
proto = headers.get("X-Forwarded-Proto", None)
|
|
184
412
|
if proto and host and path:
|
|
185
413
|
return "{}://{}{}".format(proto, host, path)
|
|
186
|
-
return "awslambda:///{}".format(
|
|
414
|
+
return "awslambda:///{}".format(aws_context.function_name)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _get_cloudwatch_logs_url(aws_context, start_time):
|
|
418
|
+
# type: (Any, datetime) -> str
|
|
419
|
+
"""
|
|
420
|
+
Generates a CloudWatchLogs console URL based on the context object
|
|
421
|
+
|
|
422
|
+
Arguments:
|
|
423
|
+
aws_context {Any} -- context from lambda handler
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
str -- AWS Console URL to logs.
|
|
427
|
+
"""
|
|
428
|
+
formatstring = "%Y-%m-%dT%H:%M:%SZ"
|
|
429
|
+
region = environ.get("AWS_REGION", "")
|
|
430
|
+
|
|
431
|
+
url = (
|
|
432
|
+
"https://console.{domain}/cloudwatch/home?region={region}"
|
|
433
|
+
"#logEventViewer:group={log_group};stream={log_stream}"
|
|
434
|
+
";start={start_time};end={end_time}"
|
|
435
|
+
).format(
|
|
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,
|
|
440
|
+
start_time=(start_time - timedelta(seconds=1)).strftime(formatstring),
|
|
441
|
+
end_time=(datetime.now(timezone.utc) + timedelta(seconds=2)).strftime(
|
|
442
|
+
formatstring
|
|
443
|
+
),
|
|
444
|
+
)
|
|
445
|
+
|
|
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
|