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.
- sentry_sdk/__init__.py +2 -0
- sentry_sdk/_compat.py +5 -12
- sentry_sdk/_init_implementation.py +7 -7
- sentry_sdk/_log_batcher.py +17 -29
- sentry_sdk/_lru_cache.py +7 -9
- sentry_sdk/_queue.py +2 -4
- sentry_sdk/_types.py +11 -18
- sentry_sdk/_werkzeug.py +5 -7
- sentry_sdk/ai/monitoring.py +44 -31
- sentry_sdk/ai/utils.py +3 -4
- sentry_sdk/api.py +75 -87
- sentry_sdk/attachments.py +10 -12
- sentry_sdk/client.py +137 -155
- sentry_sdk/consts.py +430 -174
- sentry_sdk/crons/api.py +16 -17
- sentry_sdk/crons/decorator.py +25 -27
- sentry_sdk/debug.py +4 -6
- sentry_sdk/envelope.py +46 -112
- sentry_sdk/feature_flags.py +9 -15
- sentry_sdk/integrations/__init__.py +24 -19
- sentry_sdk/integrations/_asgi_common.py +15 -18
- sentry_sdk/integrations/_wsgi_common.py +22 -33
- sentry_sdk/integrations/aiohttp.py +32 -30
- sentry_sdk/integrations/anthropic.py +42 -37
- sentry_sdk/integrations/argv.py +3 -4
- sentry_sdk/integrations/ariadne.py +16 -18
- sentry_sdk/integrations/arq.py +21 -29
- sentry_sdk/integrations/asgi.py +63 -37
- sentry_sdk/integrations/asyncio.py +14 -16
- sentry_sdk/integrations/atexit.py +6 -10
- sentry_sdk/integrations/aws_lambda.py +26 -36
- sentry_sdk/integrations/beam.py +10 -18
- sentry_sdk/integrations/boto3.py +18 -16
- sentry_sdk/integrations/bottle.py +25 -34
- sentry_sdk/integrations/celery/__init__.py +41 -61
- sentry_sdk/integrations/celery/beat.py +23 -27
- sentry_sdk/integrations/celery/utils.py +15 -17
- sentry_sdk/integrations/chalice.py +8 -10
- sentry_sdk/integrations/clickhouse_driver.py +21 -31
- sentry_sdk/integrations/cloud_resource_context.py +9 -16
- sentry_sdk/integrations/cohere.py +27 -33
- sentry_sdk/integrations/dedupe.py +5 -8
- sentry_sdk/integrations/django/__init__.py +57 -72
- sentry_sdk/integrations/django/asgi.py +26 -34
- sentry_sdk/integrations/django/caching.py +23 -19
- sentry_sdk/integrations/django/middleware.py +17 -20
- sentry_sdk/integrations/django/signals_handlers.py +11 -10
- sentry_sdk/integrations/django/templates.py +19 -16
- sentry_sdk/integrations/django/transactions.py +16 -11
- sentry_sdk/integrations/django/views.py +6 -10
- sentry_sdk/integrations/dramatiq.py +21 -21
- sentry_sdk/integrations/excepthook.py +10 -10
- sentry_sdk/integrations/executing.py +3 -4
- sentry_sdk/integrations/falcon.py +27 -42
- sentry_sdk/integrations/fastapi.py +13 -16
- sentry_sdk/integrations/flask.py +31 -38
- sentry_sdk/integrations/gcp.py +13 -16
- sentry_sdk/integrations/gnu_backtrace.py +4 -6
- sentry_sdk/integrations/gql.py +16 -17
- sentry_sdk/integrations/graphene.py +13 -12
- sentry_sdk/integrations/grpc/__init__.py +19 -1
- sentry_sdk/integrations/grpc/aio/server.py +15 -14
- sentry_sdk/integrations/grpc/client.py +19 -9
- sentry_sdk/integrations/grpc/consts.py +2 -0
- sentry_sdk/integrations/grpc/server.py +12 -8
- sentry_sdk/integrations/httpx.py +9 -12
- sentry_sdk/integrations/huey.py +13 -20
- sentry_sdk/integrations/huggingface_hub.py +18 -18
- sentry_sdk/integrations/langchain.py +203 -113
- sentry_sdk/integrations/launchdarkly.py +13 -10
- sentry_sdk/integrations/litestar.py +37 -35
- sentry_sdk/integrations/logging.py +52 -65
- sentry_sdk/integrations/loguru.py +127 -57
- sentry_sdk/integrations/modules.py +3 -4
- sentry_sdk/integrations/openai.py +100 -88
- sentry_sdk/integrations/openai_agents/__init__.py +49 -0
- sentry_sdk/integrations/openai_agents/consts.py +1 -0
- sentry_sdk/integrations/openai_agents/patches/__init__.py +4 -0
- sentry_sdk/integrations/openai_agents/patches/agent_run.py +152 -0
- sentry_sdk/integrations/openai_agents/patches/models.py +52 -0
- sentry_sdk/integrations/openai_agents/patches/runner.py +42 -0
- sentry_sdk/integrations/openai_agents/patches/tools.py +84 -0
- sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -0
- sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +20 -0
- sentry_sdk/integrations/openai_agents/spans/ai_client.py +46 -0
- sentry_sdk/integrations/openai_agents/spans/execute_tool.py +47 -0
- sentry_sdk/integrations/openai_agents/spans/handoff.py +24 -0
- sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +41 -0
- sentry_sdk/integrations/openai_agents/utils.py +201 -0
- sentry_sdk/integrations/openfeature.py +11 -6
- sentry_sdk/integrations/pure_eval.py +6 -10
- sentry_sdk/integrations/pymongo.py +13 -17
- sentry_sdk/integrations/pyramid.py +31 -36
- sentry_sdk/integrations/quart.py +23 -28
- sentry_sdk/integrations/ray.py +73 -64
- sentry_sdk/integrations/redis/__init__.py +7 -4
- sentry_sdk/integrations/redis/_async_common.py +25 -12
- sentry_sdk/integrations/redis/_sync_common.py +19 -13
- sentry_sdk/integrations/redis/modules/caches.py +17 -8
- sentry_sdk/integrations/redis/modules/queries.py +9 -8
- sentry_sdk/integrations/redis/rb.py +3 -2
- sentry_sdk/integrations/redis/redis.py +4 -4
- sentry_sdk/integrations/redis/redis_cluster.py +21 -13
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +3 -2
- sentry_sdk/integrations/redis/utils.py +23 -24
- sentry_sdk/integrations/rq.py +13 -16
- sentry_sdk/integrations/rust_tracing.py +9 -6
- sentry_sdk/integrations/sanic.py +34 -46
- sentry_sdk/integrations/serverless.py +22 -27
- sentry_sdk/integrations/socket.py +27 -15
- sentry_sdk/integrations/spark/__init__.py +1 -0
- sentry_sdk/integrations/spark/spark_driver.py +45 -83
- sentry_sdk/integrations/spark/spark_worker.py +7 -11
- sentry_sdk/integrations/sqlalchemy.py +22 -19
- sentry_sdk/integrations/starlette.py +86 -90
- sentry_sdk/integrations/starlite.py +28 -34
- sentry_sdk/integrations/statsig.py +5 -4
- sentry_sdk/integrations/stdlib.py +28 -24
- sentry_sdk/integrations/strawberry.py +62 -49
- sentry_sdk/integrations/sys_exit.py +7 -11
- sentry_sdk/integrations/threading.py +12 -14
- sentry_sdk/integrations/tornado.py +28 -32
- sentry_sdk/integrations/trytond.py +4 -3
- sentry_sdk/integrations/typer.py +8 -6
- sentry_sdk/integrations/unleash.py +5 -4
- sentry_sdk/integrations/wsgi.py +47 -46
- sentry_sdk/logger.py +41 -10
- sentry_sdk/monitor.py +16 -28
- sentry_sdk/opentelemetry/consts.py +11 -4
- sentry_sdk/opentelemetry/contextvars_context.py +26 -16
- sentry_sdk/opentelemetry/propagator.py +38 -21
- sentry_sdk/opentelemetry/sampler.py +51 -34
- sentry_sdk/opentelemetry/scope.py +36 -37
- sentry_sdk/opentelemetry/span_processor.py +48 -58
- sentry_sdk/opentelemetry/tracing.py +58 -14
- sentry_sdk/opentelemetry/utils.py +186 -194
- sentry_sdk/profiler/continuous_profiler.py +108 -97
- sentry_sdk/profiler/transaction_profiler.py +70 -97
- sentry_sdk/profiler/utils.py +11 -15
- sentry_sdk/scope.py +251 -273
- sentry_sdk/scrubber.py +22 -26
- sentry_sdk/serializer.py +40 -54
- sentry_sdk/session.py +44 -61
- sentry_sdk/sessions.py +35 -49
- sentry_sdk/spotlight.py +15 -21
- sentry_sdk/tracing.py +121 -187
- sentry_sdk/tracing_utils.py +104 -122
- sentry_sdk/transport.py +131 -157
- sentry_sdk/utils.py +232 -309
- sentry_sdk/worker.py +16 -28
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/METADATA +3 -3
- sentry_sdk-3.0.0a3.dist-info/RECORD +168 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/RECORD +0 -154
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/entry_points.txt +0 -0
- {sentry_sdk-3.0.0a1.dist-info → sentry_sdk-3.0.0a3.dist-info}/licenses/LICENSE +0 -0
- {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(
|
|
24
|
-
|
|
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
|
sentry_sdk/integrations/wsgi.py
CHANGED
|
@@ -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 =
|
|
38
|
-
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__(
|
|
43
|
-
|
|
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
|
|
92
|
-
use_x_forwarded_for=False,
|
|
93
|
-
span_origin
|
|
94
|
-
http_methods_to_capture
|
|
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__(
|
|
103
|
-
|
|
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(
|
|
148
|
-
|
|
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(
|
|
163
|
-
old_start_response
|
|
164
|
-
span
|
|
165
|
-
status
|
|
166
|
-
response_headers
|
|
167
|
-
exc_info
|
|
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(
|
|
288
|
-
|
|
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(
|
|
331
|
-
|
|
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
|
|
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(
|
|
11
|
-
|
|
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
|
-
}
|
|
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.
|
|
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, "
|
|
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
|
-
|
|
28
|
-
self.
|
|
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
|
|
32
|
+
self._downsample_factor: int = 0
|
|
33
33
|
|
|
34
|
-
self._thread
|
|
34
|
+
self._thread: Optional[Thread] = None
|
|
35
35
|
self._thread_lock = Lock()
|
|
36
|
-
self._thread_for_pid
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
33
|
-
|
|
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 =
|
|
38
|
-
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
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
|
|
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(
|
|
48
|
-
|
|
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(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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}
|