sentry-sdk 3.0.0a1__py2.py3-none-any.whl → 3.0.0a3__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.

Potentially problematic release.


This version of sentry-sdk might be problematic. Click here for more details.

Files changed (157) hide show
  1. sentry_sdk/__init__.py +2 -0
  2. sentry_sdk/_compat.py +5 -12
  3. sentry_sdk/_init_implementation.py +7 -7
  4. sentry_sdk/_log_batcher.py +17 -29
  5. sentry_sdk/_lru_cache.py +7 -9
  6. sentry_sdk/_queue.py +2 -4
  7. sentry_sdk/_types.py +11 -18
  8. sentry_sdk/_werkzeug.py +5 -7
  9. sentry_sdk/ai/monitoring.py +44 -31
  10. sentry_sdk/ai/utils.py +3 -4
  11. sentry_sdk/api.py +75 -87
  12. sentry_sdk/attachments.py +10 -12
  13. sentry_sdk/client.py +137 -155
  14. sentry_sdk/consts.py +430 -174
  15. sentry_sdk/crons/api.py +16 -17
  16. sentry_sdk/crons/decorator.py +25 -27
  17. sentry_sdk/debug.py +4 -6
  18. sentry_sdk/envelope.py +46 -112
  19. sentry_sdk/feature_flags.py +9 -15
  20. sentry_sdk/integrations/__init__.py +24 -19
  21. sentry_sdk/integrations/_asgi_common.py +15 -18
  22. sentry_sdk/integrations/_wsgi_common.py +22 -33
  23. sentry_sdk/integrations/aiohttp.py +32 -30
  24. sentry_sdk/integrations/anthropic.py +42 -37
  25. sentry_sdk/integrations/argv.py +3 -4
  26. sentry_sdk/integrations/ariadne.py +16 -18
  27. sentry_sdk/integrations/arq.py +21 -29
  28. sentry_sdk/integrations/asgi.py +63 -37
  29. sentry_sdk/integrations/asyncio.py +14 -16
  30. sentry_sdk/integrations/atexit.py +6 -10
  31. sentry_sdk/integrations/aws_lambda.py +26 -36
  32. sentry_sdk/integrations/beam.py +10 -18
  33. sentry_sdk/integrations/boto3.py +18 -16
  34. sentry_sdk/integrations/bottle.py +25 -34
  35. sentry_sdk/integrations/celery/__init__.py +41 -61
  36. sentry_sdk/integrations/celery/beat.py +23 -27
  37. sentry_sdk/integrations/celery/utils.py +15 -17
  38. sentry_sdk/integrations/chalice.py +8 -10
  39. sentry_sdk/integrations/clickhouse_driver.py +21 -31
  40. sentry_sdk/integrations/cloud_resource_context.py +9 -16
  41. sentry_sdk/integrations/cohere.py +27 -33
  42. sentry_sdk/integrations/dedupe.py +5 -8
  43. sentry_sdk/integrations/django/__init__.py +57 -72
  44. sentry_sdk/integrations/django/asgi.py +26 -34
  45. sentry_sdk/integrations/django/caching.py +23 -19
  46. sentry_sdk/integrations/django/middleware.py +17 -20
  47. sentry_sdk/integrations/django/signals_handlers.py +11 -10
  48. sentry_sdk/integrations/django/templates.py +19 -16
  49. sentry_sdk/integrations/django/transactions.py +16 -11
  50. sentry_sdk/integrations/django/views.py +6 -10
  51. sentry_sdk/integrations/dramatiq.py +21 -21
  52. sentry_sdk/integrations/excepthook.py +10 -10
  53. sentry_sdk/integrations/executing.py +3 -4
  54. sentry_sdk/integrations/falcon.py +27 -42
  55. sentry_sdk/integrations/fastapi.py +13 -16
  56. sentry_sdk/integrations/flask.py +31 -38
  57. sentry_sdk/integrations/gcp.py +13 -16
  58. sentry_sdk/integrations/gnu_backtrace.py +4 -6
  59. sentry_sdk/integrations/gql.py +16 -17
  60. sentry_sdk/integrations/graphene.py +13 -12
  61. sentry_sdk/integrations/grpc/__init__.py +19 -1
  62. sentry_sdk/integrations/grpc/aio/server.py +15 -14
  63. sentry_sdk/integrations/grpc/client.py +19 -9
  64. sentry_sdk/integrations/grpc/consts.py +2 -0
  65. sentry_sdk/integrations/grpc/server.py +12 -8
  66. sentry_sdk/integrations/httpx.py +9 -12
  67. sentry_sdk/integrations/huey.py +13 -20
  68. sentry_sdk/integrations/huggingface_hub.py +18 -18
  69. sentry_sdk/integrations/langchain.py +203 -113
  70. sentry_sdk/integrations/launchdarkly.py +13 -10
  71. sentry_sdk/integrations/litestar.py +37 -35
  72. sentry_sdk/integrations/logging.py +52 -65
  73. sentry_sdk/integrations/loguru.py +127 -57
  74. sentry_sdk/integrations/modules.py +3 -4
  75. sentry_sdk/integrations/openai.py +100 -88
  76. sentry_sdk/integrations/openai_agents/__init__.py +49 -0
  77. sentry_sdk/integrations/openai_agents/consts.py +1 -0
  78. sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
  79. sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
  80. sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
  81. sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
  82. sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
  83. sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
  84. sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
  85. sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
  86. sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
  87. sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
  88. sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
  89. sentry_sdk/integrations/openai_agents/utils.py +201 -0
  90. sentry_sdk/integrations/openfeature.py +11 -6
  91. sentry_sdk/integrations/pure_eval.py +6 -10
  92. sentry_sdk/integrations/pymongo.py +13 -17
  93. sentry_sdk/integrations/pyramid.py +31 -36
  94. sentry_sdk/integrations/quart.py +23 -28
  95. sentry_sdk/integrations/ray.py +73 -64
  96. sentry_sdk/integrations/redis/__init__.py +7 -4
  97. sentry_sdk/integrations/redis/_async_common.py +25 -12
  98. sentry_sdk/integrations/redis/_sync_common.py +19 -13
  99. sentry_sdk/integrations/redis/modules/caches.py +17 -8
  100. sentry_sdk/integrations/redis/modules/queries.py +9 -8
  101. sentry_sdk/integrations/redis/rb.py +3 -2
  102. sentry_sdk/integrations/redis/redis.py +4 -4
  103. sentry_sdk/integrations/redis/redis_cluster.py +21 -13
  104. sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
  105. sentry_sdk/integrations/redis/utils.py +23 -24
  106. sentry_sdk/integrations/rq.py +13 -16
  107. sentry_sdk/integrations/rust_tracing.py +9 -6
  108. sentry_sdk/integrations/sanic.py +34 -46
  109. sentry_sdk/integrations/serverless.py +22 -27
  110. sentry_sdk/integrations/socket.py +27 -15
  111. sentry_sdk/integrations/spark/__init__.py +1 -0
  112. sentry_sdk/integrations/spark/spark_driver.py +45 -83
  113. sentry_sdk/integrations/spark/spark_worker.py +7 -11
  114. sentry_sdk/integrations/sqlalchemy.py +22 -19
  115. sentry_sdk/integrations/starlette.py +86 -90
  116. sentry_sdk/integrations/starlite.py +28 -34
  117. sentry_sdk/integrations/statsig.py +5 -4
  118. sentry_sdk/integrations/stdlib.py +28 -24
  119. sentry_sdk/integrations/strawberry.py +62 -49
  120. sentry_sdk/integrations/sys_exit.py +7 -11
  121. sentry_sdk/integrations/threading.py +12 -14
  122. sentry_sdk/integrations/tornado.py +28 -32
  123. sentry_sdk/integrations/trytond.py +4 -3
  124. sentry_sdk/integrations/typer.py +8 -6
  125. sentry_sdk/integrations/unleash.py +5 -4
  126. sentry_sdk/integrations/wsgi.py +47 -46
  127. sentry_sdk/logger.py +41 -10
  128. sentry_sdk/monitor.py +16 -28
  129. sentry_sdk/opentelemetry/consts.py +11 -4
  130. sentry_sdk/opentelemetry/contextvars_context.py +26 -16
  131. sentry_sdk/opentelemetry/propagator.py +38 -21
  132. sentry_sdk/opentelemetry/sampler.py +51 -34
  133. sentry_sdk/opentelemetry/scope.py +36 -37
  134. sentry_sdk/opentelemetry/span_processor.py +48 -58
  135. sentry_sdk/opentelemetry/tracing.py +58 -14
  136. sentry_sdk/opentelemetry/utils.py +186 -194
  137. sentry_sdk/profiler/continuous_profiler.py +108 -97
  138. sentry_sdk/profiler/transaction_profiler.py +70 -97
  139. sentry_sdk/profiler/utils.py +11 -15
  140. sentry_sdk/scope.py +251 -273
  141. sentry_sdk/scrubber.py +22 -26
  142. sentry_sdk/serializer.py +40 -54
  143. sentry_sdk/session.py +44 -61
  144. sentry_sdk/sessions.py +35 -49
  145. sentry_sdk/spotlight.py +15 -21
  146. sentry_sdk/tracing.py +121 -187
  147. sentry_sdk/tracing_utils.py +104 -122
  148. sentry_sdk/transport.py +131 -157
  149. sentry_sdk/utils.py +232 -309
  150. sentry_sdk/worker.py +16 -28
  151. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
  152. sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
  153. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
  154. sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
  155. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
  156. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
  157. {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  from functools import wraps
2
3
  from typing import Any
3
4
 
@@ -14,14 +15,14 @@ class UnleashIntegration(Integration):
14
15
  identifier = "unleash"
15
16
 
16
17
  @staticmethod
17
- def setup_once():
18
- # type: () -> None
18
+ def setup_once() -> None:
19
19
  # Wrap and patch evaluation methods (class methods)
20
20
  old_is_enabled = UnleashClient.is_enabled
21
21
 
22
22
  @wraps(old_is_enabled)
23
- def sentry_is_enabled(self, feature, *args, **kwargs):
24
- # type: (UnleashClient, str, *Any, **Any) -> Any
23
+ def sentry_is_enabled(
24
+ self: UnleashClient, feature: str, *args: Any, **kwargs: Any
25
+ ) -> Any:
25
26
  enabled = old_is_enabled(self, feature, *args, **kwargs)
26
27
 
27
28
  # We have no way of knowing what type of unleash feature this is, so we have to treat
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import sys
2
3
  from functools import partial
3
4
 
@@ -25,22 +26,26 @@ if TYPE_CHECKING:
25
26
  from typing import Callable
26
27
  from typing import Dict
27
28
  from typing import Iterator
29
+ from typing import Iterable
28
30
  from typing import Any
29
31
  from typing import Tuple
32
+ from typing import List
30
33
  from typing import Optional
31
- from typing import TypeVar
32
34
  from typing import Protocol
33
35
 
34
36
  from sentry_sdk.utils import ExcInfo
35
37
  from sentry_sdk._types import Event, EventProcessor
36
38
 
37
- WsgiResponseIter = TypeVar("WsgiResponseIter")
38
- WsgiResponseHeaders = TypeVar("WsgiResponseHeaders")
39
- WsgiExcInfo = TypeVar("WsgiExcInfo")
39
+ WsgiResponseIter = Iterable[bytes]
40
+ WsgiResponseHeaders = List[Tuple[str, str]]
40
41
 
41
42
  class StartResponse(Protocol):
42
- def __call__(self, status, response_headers, exc_info=None): # type: ignore
43
- # type: (str, WsgiResponseHeaders, Optional[WsgiExcInfo]) -> WsgiResponseIter
43
+ def __call__(
44
+ self,
45
+ status: str,
46
+ response_headers: WsgiResponseHeaders,
47
+ exc_info: Optional[ExcInfo] = None,
48
+ ) -> WsgiResponseIter:
44
49
  pass
45
50
 
46
51
 
@@ -58,13 +63,11 @@ ENVIRON_TO_ATTRIBUTE = {
58
63
  }
59
64
 
60
65
 
61
- def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
62
- # type: (str, str, str) -> str
66
+ def wsgi_decoding_dance(s: str, charset: str = "utf-8", errors: str = "replace") -> str:
63
67
  return s.encode("latin1").decode(charset, errors)
64
68
 
65
69
 
66
- def get_request_url(environ, use_x_forwarded_for=False):
67
- # type: (Dict[str, str], bool) -> str
70
+ def get_request_url(environ: Dict[str, str], use_x_forwarded_for: bool = False) -> str:
68
71
  """Return the absolute URL without query string for the given WSGI
69
72
  environment."""
70
73
  script_name = environ.get("SCRIPT_NAME", "").rstrip("/")
@@ -88,19 +91,19 @@ class SentryWsgiMiddleware:
88
91
 
89
92
  def __init__(
90
93
  self,
91
- app, # type: Callable[[Dict[str, str], Callable[..., Any]], Any]
92
- use_x_forwarded_for=False, # type: bool
93
- span_origin=None, # type: Optional[str]
94
- http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...]
95
- ):
96
- # type: (...) -> None
94
+ app: Callable[[Dict[str, str], Callable[..., Any]], Any],
95
+ use_x_forwarded_for: bool = False,
96
+ span_origin: Optional[str] = None,
97
+ http_methods_to_capture: Tuple[str, ...] = DEFAULT_HTTP_METHODS_TO_CAPTURE,
98
+ ) -> None:
97
99
  self.app = app
98
100
  self.use_x_forwarded_for = use_x_forwarded_for
99
101
  self.span_origin = span_origin
100
102
  self.http_methods_to_capture = http_methods_to_capture
101
103
 
102
- def __call__(self, environ, start_response):
103
- # type: (Dict[str, str], Callable[..., Any]) -> _ScopedResponse
104
+ def __call__(
105
+ self, environ: Dict[str, str], start_response: Callable[..., Any]
106
+ ) -> _ScopedResponse:
104
107
  if _wsgi_middleware_applied.get(False):
105
108
  return self.app(environ, start_response)
106
109
 
@@ -144,8 +147,12 @@ class SentryWsgiMiddleware:
144
147
 
145
148
  return _ScopedResponse(scope, response)
146
149
 
147
- def _run_original_app(self, environ, start_response, span):
148
- # type: (dict[str, str], StartResponse, Optional[Span]) -> Any
150
+ def _run_original_app(
151
+ self,
152
+ environ: dict[str, str],
153
+ start_response: StartResponse,
154
+ span: Optional[Span],
155
+ ) -> Any:
149
156
  try:
150
157
  return self.app(
151
158
  environ,
@@ -159,14 +166,13 @@ class SentryWsgiMiddleware:
159
166
  reraise(*_capture_exception())
160
167
 
161
168
 
162
- def _sentry_start_response( # type: ignore
163
- old_start_response, # type: StartResponse
164
- span, # type: Optional[Span]
165
- status, # type: str
166
- response_headers, # type: WsgiResponseHeaders
167
- exc_info=None, # type: Optional[WsgiExcInfo]
168
- ):
169
- # type: (...) -> WsgiResponseIter
169
+ def _sentry_start_response(
170
+ old_start_response: StartResponse,
171
+ span: Optional[Span],
172
+ status: str,
173
+ response_headers: WsgiResponseHeaders,
174
+ exc_info: Optional[ExcInfo] = None,
175
+ ) -> WsgiResponseIter:
170
176
  with capture_internal_exceptions():
171
177
  status_int = int(status.split(" ", 1)[0])
172
178
  if span is not None:
@@ -181,8 +187,7 @@ def _sentry_start_response( # type: ignore
181
187
  return old_start_response(status, response_headers, exc_info)
182
188
 
183
189
 
184
- def _get_environ(environ):
185
- # type: (Dict[str, str]) -> Iterator[Tuple[str, str]]
190
+ def _get_environ(environ: Dict[str, str]) -> Iterator[Tuple[str, str]]:
186
191
  """
187
192
  Returns our explicitly included environment variables we want to
188
193
  capture (server name, port and remote addr if pii is enabled).
@@ -198,8 +203,7 @@ def _get_environ(environ):
198
203
  yield key, environ[key]
199
204
 
200
205
 
201
- def get_client_ip(environ):
202
- # type: (Dict[str, str]) -> Optional[Any]
206
+ def get_client_ip(environ: Dict[str, str]) -> Optional[Any]:
203
207
  """
204
208
  Infer the user IP address from various headers. This cannot be used in
205
209
  security sensitive situations since the value may be forged from a client,
@@ -218,8 +222,7 @@ def get_client_ip(environ):
218
222
  return environ.get("REMOTE_ADDR")
219
223
 
220
224
 
221
- def _capture_exception():
222
- # type: () -> ExcInfo
225
+ def _capture_exception() -> ExcInfo:
223
226
  """
224
227
  Captures the current exception and sends it to Sentry.
225
228
  Returns the ExcInfo tuple to it can be reraised afterwards.
@@ -253,13 +256,11 @@ class _ScopedResponse:
253
256
 
254
257
  __slots__ = ("_response", "_scope")
255
258
 
256
- def __init__(self, scope, response):
257
- # type: (sentry_sdk.Scope, Iterator[bytes]) -> None
259
+ def __init__(self, scope: sentry_sdk.Scope, response: Iterator[bytes]) -> None:
258
260
  self._scope = scope
259
261
  self._response = response
260
262
 
261
- def __iter__(self):
262
- # type: () -> Iterator[bytes]
263
+ def __iter__(self) -> Iterator[bytes]:
263
264
  iterator = iter(self._response)
264
265
 
265
266
  while True:
@@ -273,8 +274,7 @@ class _ScopedResponse:
273
274
 
274
275
  yield chunk
275
276
 
276
- def close(self):
277
- # type: () -> None
277
+ def close(self) -> None:
278
278
  with sentry_sdk.use_isolation_scope(self._scope):
279
279
  try:
280
280
  self._response.close() # type: ignore
@@ -284,8 +284,9 @@ class _ScopedResponse:
284
284
  reraise(*_capture_exception())
285
285
 
286
286
 
287
- def _make_wsgi_event_processor(environ, use_x_forwarded_for):
288
- # type: (Dict[str, str], bool) -> EventProcessor
287
+ def _make_wsgi_event_processor(
288
+ environ: Dict[str, str], use_x_forwarded_for: bool
289
+ ) -> EventProcessor:
289
290
  # It's a bit unfortunate that we have to extract and parse the request data
290
291
  # from the environ so eagerly, but there are a few good reasons for this.
291
292
  #
@@ -305,8 +306,7 @@ def _make_wsgi_event_processor(environ, use_x_forwarded_for):
305
306
  env = dict(_get_environ(environ))
306
307
  headers = _filter_headers(dict(_get_headers(environ)))
307
308
 
308
- def event_processor(event, hint):
309
- # type: (Event, Dict[str, Any]) -> Event
309
+ def event_processor(event: Event, hint: Dict[str, Any]) -> Event:
310
310
  with capture_internal_exceptions():
311
311
  # if the code below fails halfway through we at least have some data
312
312
  request_info = event.setdefault("request", {})
@@ -327,8 +327,9 @@ def _make_wsgi_event_processor(environ, use_x_forwarded_for):
327
327
  return event_processor
328
328
 
329
329
 
330
- def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False):
331
- # type: (dict[str, str], bool) -> dict[str, str]
330
+ def _prepopulate_attributes(
331
+ wsgi_environ: dict[str, str], use_x_forwarded_for: bool = False
332
+ ) -> dict[str, str]:
332
333
  """Extract span attributes from the WSGI environment."""
333
334
  attributes = {}
334
335
 
sentry_sdk/logger.py CHANGED
@@ -1,24 +1,40 @@
1
1
  # NOTE: this is the logger sentry exposes to users, not some generic logger.
2
+ from __future__ import annotations
2
3
  import functools
3
4
  import time
4
- from typing import Any
5
5
 
6
- from sentry_sdk import get_client, get_current_scope
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Any
10
+
11
+ from sentry_sdk import get_client
7
12
  from sentry_sdk.utils import safe_repr
8
13
 
14
+ OTEL_RANGES = [
15
+ # ((severity level range), severity text)
16
+ # https://opentelemetry.io/docs/specs/otel/logs/data-model
17
+ ((1, 4), "trace"),
18
+ ((5, 8), "debug"),
19
+ ((9, 12), "info"),
20
+ ((13, 16), "warn"),
21
+ ((17, 20), "error"),
22
+ ((21, 24), "fatal"),
23
+ ]
24
+
9
25
 
10
- def _capture_log(severity_text, severity_number, template, **kwargs):
11
- # type: (str, int, str, **Any) -> None
26
+ def _capture_log(
27
+ severity_text: str, severity_number: int, template: str, **kwargs: Any
28
+ ) -> None:
12
29
  client = get_client()
13
- scope = get_current_scope()
14
30
 
15
- attrs = {
31
+ attrs: dict[str, str | bool | float | int] = {
16
32
  "sentry.message.template": template,
17
- } # type: dict[str, str | bool | float | int]
33
+ }
18
34
  if "attributes" in kwargs:
19
35
  attrs.update(kwargs.pop("attributes"))
20
36
  for k, v in kwargs.items():
21
- attrs[f"sentry.message.parameters.{k}"] = v
37
+ attrs[f"sentry.message.parameter.{k}"] = v
22
38
 
23
39
  attrs = {
24
40
  k: (
@@ -36,7 +52,6 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
36
52
 
37
53
  # noinspection PyProtectedMember
38
54
  client._capture_experimental_log(
39
- scope,
40
55
  {
41
56
  "severity_text": severity_text,
42
57
  "severity_number": severity_number,
@@ -51,6 +66,22 @@ def _capture_log(severity_text, severity_number, template, **kwargs):
51
66
  trace = functools.partial(_capture_log, "trace", 1)
52
67
  debug = functools.partial(_capture_log, "debug", 5)
53
68
  info = functools.partial(_capture_log, "info", 9)
54
- warning = functools.partial(_capture_log, "warning", 13)
69
+ warning = functools.partial(_capture_log, "warn", 13)
55
70
  error = functools.partial(_capture_log, "error", 17)
56
71
  fatal = functools.partial(_capture_log, "fatal", 21)
72
+
73
+
74
+ def _otel_severity_text(otel_severity_number: int) -> str:
75
+ for (lower, upper), severity in OTEL_RANGES:
76
+ if lower <= otel_severity_number <= upper:
77
+ return severity
78
+
79
+ return "default"
80
+
81
+
82
+ def _log_level_to_otel(level: int, mapping: dict[Any, int]) -> tuple[int, str]:
83
+ for py_level, otel_severity_number in sorted(mapping.items(), reverse=True):
84
+ if level >= py_level:
85
+ return otel_severity_number, _otel_severity_text(otel_severity_number)
86
+
87
+ return 0, "default"
sentry_sdk/monitor.py CHANGED
@@ -1,14 +1,15 @@
1
+ from __future__ import annotations
1
2
  import os
2
3
  import time
3
4
  from threading import Thread, Lock
4
5
 
5
- import sentry_sdk
6
6
  from sentry_sdk.utils import logger
7
7
 
8
8
  from typing import TYPE_CHECKING
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from typing import Optional
12
+ from sentry_sdk.transport import Transport
12
13
 
13
14
 
14
15
  MAX_DOWNSAMPLE_FACTOR = 10
@@ -23,21 +24,19 @@ class Monitor:
23
24
 
24
25
  name = "sentry.monitor"
25
26
 
26
- def __init__(self, transport, interval=10):
27
- # type: (sentry_sdk.transport.Transport, float) -> None
28
- self.transport = transport # type: sentry_sdk.transport.Transport
29
- self.interval = interval # type: float
27
+ def __init__(self, transport: Transport, interval: float = 10) -> None:
28
+ self.transport: Transport = transport
29
+ self.interval: float = interval
30
30
 
31
31
  self._healthy = True
32
- self._downsample_factor = 0 # type: int
32
+ self._downsample_factor: int = 0
33
33
 
34
- self._thread = None # type: Optional[Thread]
34
+ self._thread: Optional[Thread] = None
35
35
  self._thread_lock = Lock()
36
- self._thread_for_pid = None # type: Optional[int]
36
+ self._thread_for_pid: Optional[int] = None
37
37
  self._running = True
38
38
 
39
- def _ensure_running(self):
40
- # type: () -> None
39
+ def _ensure_running(self) -> None:
41
40
  """
42
41
  Check that the monitor has an active thread to run in, or create one if not.
43
42
 
@@ -52,8 +51,7 @@ class Monitor:
52
51
  if self._thread_for_pid == os.getpid() and self._thread is not None:
53
52
  return None
54
53
 
55
- def _thread():
56
- # type: (...) -> None
54
+ def _thread() -> None:
57
55
  while self._running:
58
56
  time.sleep(self.interval)
59
57
  if self._running:
@@ -74,13 +72,11 @@ class Monitor:
74
72
 
75
73
  return None
76
74
 
77
- def run(self):
78
- # type: () -> None
75
+ def run(self) -> None:
79
76
  self.check_health()
80
77
  self.set_downsample_factor()
81
78
 
82
- def set_downsample_factor(self):
83
- # type: () -> None
79
+ def set_downsample_factor(self) -> None:
84
80
  if self._healthy:
85
81
  if self._downsample_factor > 0:
86
82
  logger.debug(
@@ -95,8 +91,7 @@ class Monitor:
95
91
  self._downsample_factor,
96
92
  )
97
93
 
98
- def check_health(self):
99
- # type: () -> None
94
+ def check_health(self) -> None:
100
95
  """
101
96
  Perform the actual health checks,
102
97
  currently only checks if the transport is rate-limited.
@@ -104,21 +99,14 @@ class Monitor:
104
99
  """
105
100
  self._healthy = self.transport.is_healthy()
106
101
 
107
- def is_healthy(self):
108
- # type: () -> bool
102
+ def is_healthy(self) -> bool:
109
103
  self._ensure_running()
110
104
  return self._healthy
111
105
 
112
106
  @property
113
- def downsample_factor(self):
114
- # type: () -> int
107
+ def downsample_factor(self) -> int:
115
108
  self._ensure_running()
116
109
  return self._downsample_factor
117
110
 
118
- def kill(self):
119
- # type: () -> None
111
+ def kill(self) -> None:
120
112
  self._running = False
121
-
122
- def __del__(self):
123
- # type: () -> None
124
- self.kill()
@@ -1,5 +1,4 @@
1
1
  from opentelemetry.context import create_key
2
- from sentry_sdk.tracing_utils import Baggage
3
2
 
4
3
 
5
4
  # propagation keys
@@ -13,14 +12,22 @@ SENTRY_USE_CURRENT_SCOPE_KEY = create_key("sentry_use_current_scope")
13
12
  SENTRY_USE_ISOLATION_SCOPE_KEY = create_key("sentry_use_isolation_scope")
14
13
 
15
14
  # trace state keys
16
- TRACESTATE_SAMPLED_KEY = Baggage.SENTRY_PREFIX + "sampled"
17
- TRACESTATE_SAMPLE_RATE_KEY = Baggage.SENTRY_PREFIX + "sample_rate"
18
- TRACESTATE_SAMPLE_RAND_KEY = Baggage.SENTRY_PREFIX + "sample_rand"
15
+ SENTRY_PREFIX = "sentry-"
16
+ TRACESTATE_SAMPLED_KEY = SENTRY_PREFIX + "sampled"
17
+ TRACESTATE_SAMPLE_RATE_KEY = SENTRY_PREFIX + "sample_rate"
18
+ TRACESTATE_SAMPLE_RAND_KEY = SENTRY_PREFIX + "sample_rand"
19
19
 
20
20
  # misc
21
21
  OTEL_SENTRY_CONTEXT = "otel"
22
22
  SPAN_ORIGIN = "auto.otel"
23
23
 
24
+ # resource semconv attributes
25
+ # Not all of these are stable yet, so defining them here rather than importing.
26
+ # https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service
27
+ RESOURCE_SERVICE_NAME = "service.name"
28
+ RESOURCE_SERVICE_NAMESPACE = "service.namespace"
29
+ RESOURCE_SERVICE_VERSION = "service.version"
30
+
24
31
 
25
32
  class SentrySpanAttribute:
26
33
  DESCRIPTION = "sentry.description"
@@ -1,46 +1,50 @@
1
- from typing import cast, TYPE_CHECKING
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING
2
3
 
3
- from opentelemetry.trace import set_span_in_context
4
+ from opentelemetry.trace import get_current_span, set_span_in_context
5
+ from opentelemetry.trace.span import INVALID_SPAN
4
6
  from opentelemetry.context import Context, get_value, set_value
5
7
  from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext
6
8
 
7
9
  import sentry_sdk
10
+ from sentry_sdk.tracing import Span
8
11
  from sentry_sdk.opentelemetry.consts import (
9
12
  SENTRY_SCOPES_KEY,
10
13
  SENTRY_FORK_ISOLATION_SCOPE_KEY,
11
14
  SENTRY_USE_CURRENT_SCOPE_KEY,
12
15
  SENTRY_USE_ISOLATION_SCOPE_KEY,
13
16
  )
17
+ from sentry_sdk.opentelemetry.scope import PotelScope, validate_scopes
14
18
 
15
19
  if TYPE_CHECKING:
16
- from typing import Optional
17
20
  from contextvars import Token
18
- import sentry_sdk.opentelemetry.scope as scope
19
21
 
20
22
 
21
23
  class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext):
22
- def attach(self, context):
23
- # type: (Context) -> Token[Context]
24
- scopes = get_value(SENTRY_SCOPES_KEY, context)
24
+ def attach(self, context: Context) -> Token[Context]:
25
+ scopes = validate_scopes(get_value(SENTRY_SCOPES_KEY, context))
25
26
 
26
- should_fork_isolation_scope = context.pop(
27
- SENTRY_FORK_ISOLATION_SCOPE_KEY, False
27
+ should_fork_isolation_scope = bool(
28
+ context.pop(SENTRY_FORK_ISOLATION_SCOPE_KEY, False)
28
29
  )
29
- should_fork_isolation_scope = cast("bool", should_fork_isolation_scope)
30
30
 
31
31
  should_use_isolation_scope = context.pop(SENTRY_USE_ISOLATION_SCOPE_KEY, None)
32
- should_use_isolation_scope = cast(
33
- "Optional[scope.PotelScope]", should_use_isolation_scope
32
+ should_use_isolation_scope = (
33
+ should_use_isolation_scope
34
+ if isinstance(should_use_isolation_scope, PotelScope)
35
+ else None
34
36
  )
35
37
 
36
38
  should_use_current_scope = context.pop(SENTRY_USE_CURRENT_SCOPE_KEY, None)
37
- should_use_current_scope = cast(
38
- "Optional[scope.PotelScope]", should_use_current_scope
39
+ should_use_current_scope = (
40
+ should_use_current_scope
41
+ if isinstance(should_use_current_scope, PotelScope)
42
+ else None
39
43
  )
40
44
 
41
45
  if scopes:
42
- scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
43
- (current_scope, isolation_scope) = scopes
46
+ current_scope = scopes[0]
47
+ isolation_scope = scopes[1]
44
48
  else:
45
49
  current_scope = sentry_sdk.get_current_scope()
46
50
  isolation_scope = sentry_sdk.get_isolation_scope()
@@ -60,6 +64,12 @@ class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext):
60
64
  else:
61
65
  new_scope = current_scope.fork()
62
66
 
67
+ # carry forward a wrapped span reference since the otel context is always the
68
+ # source of truth for the active span
69
+ current_span = get_current_span(context)
70
+ if current_span != INVALID_SPAN:
71
+ new_scope._span = Span(otel_span=get_current_span(context))
72
+
63
73
  if should_use_isolation_scope:
64
74
  new_isolation_scope = should_use_isolation_scope
65
75
  elif should_fork_isolation_scope:
@@ -1,4 +1,4 @@
1
- from typing import cast
1
+ from __future__ import annotations
2
2
 
3
3
  from opentelemetry import trace
4
4
  from opentelemetry.context import (
@@ -20,7 +20,9 @@ from opentelemetry.trace import (
20
20
  SpanContext,
21
21
  TraceFlags,
22
22
  )
23
+ from opentelemetry.semconv.trace import SpanAttributes
23
24
 
25
+ import sentry_sdk
24
26
  from sentry_sdk.consts import (
25
27
  BAGGAGE_HEADER_NAME,
26
28
  SENTRY_TRACE_HEADER_NAME,
@@ -30,13 +32,17 @@ from sentry_sdk.opentelemetry.consts import (
30
32
  SENTRY_TRACE_KEY,
31
33
  SENTRY_SCOPES_KEY,
32
34
  )
33
- from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
35
+ from sentry_sdk.tracing_utils import (
36
+ Baggage,
37
+ extract_sentrytrace_data,
38
+ should_propagate_trace,
39
+ )
40
+ from sentry_sdk.opentelemetry.scope import validate_scopes
34
41
 
35
42
  from typing import TYPE_CHECKING
36
43
 
37
44
  if TYPE_CHECKING:
38
45
  from typing import Optional, Set
39
- import sentry_sdk.opentelemetry.scope as scope
40
46
 
41
47
 
42
48
  class SentryPropagator(TextMapPropagator):
@@ -44,8 +50,12 @@ class SentryPropagator(TextMapPropagator):
44
50
  Propagates tracing headers for Sentry's tracing system in a way OTel understands.
45
51
  """
46
52
 
47
- def extract(self, carrier, context=None, getter=default_getter):
48
- # type: (CarrierT, Optional[Context], Getter[CarrierT]) -> Context
53
+ def extract(
54
+ self,
55
+ carrier: CarrierT,
56
+ context: Optional[Context] = None,
57
+ getter: Getter[CarrierT] = default_getter,
58
+ ) -> Context:
49
59
  if context is None:
50
60
  context = get_current()
51
61
 
@@ -87,22 +97,29 @@ class SentryPropagator(TextMapPropagator):
87
97
  modified_context = trace.set_span_in_context(span, context)
88
98
  return modified_context
89
99
 
90
- def inject(self, carrier, context=None, setter=default_setter):
91
- # type: (CarrierT, Optional[Context], Setter[CarrierT]) -> None
92
- if context is None:
93
- context = get_current()
94
-
95
- scopes = get_value(SENTRY_SCOPES_KEY, context)
96
- if scopes:
97
- scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
98
- (current_scope, _) = scopes
99
-
100
- # TODO-neel-potel check trace_propagation_targets
101
- # TODO-neel-potel test propagator works with twp
102
- for key, value in current_scope.iter_trace_propagation_headers():
103
- setter.set(carrier, key, value)
100
+ def inject(
101
+ self,
102
+ carrier: CarrierT,
103
+ context: Optional[Context] = None,
104
+ setter: Setter[CarrierT] = default_setter,
105
+ ) -> None:
106
+ scopes = validate_scopes(get_value(SENTRY_SCOPES_KEY, context))
107
+ if not scopes:
108
+ return
109
+
110
+ (current_scope, _) = scopes
111
+
112
+ span = current_scope.span
113
+ if span:
114
+ span_url = span.get_attribute(SpanAttributes.HTTP_URL)
115
+ if span_url and not should_propagate_trace(
116
+ sentry_sdk.get_client(), span_url
117
+ ):
118
+ return
119
+
120
+ for key, value in current_scope.iter_trace_propagation_headers():
121
+ setter.set(carrier, key, value)
104
122
 
105
123
  @property
106
- def fields(self):
107
- # type: () -> Set[str]
124
+ def fields(self) -> Set[str]:
108
125
  return {SENTRY_TRACE_HEADER_NAME, BAGGAGE_HEADER_NAME}