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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -30
  2. sentry_sdk/_compat.py +74 -61
  3. sentry_sdk/_init_implementation.py +84 -0
  4. sentry_sdk/_log_batcher.py +172 -0
  5. sentry_sdk/_lru_cache.py +47 -0
  6. sentry_sdk/_metrics_batcher.py +167 -0
  7. sentry_sdk/_queue.py +289 -0
  8. sentry_sdk/_types.py +338 -0
  9. sentry_sdk/_werkzeug.py +98 -0
  10. sentry_sdk/ai/__init__.py +7 -0
  11. sentry_sdk/ai/monitoring.py +137 -0
  12. sentry_sdk/ai/utils.py +144 -0
  13. sentry_sdk/api.py +496 -80
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +1023 -103
  16. sentry_sdk/consts.py +1438 -66
  17. sentry_sdk/crons/__init__.py +10 -0
  18. sentry_sdk/crons/api.py +62 -0
  19. sentry_sdk/crons/consts.py +4 -0
  20. sentry_sdk/crons/decorator.py +135 -0
  21. sentry_sdk/debug.py +15 -14
  22. sentry_sdk/envelope.py +369 -0
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +611 -280
  25. sentry_sdk/integrations/__init__.py +276 -49
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +180 -44
  28. sentry_sdk/integrations/aiohttp.py +291 -42
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +9 -8
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +341 -0
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +17 -10
  37. sentry_sdk/integrations/aws_lambda.py +377 -62
  38. sentry_sdk/integrations/beam.py +176 -0
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +221 -0
  41. sentry_sdk/integrations/celery/__init__.py +529 -0
  42. sentry_sdk/integrations/celery/beat.py +293 -0
  43. sentry_sdk/integrations/celery/utils.py +43 -0
  44. sentry_sdk/integrations/chalice.py +134 -0
  45. sentry_sdk/integrations/clickhouse_driver.py +177 -0
  46. sentry_sdk/integrations/cloud_resource_context.py +280 -0
  47. sentry_sdk/integrations/cohere.py +274 -0
  48. sentry_sdk/integrations/dedupe.py +48 -14
  49. sentry_sdk/integrations/django/__init__.py +584 -191
  50. sentry_sdk/integrations/django/asgi.py +245 -0
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +187 -0
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +79 -5
  55. sentry_sdk/integrations/django/transactions.py +49 -22
  56. sentry_sdk/integrations/django/views.py +96 -0
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +50 -13
  59. sentry_sdk/integrations/executing.py +67 -0
  60. sentry_sdk/integrations/falcon.py +272 -0
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +142 -88
  63. sentry_sdk/integrations/gcp.py +239 -0
  64. sentry_sdk/integrations/gnu_backtrace.py +99 -0
  65. sentry_sdk/integrations/google_genai/__init__.py +301 -0
  66. sentry_sdk/integrations/google_genai/consts.py +16 -0
  67. sentry_sdk/integrations/google_genai/streaming.py +155 -0
  68. sentry_sdk/integrations/google_genai/utils.py +576 -0
  69. sentry_sdk/integrations/gql.py +162 -0
  70. sentry_sdk/integrations/graphene.py +151 -0
  71. sentry_sdk/integrations/grpc/__init__.py +168 -0
  72. sentry_sdk/integrations/grpc/aio/__init__.py +7 -0
  73. sentry_sdk/integrations/grpc/aio/client.py +95 -0
  74. sentry_sdk/integrations/grpc/aio/server.py +100 -0
  75. sentry_sdk/integrations/grpc/client.py +91 -0
  76. sentry_sdk/integrations/grpc/consts.py +1 -0
  77. sentry_sdk/integrations/grpc/server.py +66 -0
  78. sentry_sdk/integrations/httpx.py +178 -0
  79. sentry_sdk/integrations/huey.py +174 -0
  80. sentry_sdk/integrations/huggingface_hub.py +378 -0
  81. sentry_sdk/integrations/langchain.py +1132 -0
  82. sentry_sdk/integrations/langgraph.py +337 -0
  83. sentry_sdk/integrations/launchdarkly.py +61 -0
  84. sentry_sdk/integrations/litellm.py +287 -0
  85. sentry_sdk/integrations/litestar.py +315 -0
  86. sentry_sdk/integrations/logging.py +307 -96
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +14 -31
  90. sentry_sdk/integrations/openai.py +725 -0
  91. sentry_sdk/integrations/openai_agents/__init__.py +61 -0
  92. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  93. sentry_sdk/integrations/openai_agents/patches/__init__.py +5 -0
  94. sentry_sdk/integrations/openai_agents/patches/agent_run.py +140 -0
  95. sentry_sdk/integrations/openai_agents/patches/error_tracing.py +77 -0
  96. sentry_sdk/integrations/openai_agents/patches/models.py +50 -0
  97. sentry_sdk/integrations/openai_agents/patches/runner.py +45 -0
  98. sentry_sdk/integrations/openai_agents/patches/tools.py +77 -0
  99. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  100. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +21 -0
  101. sentry_sdk/integrations/openai_agents/spans/ai_client.py +42 -0
  102. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +48 -0
  103. sentry_sdk/integrations/openai_agents/spans/handoff.py +19 -0
  104. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +86 -0
  105. sentry_sdk/integrations/openai_agents/utils.py +199 -0
  106. sentry_sdk/integrations/openfeature.py +35 -0
  107. sentry_sdk/integrations/opentelemetry/__init__.py +7 -0
  108. sentry_sdk/integrations/opentelemetry/consts.py +5 -0
  109. sentry_sdk/integrations/opentelemetry/integration.py +58 -0
  110. sentry_sdk/integrations/opentelemetry/propagator.py +117 -0
  111. sentry_sdk/integrations/opentelemetry/span_processor.py +391 -0
  112. sentry_sdk/integrations/otlp.py +82 -0
  113. sentry_sdk/integrations/pure_eval.py +141 -0
  114. sentry_sdk/integrations/pydantic_ai/__init__.py +47 -0
  115. sentry_sdk/integrations/pydantic_ai/consts.py +1 -0
  116. sentry_sdk/integrations/pydantic_ai/patches/__init__.py +4 -0
  117. sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +215 -0
  118. sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +110 -0
  119. sentry_sdk/integrations/pydantic_ai/patches/model_request.py +40 -0
  120. sentry_sdk/integrations/pydantic_ai/patches/tools.py +98 -0
  121. sentry_sdk/integrations/pydantic_ai/spans/__init__.py +3 -0
  122. sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +246 -0
  123. sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +49 -0
  124. sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +112 -0
  125. sentry_sdk/integrations/pydantic_ai/utils.py +223 -0
  126. sentry_sdk/integrations/pymongo.py +214 -0
  127. sentry_sdk/integrations/pyramid.py +112 -68
  128. sentry_sdk/integrations/quart.py +237 -0
  129. sentry_sdk/integrations/ray.py +165 -0
  130. sentry_sdk/integrations/redis/__init__.py +48 -0
  131. sentry_sdk/integrations/redis/_async_common.py +116 -0
  132. sentry_sdk/integrations/redis/_sync_common.py +119 -0
  133. sentry_sdk/integrations/redis/consts.py +19 -0
  134. sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  135. sentry_sdk/integrations/redis/modules/caches.py +118 -0
  136. sentry_sdk/integrations/redis/modules/queries.py +65 -0
  137. sentry_sdk/integrations/redis/rb.py +32 -0
  138. sentry_sdk/integrations/redis/redis.py +69 -0
  139. sentry_sdk/integrations/redis/redis_cluster.py +107 -0
  140. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +50 -0
  141. sentry_sdk/integrations/redis/utils.py +148 -0
  142. sentry_sdk/integrations/rq.py +95 -37
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +294 -123
  145. sentry_sdk/integrations/serverless.py +48 -19
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/__init__.py +4 -0
  148. sentry_sdk/integrations/spark/spark_driver.py +316 -0
  149. sentry_sdk/integrations/spark/spark_worker.py +116 -0
  150. sentry_sdk/integrations/sqlalchemy.py +142 -0
  151. sentry_sdk/integrations/starlette.py +737 -0
  152. sentry_sdk/integrations/starlite.py +292 -0
  153. sentry_sdk/integrations/statsig.py +37 -0
  154. sentry_sdk/integrations/stdlib.py +235 -29
  155. sentry_sdk/integrations/strawberry.py +394 -0
  156. sentry_sdk/integrations/sys_exit.py +70 -0
  157. sentry_sdk/integrations/threading.py +158 -28
  158. sentry_sdk/integrations/tornado.py +84 -52
  159. sentry_sdk/integrations/trytond.py +50 -0
  160. sentry_sdk/integrations/typer.py +60 -0
  161. sentry_sdk/integrations/unleash.py +33 -0
  162. sentry_sdk/integrations/unraisablehook.py +53 -0
  163. sentry_sdk/integrations/wsgi.py +201 -119
  164. sentry_sdk/logger.py +96 -0
  165. sentry_sdk/metrics.py +81 -0
  166. sentry_sdk/monitor.py +120 -0
  167. sentry_sdk/profiler/__init__.py +49 -0
  168. sentry_sdk/profiler/continuous_profiler.py +730 -0
  169. sentry_sdk/profiler/transaction_profiler.py +839 -0
  170. sentry_sdk/profiler/utils.py +195 -0
  171. sentry_sdk/py.typed +0 -0
  172. sentry_sdk/scope.py +1713 -85
  173. sentry_sdk/scrubber.py +177 -0
  174. sentry_sdk/serializer.py +405 -0
  175. sentry_sdk/session.py +177 -0
  176. sentry_sdk/sessions.py +275 -0
  177. sentry_sdk/spotlight.py +242 -0
  178. sentry_sdk/tracing.py +1486 -0
  179. sentry_sdk/tracing_utils.py +1236 -0
  180. sentry_sdk/transport.py +806 -134
  181. sentry_sdk/types.py +52 -0
  182. sentry_sdk/utils.py +1625 -465
  183. sentry_sdk/worker.py +54 -25
  184. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  185. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  186. {sentry_sdk-0.7.5.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  187. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  188. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  189. sentry_sdk/integrations/celery.py +0 -119
  190. sentry_sdk-0.7.5.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.7.5.dist-info/METADATA +0 -36
  192. sentry_sdk-0.7.5.dist-info/RECORD +0 -39
  193. {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 sentry_sdk.hub import Hub, _should_send_default_pii
4
- from sentry_sdk._compat import reraise
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
- def _wrap_handler(handler):
16
- def sentry_handler(event, context, *args, **kwargs):
17
- hub = Hub.current
18
- integration = hub.get_integration(AwsLambdaIntegration)
19
- if integration is None:
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
- with hub.push_scope() as scope:
23
- with capture_internal_exceptions():
24
- scope.transaction = context.function_name
25
- scope.add_event_processor(_make_request_event_processor(event, context))
26
-
27
- try:
28
- return handler(event, context, *args, **kwargs)
29
- except Exception:
30
- exc_info = sys.exc_info()
31
- event, hint = event_from_exception(
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=hub.client.options,
57
+ client_options=client.options,
34
58
  mechanism={"type": "aws_lambda", "handled": False},
35
59
  )
36
- hub.capture_event(event, hint=hint)
37
- reraise(*exc_info)
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 sentry_handler
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
- hub = Hub.current
45
- integration = hub.get_integration(AwsLambdaIntegration)
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
- hub.client.flush()
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
- import __main__ as lambda_bootstrap # type: ignore
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
- pre_37 = False # Python 3.7
66
- except ImportError:
67
- pass
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 = _wrap_post_function(
127
- lambda_bootstrap.LambdaRuntimeClient.post_invocation_result
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 = _wrap_post_function(
130
- lambda_bootstrap.LambdaRuntimeClient.post_invocation_error
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 _make_request_event_processor(aws_event, aws_context):
135
- def event_processor(event, hint):
136
- extra = event.setdefault("extra", {})
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
- request = event.setdefault("request", {})
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 aws_event.get("body", None):
159
- # Unfortunately couldn't find a way to get structured body from AWS
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
- if _should_send_default_pii():
164
- user_info = event.setdefault("user", {})
375
+ identity = aws_event.get("identity")
376
+ if identity is None:
377
+ identity = {}
165
378
 
166
- id = aws_event.get("identity", {}).get("userArn")
379
+ id = identity.get("userArn")
167
380
  if id is not None:
168
- user_info["id"] = id
381
+ user_info.setdefault("id", id)
169
382
 
170
- ip = aws_event.get("identity", {}).get("sourceIp")
383
+ ip = identity.get("sourceIp")
171
384
  if ip is not None:
172
- user_info["ip_address"] = ip
385
+ user_info.setdefault("ip_address", ip)
173
386
 
174
- return event
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(event, context):
180
- path = event.get("path", None)
181
- headers = event.get("headers", {})
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(context.function_name)
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