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.
Files changed (193) hide show
  1. sentry_sdk/__init__.py +48 -6
  2. sentry_sdk/_compat.py +64 -56
  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 +81 -19
  8. sentry_sdk/_types.py +311 -11
  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 +409 -67
  14. sentry_sdk/attachments.py +75 -0
  15. sentry_sdk/client.py +849 -103
  16. sentry_sdk/consts.py +1389 -34
  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 +12 -15
  22. sentry_sdk/envelope.py +112 -61
  23. sentry_sdk/feature_flags.py +71 -0
  24. sentry_sdk/hub.py +442 -386
  25. sentry_sdk/integrations/__init__.py +228 -58
  26. sentry_sdk/integrations/_asgi_common.py +108 -0
  27. sentry_sdk/integrations/_wsgi_common.py +131 -40
  28. sentry_sdk/integrations/aiohttp.py +221 -72
  29. sentry_sdk/integrations/anthropic.py +439 -0
  30. sentry_sdk/integrations/argv.py +4 -6
  31. sentry_sdk/integrations/ariadne.py +161 -0
  32. sentry_sdk/integrations/arq.py +247 -0
  33. sentry_sdk/integrations/asgi.py +237 -135
  34. sentry_sdk/integrations/asyncio.py +144 -0
  35. sentry_sdk/integrations/asyncpg.py +208 -0
  36. sentry_sdk/integrations/atexit.py +13 -18
  37. sentry_sdk/integrations/aws_lambda.py +233 -80
  38. sentry_sdk/integrations/beam.py +27 -35
  39. sentry_sdk/integrations/boto3.py +137 -0
  40. sentry_sdk/integrations/bottle.py +91 -69
  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 +35 -28
  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 +32 -8
  49. sentry_sdk/integrations/django/__init__.py +343 -89
  50. sentry_sdk/integrations/django/asgi.py +201 -22
  51. sentry_sdk/integrations/django/caching.py +204 -0
  52. sentry_sdk/integrations/django/middleware.py +80 -32
  53. sentry_sdk/integrations/django/signals_handlers.py +91 -0
  54. sentry_sdk/integrations/django/templates.py +69 -2
  55. sentry_sdk/integrations/django/transactions.py +39 -14
  56. sentry_sdk/integrations/django/views.py +69 -16
  57. sentry_sdk/integrations/dramatiq.py +226 -0
  58. sentry_sdk/integrations/excepthook.py +19 -13
  59. sentry_sdk/integrations/executing.py +5 -6
  60. sentry_sdk/integrations/falcon.py +128 -65
  61. sentry_sdk/integrations/fastapi.py +141 -0
  62. sentry_sdk/integrations/flask.py +114 -75
  63. sentry_sdk/integrations/gcp.py +67 -36
  64. sentry_sdk/integrations/gnu_backtrace.py +14 -22
  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 +261 -85
  87. sentry_sdk/integrations/loguru.py +213 -0
  88. sentry_sdk/integrations/mcp.py +566 -0
  89. sentry_sdk/integrations/modules.py +6 -33
  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 +20 -11
  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 +71 -60
  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 +62 -52
  143. sentry_sdk/integrations/rust_tracing.py +284 -0
  144. sentry_sdk/integrations/sanic.py +248 -114
  145. sentry_sdk/integrations/serverless.py +13 -22
  146. sentry_sdk/integrations/socket.py +96 -0
  147. sentry_sdk/integrations/spark/spark_driver.py +115 -62
  148. sentry_sdk/integrations/spark/spark_worker.py +42 -50
  149. sentry_sdk/integrations/sqlalchemy.py +82 -37
  150. sentry_sdk/integrations/starlette.py +737 -0
  151. sentry_sdk/integrations/starlite.py +292 -0
  152. sentry_sdk/integrations/statsig.py +37 -0
  153. sentry_sdk/integrations/stdlib.py +100 -58
  154. sentry_sdk/integrations/strawberry.py +394 -0
  155. sentry_sdk/integrations/sys_exit.py +70 -0
  156. sentry_sdk/integrations/threading.py +142 -38
  157. sentry_sdk/integrations/tornado.py +68 -53
  158. sentry_sdk/integrations/trytond.py +15 -20
  159. sentry_sdk/integrations/typer.py +60 -0
  160. sentry_sdk/integrations/unleash.py +33 -0
  161. sentry_sdk/integrations/unraisablehook.py +53 -0
  162. sentry_sdk/integrations/wsgi.py +126 -125
  163. sentry_sdk/logger.py +96 -0
  164. sentry_sdk/metrics.py +81 -0
  165. sentry_sdk/monitor.py +120 -0
  166. sentry_sdk/profiler/__init__.py +49 -0
  167. sentry_sdk/profiler/continuous_profiler.py +730 -0
  168. sentry_sdk/profiler/transaction_profiler.py +839 -0
  169. sentry_sdk/profiler/utils.py +195 -0
  170. sentry_sdk/scope.py +1542 -112
  171. sentry_sdk/scrubber.py +177 -0
  172. sentry_sdk/serializer.py +152 -210
  173. sentry_sdk/session.py +177 -0
  174. sentry_sdk/sessions.py +202 -179
  175. sentry_sdk/spotlight.py +242 -0
  176. sentry_sdk/tracing.py +1202 -294
  177. sentry_sdk/tracing_utils.py +1236 -0
  178. sentry_sdk/transport.py +693 -189
  179. sentry_sdk/types.py +52 -0
  180. sentry_sdk/utils.py +1395 -228
  181. sentry_sdk/worker.py +30 -17
  182. sentry_sdk-2.46.0.dist-info/METADATA +268 -0
  183. sentry_sdk-2.46.0.dist-info/RECORD +189 -0
  184. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/WHEEL +1 -1
  185. sentry_sdk-2.46.0.dist-info/entry_points.txt +2 -0
  186. sentry_sdk-2.46.0.dist-info/licenses/LICENSE +21 -0
  187. sentry_sdk/_functools.py +0 -66
  188. sentry_sdk/integrations/celery.py +0 -275
  189. sentry_sdk/integrations/redis.py +0 -103
  190. sentry_sdk-0.18.0.dist-info/LICENSE +0 -9
  191. sentry_sdk-0.18.0.dist-info/METADATA +0 -66
  192. sentry_sdk-0.18.0.dist-info/RECORD +0 -65
  193. {sentry_sdk-0.18.0.dist-info → sentry_sdk-2.46.0.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,31 @@
1
- from datetime import datetime, timedelta
2
- from os import environ
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
- from sentry_sdk.hub import Hub, _should_send_default_pii
6
- from sentry_sdk.tracing import Transaction
7
- from sentry_sdk._compat import reraise
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 sentry_sdk._types import MYPY
26
+ from typing import TYPE_CHECKING
19
27
 
20
- if MYPY:
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
- with hub.configure_scope() as scope:
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
- event, hint = event_from_exception(
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
- hub.capture_event(event, hint=hint)
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
- def sentry_handler(event, context, *args, **kwargs):
77
+ @functools.wraps(handler)
78
+ def sentry_handler(aws_event, aws_context, *args, **kwargs):
69
79
  # type: (Any, Any, *Any, **Any) -> Any
70
- hub = Hub.current
71
- integration = hub.get_integration(AwsLambdaIntegration)
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(event, context, *args, **kwargs)
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
- # If an integration is there, a client has to be there.
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 hub.push_scope() as scope:
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(event, context, configured_time)
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
- scope.set_tag("aws_region", context.invoked_function_arn.split(":")[3])
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, configured_time / MILLIS_TO_SECONDS
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 = event.get("headers", {})
104
- transaction = Transaction.continue_from_headers(
105
- headers, op="serverless.function", name=context.function_name
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 hub.start_transaction(transaction):
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(event, context, *args, **kwargs)
169
+ return handler(aws_event, aws_context, *args, **kwargs)
110
170
  except Exception:
111
171
  exc_info = sys.exc_info()
112
- event, hint = event_from_exception(
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
- hub.capture_event(event, hint=hint)
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
- hub = Hub.current
127
- integration = hub.get_integration(AwsLambdaIntegration)
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
- hub.flush()
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
- # Python 2.7: Everything is in `__main__`.
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 or 2.7
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.utcnow()
336
+ start_time = datetime.now(timezone.utc)
251
337
 
252
- def event_processor(event, hint, start_time=start_time):
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 = event.setdefault("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 = event.get("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 _should_send_default_pii():
287
- user_info = event.setdefault("user", {})
372
+ if should_send_default_pii():
373
+ user_info = sentry_event.setdefault("user", {})
288
374
 
289
- id = aws_event.get("identity", {}).get("userArn")
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 = aws_event.get("identity", {}).get("sourceIp")
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("", {"rem": [["!raw", "x", 0, 0]]})
393
+ request["data"] = AnnotatedValue.removed_because_raw_data()
304
394
 
305
- event["request"] = request
395
+ sentry_event["request"] = deepcopy(request)
306
396
 
307
- return event
397
+ return sentry_event
308
398
 
309
399
  return event_processor
310
400
 
311
401
 
312
- def _get_url(event, context):
402
+ def _get_url(aws_event, aws_context):
313
403
  # type: (Any, Any) -> str
314
- path = event.get("path", None)
315
- headers = event.get("headers", {})
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(context.function_name)
414
+ return "awslambda:///{}".format(aws_context.function_name)
321
415
 
322
416
 
323
- def _get_cloudwatch_logs_url(context, start_time):
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
- context {Any} -- context from lambda handler
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.aws.amazon.com/cloudwatch/home?region={region}"
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
- region=environ.get("AWS_REGION"),
342
- log_group=context.log_group_name,
343
- log_stream=context.log_stream_name,
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.utcnow() + timedelta(seconds=2)).strftime(formatstring),
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
@@ -1,24 +1,25 @@
1
- from __future__ import absolute_import
2
-
3
1
  import sys
4
2
  import types
5
- from sentry_sdk._functools import wraps
3
+ from functools import wraps
6
4
 
7
- from sentry_sdk.hub import Hub
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._types import MYPY
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 MYPY:
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(client)
128
+ raise_exception()
129
129
 
130
130
  if not isinstance(gen, types.GeneratorType):
131
131
  return gen
132
- return _wrap_generator_call(gen, client)
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
- def _capture_exception(exc_info, hub):
139
- # type: (ExcInfo, Hub) -> None
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
- integration = hub.get_integration(BeamIntegration)
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
- hub.capture_event(event, hint=hint)
151
+ sentry_sdk.capture_event(event, hint=hint)
157
152
 
158
153
 
159
- def raise_exception(client):
160
- # type: (Optional[Client]) -> None
154
+ def raise_exception():
155
+ # type: () -> None
161
156
  """
162
- Raise an exception. If the client is not in the hub, rebind it.
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, hub)
161
+ _capture_exception(exc_info)
170
162
  reraise(*exc_info)
171
163
 
172
164
 
173
- def _wrap_generator_call(gen, client):
174
- # type: (Iterator[T], Optional[Client]) -> 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(client)
176
+ raise_exception()