sentry-sdk 2.26.1__py2.py3-none-any.whl → 3.0.0a1__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 +4 -8
- sentry_sdk/_compat.py +0 -1
- sentry_sdk/_init_implementation.py +6 -44
- sentry_sdk/_log_batcher.py +47 -28
- sentry_sdk/_types.py +8 -64
- sentry_sdk/ai/monitoring.py +14 -10
- sentry_sdk/ai/utils.py +1 -1
- sentry_sdk/api.py +69 -163
- sentry_sdk/client.py +25 -72
- sentry_sdk/consts.py +42 -23
- sentry_sdk/debug.py +0 -10
- sentry_sdk/envelope.py +2 -10
- sentry_sdk/feature_flags.py +5 -1
- sentry_sdk/integrations/__init__.py +5 -2
- sentry_sdk/integrations/_asgi_common.py +3 -3
- sentry_sdk/integrations/_wsgi_common.py +11 -40
- sentry_sdk/integrations/aiohttp.py +104 -57
- sentry_sdk/integrations/anthropic.py +10 -7
- sentry_sdk/integrations/arq.py +24 -13
- sentry_sdk/integrations/asgi.py +103 -83
- sentry_sdk/integrations/asyncio.py +1 -0
- sentry_sdk/integrations/asyncpg.py +45 -30
- sentry_sdk/integrations/aws_lambda.py +109 -92
- sentry_sdk/integrations/boto3.py +38 -9
- sentry_sdk/integrations/bottle.py +1 -1
- sentry_sdk/integrations/celery/__init__.py +48 -38
- sentry_sdk/integrations/clickhouse_driver.py +59 -28
- sentry_sdk/integrations/cohere.py +2 -0
- sentry_sdk/integrations/django/__init__.py +25 -46
- sentry_sdk/integrations/django/asgi.py +6 -2
- sentry_sdk/integrations/django/caching.py +13 -22
- sentry_sdk/integrations/django/middleware.py +1 -0
- sentry_sdk/integrations/django/signals_handlers.py +3 -1
- sentry_sdk/integrations/django/templates.py +8 -12
- sentry_sdk/integrations/django/transactions.py +1 -6
- sentry_sdk/integrations/django/views.py +5 -2
- sentry_sdk/integrations/falcon.py +7 -25
- sentry_sdk/integrations/fastapi.py +3 -3
- sentry_sdk/integrations/flask.py +1 -1
- sentry_sdk/integrations/gcp.py +63 -38
- sentry_sdk/integrations/graphene.py +6 -13
- sentry_sdk/integrations/grpc/aio/client.py +14 -8
- sentry_sdk/integrations/grpc/aio/server.py +19 -21
- sentry_sdk/integrations/grpc/client.py +8 -6
- sentry_sdk/integrations/grpc/server.py +12 -14
- sentry_sdk/integrations/httpx.py +47 -12
- sentry_sdk/integrations/huey.py +26 -22
- sentry_sdk/integrations/huggingface_hub.py +1 -0
- sentry_sdk/integrations/langchain.py +22 -15
- sentry_sdk/integrations/launchdarkly.py +3 -3
- sentry_sdk/integrations/litestar.py +4 -2
- sentry_sdk/integrations/logging.py +12 -3
- sentry_sdk/integrations/openai.py +2 -0
- sentry_sdk/integrations/openfeature.py +3 -5
- sentry_sdk/integrations/pymongo.py +18 -25
- sentry_sdk/integrations/pyramid.py +1 -1
- sentry_sdk/integrations/quart.py +3 -3
- sentry_sdk/integrations/ray.py +23 -17
- sentry_sdk/integrations/redis/_async_common.py +30 -18
- sentry_sdk/integrations/redis/_sync_common.py +28 -18
- sentry_sdk/integrations/redis/modules/caches.py +13 -10
- sentry_sdk/integrations/redis/modules/queries.py +14 -11
- sentry_sdk/integrations/redis/rb.py +4 -4
- sentry_sdk/integrations/redis/redis.py +6 -6
- sentry_sdk/integrations/redis/redis_cluster.py +18 -16
- sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +4 -4
- sentry_sdk/integrations/redis/utils.py +63 -19
- sentry_sdk/integrations/rq.py +68 -23
- sentry_sdk/integrations/rust_tracing.py +28 -43
- sentry_sdk/integrations/sanic.py +23 -13
- sentry_sdk/integrations/socket.py +9 -5
- sentry_sdk/integrations/sqlalchemy.py +8 -8
- sentry_sdk/integrations/starlette.py +11 -31
- sentry_sdk/integrations/starlite.py +4 -2
- sentry_sdk/integrations/stdlib.py +56 -9
- sentry_sdk/integrations/strawberry.py +40 -59
- sentry_sdk/integrations/threading.py +10 -26
- sentry_sdk/integrations/tornado.py +57 -18
- sentry_sdk/integrations/trytond.py +4 -1
- sentry_sdk/integrations/unleash.py +2 -3
- sentry_sdk/integrations/wsgi.py +84 -38
- sentry_sdk/opentelemetry/__init__.py +9 -0
- sentry_sdk/opentelemetry/consts.py +33 -0
- sentry_sdk/opentelemetry/contextvars_context.py +73 -0
- sentry_sdk/{integrations/opentelemetry → opentelemetry}/propagator.py +19 -28
- sentry_sdk/opentelemetry/sampler.py +326 -0
- sentry_sdk/opentelemetry/scope.py +218 -0
- sentry_sdk/opentelemetry/span_processor.py +329 -0
- sentry_sdk/opentelemetry/tracing.py +35 -0
- sentry_sdk/opentelemetry/utils.py +476 -0
- sentry_sdk/profiler/__init__.py +0 -40
- sentry_sdk/profiler/continuous_profiler.py +1 -30
- sentry_sdk/profiler/transaction_profiler.py +5 -56
- sentry_sdk/scope.py +107 -351
- sentry_sdk/sessions.py +0 -87
- sentry_sdk/tracing.py +418 -1134
- sentry_sdk/tracing_utils.py +134 -169
- sentry_sdk/transport.py +4 -104
- sentry_sdk/types.py +26 -2
- sentry_sdk/utils.py +169 -152
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/METADATA +3 -5
- sentry_sdk-3.0.0a1.dist-info/RECORD +154 -0
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/WHEEL +1 -1
- sentry_sdk-3.0.0a1.dist-info/entry_points.txt +2 -0
- sentry_sdk/hub.py +0 -739
- sentry_sdk/integrations/opentelemetry/__init__.py +0 -7
- sentry_sdk/integrations/opentelemetry/consts.py +0 -5
- sentry_sdk/integrations/opentelemetry/integration.py +0 -58
- sentry_sdk/integrations/opentelemetry/span_processor.py +0 -391
- sentry_sdk/metrics.py +0 -965
- sentry_sdk-2.26.1.dist-info/RECORD +0 -152
- sentry_sdk-2.26.1.dist-info/entry_points.txt +0 -2
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/licenses/LICENSE +0 -0
- {sentry_sdk-2.26.1.dist-info → sentry_sdk-3.0.0a1.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,6 @@ import contextlib
|
|
|
3
3
|
from inspect import iscoroutinefunction
|
|
4
4
|
|
|
5
5
|
import sentry_sdk
|
|
6
|
-
from sentry_sdk.api import continue_trace
|
|
7
6
|
from sentry_sdk.consts import OP
|
|
8
7
|
from sentry_sdk.scope import should_send_default_pii
|
|
9
8
|
from sentry_sdk.tracing import TransactionSource
|
|
@@ -20,13 +19,15 @@ from sentry_sdk.integrations._wsgi_common import (
|
|
|
20
19
|
RequestExtractor,
|
|
21
20
|
_filter_headers,
|
|
22
21
|
_is_json_content_type,
|
|
22
|
+
_request_headers_to_span_attributes,
|
|
23
23
|
)
|
|
24
24
|
from sentry_sdk.integrations.logging import ignore_logger
|
|
25
25
|
|
|
26
26
|
try:
|
|
27
27
|
from tornado import version_info as TORNADO_VERSION
|
|
28
|
-
from tornado.web import RequestHandler, HTTPError
|
|
29
28
|
from tornado.gen import coroutine
|
|
29
|
+
from tornado.httputil import HTTPServerRequest
|
|
30
|
+
from tornado.web import RequestHandler, HTTPError
|
|
30
31
|
except ImportError:
|
|
31
32
|
raise DidNotEnable("Tornado not installed")
|
|
32
33
|
|
|
@@ -42,6 +43,14 @@ if TYPE_CHECKING:
|
|
|
42
43
|
from sentry_sdk._types import Event, EventProcessor
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
REQUEST_PROPERTY_TO_ATTRIBUTE = {
|
|
47
|
+
"method": "http.request.method",
|
|
48
|
+
"path": "url.path",
|
|
49
|
+
"query": "url.query",
|
|
50
|
+
"protocol": "url.scheme",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
45
54
|
class TornadoIntegration(Integration):
|
|
46
55
|
identifier = "tornado"
|
|
47
56
|
origin = f"auto.http.{identifier}"
|
|
@@ -111,22 +120,19 @@ def _handle_request_impl(self):
|
|
|
111
120
|
processor = _make_event_processor(weak_handler)
|
|
112
121
|
scope.add_event_processor(processor)
|
|
113
122
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
transaction, custom_sampling_context={"tornado_request": self.request}
|
|
128
|
-
):
|
|
129
|
-
yield
|
|
123
|
+
with sentry_sdk.continue_trace(headers):
|
|
124
|
+
with sentry_sdk.start_span(
|
|
125
|
+
op=OP.HTTP_SERVER,
|
|
126
|
+
# Like with all other integrations, this is our
|
|
127
|
+
# fallback transaction in case there is no route.
|
|
128
|
+
# sentry_urldispatcher_resolve is responsible for
|
|
129
|
+
# setting a transaction name later.
|
|
130
|
+
name="generic Tornado request",
|
|
131
|
+
source=TransactionSource.ROUTE,
|
|
132
|
+
origin=TornadoIntegration.origin,
|
|
133
|
+
attributes=_prepopulate_attributes(self.request),
|
|
134
|
+
):
|
|
135
|
+
yield
|
|
130
136
|
|
|
131
137
|
|
|
132
138
|
@ensure_integration_enabled(TornadoIntegration)
|
|
@@ -218,3 +224,36 @@ class TornadoRequestExtractor(RequestExtractor):
|
|
|
218
224
|
def size_of_file(self, file):
|
|
219
225
|
# type: (Any) -> int
|
|
220
226
|
return len(file.body or ())
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _prepopulate_attributes(request):
|
|
230
|
+
# type: (HTTPServerRequest) -> dict[str, Any]
|
|
231
|
+
# https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest
|
|
232
|
+
attributes = {}
|
|
233
|
+
|
|
234
|
+
for prop, attr in REQUEST_PROPERTY_TO_ATTRIBUTE.items():
|
|
235
|
+
if getattr(request, prop, None) is not None:
|
|
236
|
+
attributes[attr] = getattr(request, prop)
|
|
237
|
+
|
|
238
|
+
if getattr(request, "version", None):
|
|
239
|
+
try:
|
|
240
|
+
proto, version = request.version.split("/")
|
|
241
|
+
attributes["network.protocol.name"] = proto
|
|
242
|
+
attributes["network.protocol.version"] = version
|
|
243
|
+
except ValueError:
|
|
244
|
+
attributes["network.protocol.name"] = request.version
|
|
245
|
+
|
|
246
|
+
if getattr(request, "host", None):
|
|
247
|
+
try:
|
|
248
|
+
address, port = request.host.split(":")
|
|
249
|
+
attributes["server.address"] = address
|
|
250
|
+
attributes["server.port"] = port
|
|
251
|
+
except ValueError:
|
|
252
|
+
attributes["server.address"] = request.host
|
|
253
|
+
|
|
254
|
+
with capture_internal_exceptions():
|
|
255
|
+
attributes["url.full"] = request.full_url()
|
|
256
|
+
|
|
257
|
+
attributes.update(_request_headers_to_span_attributes(request.headers))
|
|
258
|
+
|
|
259
|
+
return attributes
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import sentry_sdk
|
|
2
|
-
from sentry_sdk.integrations import Integration
|
|
2
|
+
from sentry_sdk.integrations import _check_minimum_version, Integration
|
|
3
3
|
from sentry_sdk.integrations.wsgi import SentryWsgiMiddleware
|
|
4
4
|
from sentry_sdk.utils import ensure_integration_enabled, event_from_exception
|
|
5
5
|
|
|
6
|
+
from trytond import __version__ as trytond_version # type: ignore
|
|
6
7
|
from trytond.exceptions import TrytonException # type: ignore
|
|
7
8
|
from trytond.wsgi import app # type: ignore
|
|
8
9
|
|
|
@@ -19,6 +20,8 @@ class TrytondWSGIIntegration(Integration):
|
|
|
19
20
|
|
|
20
21
|
@staticmethod
|
|
21
22
|
def setup_once(): # type: () -> None
|
|
23
|
+
_check_minimum_version(TrytondWSGIIntegration, trytond_version)
|
|
24
|
+
|
|
22
25
|
app.wsgi_app = SentryWsgiMiddleware(
|
|
23
26
|
app.wsgi_app,
|
|
24
27
|
span_origin=TrytondWSGIIntegration.origin,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from functools import wraps
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
from sentry_sdk.feature_flags import add_feature_flag
|
|
5
5
|
from sentry_sdk.integrations import Integration, DidNotEnable
|
|
6
6
|
|
|
7
7
|
try:
|
|
@@ -26,8 +26,7 @@ class UnleashIntegration(Integration):
|
|
|
26
26
|
|
|
27
27
|
# We have no way of knowing what type of unleash feature this is, so we have to treat
|
|
28
28
|
# it as a boolean / toggle feature.
|
|
29
|
-
|
|
30
|
-
flags.set(feature, enabled)
|
|
29
|
+
add_feature_flag(feature, enabled)
|
|
31
30
|
|
|
32
31
|
return enabled
|
|
33
32
|
|
sentry_sdk/integrations/wsgi.py
CHANGED
|
@@ -3,17 +3,15 @@ from functools import partial
|
|
|
3
3
|
|
|
4
4
|
import sentry_sdk
|
|
5
5
|
from sentry_sdk._werkzeug import get_host, _get_headers
|
|
6
|
-
from sentry_sdk.api import continue_trace
|
|
7
6
|
from sentry_sdk.consts import OP
|
|
8
7
|
from sentry_sdk.scope import should_send_default_pii
|
|
9
8
|
from sentry_sdk.integrations._wsgi_common import (
|
|
10
9
|
DEFAULT_HTTP_METHODS_TO_CAPTURE,
|
|
11
10
|
_filter_headers,
|
|
12
|
-
|
|
11
|
+
_request_headers_to_span_attributes,
|
|
13
12
|
)
|
|
14
13
|
from sentry_sdk.sessions import track_session
|
|
15
|
-
from sentry_sdk.
|
|
16
|
-
from sentry_sdk.tracing import Transaction, TransactionSource
|
|
14
|
+
from sentry_sdk.tracing import Span, TransactionSource
|
|
17
15
|
from sentry_sdk.utils import (
|
|
18
16
|
ContextVar,
|
|
19
17
|
capture_internal_exceptions,
|
|
@@ -48,6 +46,17 @@ if TYPE_CHECKING:
|
|
|
48
46
|
|
|
49
47
|
_wsgi_middleware_applied = ContextVar("sentry_wsgi_middleware_applied")
|
|
50
48
|
|
|
49
|
+
DEFAULT_TRANSACTION_NAME = "generic WSGI request"
|
|
50
|
+
|
|
51
|
+
ENVIRON_TO_ATTRIBUTE = {
|
|
52
|
+
"PATH_INFO": "url.path",
|
|
53
|
+
"QUERY_STRING": "url.query",
|
|
54
|
+
"REQUEST_METHOD": "http.request.method",
|
|
55
|
+
"SERVER_NAME": "server.address",
|
|
56
|
+
"SERVER_PORT": "server.port",
|
|
57
|
+
"wsgi.url_scheme": "url.scheme",
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
|
|
52
61
|
def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
|
|
53
62
|
# type: (str, str, str) -> str
|
|
@@ -81,7 +90,7 @@ class SentryWsgiMiddleware:
|
|
|
81
90
|
self,
|
|
82
91
|
app, # type: Callable[[Dict[str, str], Callable[..., Any]], Any]
|
|
83
92
|
use_x_forwarded_for=False, # type: bool
|
|
84
|
-
span_origin=
|
|
93
|
+
span_origin=None, # type: Optional[str]
|
|
85
94
|
http_methods_to_capture=DEFAULT_HTTP_METHODS_TO_CAPTURE, # type: Tuple[str, ...]
|
|
86
95
|
):
|
|
87
96
|
# type: (...) -> None
|
|
@@ -98,6 +107,10 @@ class SentryWsgiMiddleware:
|
|
|
98
107
|
_wsgi_middleware_applied.set(True)
|
|
99
108
|
try:
|
|
100
109
|
with sentry_sdk.isolation_scope() as scope:
|
|
110
|
+
scope.set_transaction_name(
|
|
111
|
+
DEFAULT_TRANSACTION_NAME, source=TransactionSource.ROUTE
|
|
112
|
+
)
|
|
113
|
+
|
|
101
114
|
with track_session(scope, session_mode="request"):
|
|
102
115
|
with capture_internal_exceptions():
|
|
103
116
|
scope.clear_breadcrumbs()
|
|
@@ -107,44 +120,48 @@ class SentryWsgiMiddleware:
|
|
|
107
120
|
environ, self.use_x_forwarded_for
|
|
108
121
|
)
|
|
109
122
|
)
|
|
110
|
-
|
|
111
123
|
method = environ.get("REQUEST_METHOD", "").upper()
|
|
112
|
-
|
|
113
|
-
if
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
with (
|
|
123
|
-
sentry_sdk.start_transaction(
|
|
124
|
-
transaction,
|
|
125
|
-
custom_sampling_context={"wsgi_environ": environ},
|
|
126
|
-
)
|
|
127
|
-
if transaction is not None
|
|
128
|
-
else nullcontext()
|
|
129
|
-
):
|
|
130
|
-
try:
|
|
131
|
-
response = self.app(
|
|
132
|
-
environ,
|
|
133
|
-
partial(
|
|
134
|
-
_sentry_start_response, start_response, transaction
|
|
124
|
+
should_trace = method in self.http_methods_to_capture
|
|
125
|
+
if should_trace:
|
|
126
|
+
with sentry_sdk.continue_trace(environ):
|
|
127
|
+
with sentry_sdk.start_span(
|
|
128
|
+
op=OP.HTTP_SERVER,
|
|
129
|
+
name=DEFAULT_TRANSACTION_NAME,
|
|
130
|
+
source=TransactionSource.ROUTE,
|
|
131
|
+
origin=self.span_origin,
|
|
132
|
+
attributes=_prepopulate_attributes(
|
|
133
|
+
environ, self.use_x_forwarded_for
|
|
135
134
|
),
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
) as span:
|
|
136
|
+
response = self._run_original_app(
|
|
137
|
+
environ, start_response, span
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
response = self._run_original_app(environ, start_response, None)
|
|
141
|
+
|
|
139
142
|
finally:
|
|
140
143
|
_wsgi_middleware_applied.set(False)
|
|
141
144
|
|
|
142
145
|
return _ScopedResponse(scope, response)
|
|
143
146
|
|
|
147
|
+
def _run_original_app(self, environ, start_response, span):
|
|
148
|
+
# type: (dict[str, str], StartResponse, Optional[Span]) -> Any
|
|
149
|
+
try:
|
|
150
|
+
return self.app(
|
|
151
|
+
environ,
|
|
152
|
+
partial(
|
|
153
|
+
_sentry_start_response,
|
|
154
|
+
start_response,
|
|
155
|
+
span,
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
except BaseException:
|
|
159
|
+
reraise(*_capture_exception())
|
|
160
|
+
|
|
144
161
|
|
|
145
162
|
def _sentry_start_response( # type: ignore
|
|
146
163
|
old_start_response, # type: StartResponse
|
|
147
|
-
|
|
164
|
+
span, # type: Optional[Span]
|
|
148
165
|
status, # type: str
|
|
149
166
|
response_headers, # type: WsgiResponseHeaders
|
|
150
167
|
exc_info=None, # type: Optional[WsgiExcInfo]
|
|
@@ -152,8 +169,8 @@ def _sentry_start_response( # type: ignore
|
|
|
152
169
|
# type: (...) -> WsgiResponseIter
|
|
153
170
|
with capture_internal_exceptions():
|
|
154
171
|
status_int = int(status.split(" ", 1)[0])
|
|
155
|
-
if
|
|
156
|
-
|
|
172
|
+
if span is not None:
|
|
173
|
+
span.set_http_status(status_int)
|
|
157
174
|
|
|
158
175
|
if exc_info is None:
|
|
159
176
|
# The Django Rest Framework WSGI test client, and likely other
|
|
@@ -237,7 +254,7 @@ class _ScopedResponse:
|
|
|
237
254
|
__slots__ = ("_response", "_scope")
|
|
238
255
|
|
|
239
256
|
def __init__(self, scope, response):
|
|
240
|
-
# type: (sentry_sdk.
|
|
257
|
+
# type: (sentry_sdk.Scope, Iterator[bytes]) -> None
|
|
241
258
|
self._scope = scope
|
|
242
259
|
self._response = response
|
|
243
260
|
|
|
@@ -246,7 +263,7 @@ class _ScopedResponse:
|
|
|
246
263
|
iterator = iter(self._response)
|
|
247
264
|
|
|
248
265
|
while True:
|
|
249
|
-
with use_isolation_scope(self._scope):
|
|
266
|
+
with sentry_sdk.use_isolation_scope(self._scope):
|
|
250
267
|
try:
|
|
251
268
|
chunk = next(iterator)
|
|
252
269
|
except StopIteration:
|
|
@@ -258,7 +275,7 @@ class _ScopedResponse:
|
|
|
258
275
|
|
|
259
276
|
def close(self):
|
|
260
277
|
# type: () -> None
|
|
261
|
-
with use_isolation_scope(self._scope):
|
|
278
|
+
with sentry_sdk.use_isolation_scope(self._scope):
|
|
262
279
|
try:
|
|
263
280
|
self._response.close() # type: ignore
|
|
264
281
|
except AttributeError:
|
|
@@ -308,3 +325,32 @@ def _make_wsgi_event_processor(environ, use_x_forwarded_for):
|
|
|
308
325
|
return event
|
|
309
326
|
|
|
310
327
|
return event_processor
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _prepopulate_attributes(wsgi_environ, use_x_forwarded_for=False):
|
|
331
|
+
# type: (dict[str, str], bool) -> dict[str, str]
|
|
332
|
+
"""Extract span attributes from the WSGI environment."""
|
|
333
|
+
attributes = {}
|
|
334
|
+
|
|
335
|
+
for property, attr in ENVIRON_TO_ATTRIBUTE.items():
|
|
336
|
+
if wsgi_environ.get(property) is not None:
|
|
337
|
+
attributes[attr] = wsgi_environ[property]
|
|
338
|
+
|
|
339
|
+
if wsgi_environ.get("SERVER_PROTOCOL") is not None:
|
|
340
|
+
try:
|
|
341
|
+
proto, version = wsgi_environ["SERVER_PROTOCOL"].split("/")
|
|
342
|
+
attributes["network.protocol.name"] = proto
|
|
343
|
+
attributes["network.protocol.version"] = version
|
|
344
|
+
except Exception:
|
|
345
|
+
attributes["network.protocol.name"] = wsgi_environ["SERVER_PROTOCOL"]
|
|
346
|
+
|
|
347
|
+
with capture_internal_exceptions():
|
|
348
|
+
url = get_request_url(wsgi_environ, use_x_forwarded_for)
|
|
349
|
+
query = wsgi_environ.get("QUERY_STRING")
|
|
350
|
+
attributes["url.full"] = f"{url}?{query}"
|
|
351
|
+
|
|
352
|
+
attributes.update(
|
|
353
|
+
_request_headers_to_span_attributes(dict(_get_headers(wsgi_environ)))
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return attributes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from sentry_sdk.opentelemetry.propagator import SentryPropagator
|
|
2
|
+
from sentry_sdk.opentelemetry.sampler import SentrySampler
|
|
3
|
+
from sentry_sdk.opentelemetry.span_processor import SentrySpanProcessor
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"SentryPropagator",
|
|
7
|
+
"SentrySampler",
|
|
8
|
+
"SentrySpanProcessor",
|
|
9
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from opentelemetry.context import create_key
|
|
2
|
+
from sentry_sdk.tracing_utils import Baggage
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# propagation keys
|
|
6
|
+
SENTRY_TRACE_KEY = create_key("sentry-trace")
|
|
7
|
+
SENTRY_BAGGAGE_KEY = create_key("sentry-baggage")
|
|
8
|
+
|
|
9
|
+
# scope management keys
|
|
10
|
+
SENTRY_SCOPES_KEY = create_key("sentry_scopes")
|
|
11
|
+
SENTRY_FORK_ISOLATION_SCOPE_KEY = create_key("sentry_fork_isolation_scope")
|
|
12
|
+
SENTRY_USE_CURRENT_SCOPE_KEY = create_key("sentry_use_current_scope")
|
|
13
|
+
SENTRY_USE_ISOLATION_SCOPE_KEY = create_key("sentry_use_isolation_scope")
|
|
14
|
+
|
|
15
|
+
# 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"
|
|
19
|
+
|
|
20
|
+
# misc
|
|
21
|
+
OTEL_SENTRY_CONTEXT = "otel"
|
|
22
|
+
SPAN_ORIGIN = "auto.otel"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SentrySpanAttribute:
|
|
26
|
+
DESCRIPTION = "sentry.description"
|
|
27
|
+
OP = "sentry.op"
|
|
28
|
+
ORIGIN = "sentry.origin"
|
|
29
|
+
TAG = "sentry.tag"
|
|
30
|
+
NAME = "sentry.name"
|
|
31
|
+
SOURCE = "sentry.source"
|
|
32
|
+
CONTEXT = "sentry.context"
|
|
33
|
+
CUSTOM_SAMPLED = "sentry.custom_sampled" # used for saving start_span(sampled=X)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import cast, TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from opentelemetry.trace import set_span_in_context
|
|
4
|
+
from opentelemetry.context import Context, get_value, set_value
|
|
5
|
+
from opentelemetry.context.contextvars_context import ContextVarsRuntimeContext
|
|
6
|
+
|
|
7
|
+
import sentry_sdk
|
|
8
|
+
from sentry_sdk.opentelemetry.consts import (
|
|
9
|
+
SENTRY_SCOPES_KEY,
|
|
10
|
+
SENTRY_FORK_ISOLATION_SCOPE_KEY,
|
|
11
|
+
SENTRY_USE_CURRENT_SCOPE_KEY,
|
|
12
|
+
SENTRY_USE_ISOLATION_SCOPE_KEY,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from typing import Optional
|
|
17
|
+
from contextvars import Token
|
|
18
|
+
import sentry_sdk.opentelemetry.scope as scope
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SentryContextVarsRuntimeContext(ContextVarsRuntimeContext):
|
|
22
|
+
def attach(self, context):
|
|
23
|
+
# type: (Context) -> Token[Context]
|
|
24
|
+
scopes = get_value(SENTRY_SCOPES_KEY, context)
|
|
25
|
+
|
|
26
|
+
should_fork_isolation_scope = context.pop(
|
|
27
|
+
SENTRY_FORK_ISOLATION_SCOPE_KEY, False
|
|
28
|
+
)
|
|
29
|
+
should_fork_isolation_scope = cast("bool", should_fork_isolation_scope)
|
|
30
|
+
|
|
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
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
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
|
+
)
|
|
40
|
+
|
|
41
|
+
if scopes:
|
|
42
|
+
scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
|
|
43
|
+
(current_scope, isolation_scope) = scopes
|
|
44
|
+
else:
|
|
45
|
+
current_scope = sentry_sdk.get_current_scope()
|
|
46
|
+
isolation_scope = sentry_sdk.get_isolation_scope()
|
|
47
|
+
|
|
48
|
+
new_context = context
|
|
49
|
+
|
|
50
|
+
if should_use_current_scope:
|
|
51
|
+
new_scope = should_use_current_scope
|
|
52
|
+
|
|
53
|
+
# the main case where we use use_scope is for
|
|
54
|
+
# scope propagation in the ThreadingIntegration
|
|
55
|
+
# so we need to carry forward the span reference explicitly too
|
|
56
|
+
span = should_use_current_scope.span
|
|
57
|
+
if span:
|
|
58
|
+
new_context = set_span_in_context(span._otel_span, new_context)
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
new_scope = current_scope.fork()
|
|
62
|
+
|
|
63
|
+
if should_use_isolation_scope:
|
|
64
|
+
new_isolation_scope = should_use_isolation_scope
|
|
65
|
+
elif should_fork_isolation_scope:
|
|
66
|
+
new_isolation_scope = isolation_scope.fork()
|
|
67
|
+
else:
|
|
68
|
+
new_isolation_scope = isolation_scope
|
|
69
|
+
|
|
70
|
+
new_scopes = (new_scope, new_isolation_scope)
|
|
71
|
+
|
|
72
|
+
new_context = set_value(SENTRY_SCOPES_KEY, new_scopes, new_context)
|
|
73
|
+
return super().attach(new_context)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from typing import cast
|
|
2
|
+
|
|
1
3
|
from opentelemetry import trace
|
|
2
4
|
from opentelemetry.context import (
|
|
3
5
|
Context,
|
|
4
6
|
get_current,
|
|
7
|
+
get_value,
|
|
5
8
|
set_value,
|
|
6
9
|
)
|
|
7
10
|
from opentelemetry.propagators.textmap import (
|
|
@@ -18,23 +21,22 @@ from opentelemetry.trace import (
|
|
|
18
21
|
TraceFlags,
|
|
19
22
|
)
|
|
20
23
|
|
|
21
|
-
from sentry_sdk.
|
|
22
|
-
SENTRY_BAGGAGE_KEY,
|
|
23
|
-
SENTRY_TRACE_KEY,
|
|
24
|
-
)
|
|
25
|
-
from sentry_sdk.integrations.opentelemetry.span_processor import (
|
|
26
|
-
SentrySpanProcessor,
|
|
27
|
-
)
|
|
28
|
-
from sentry_sdk.tracing import (
|
|
24
|
+
from sentry_sdk.consts import (
|
|
29
25
|
BAGGAGE_HEADER_NAME,
|
|
30
26
|
SENTRY_TRACE_HEADER_NAME,
|
|
31
27
|
)
|
|
28
|
+
from sentry_sdk.opentelemetry.consts import (
|
|
29
|
+
SENTRY_BAGGAGE_KEY,
|
|
30
|
+
SENTRY_TRACE_KEY,
|
|
31
|
+
SENTRY_SCOPES_KEY,
|
|
32
|
+
)
|
|
32
33
|
from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
|
|
33
34
|
|
|
34
35
|
from typing import TYPE_CHECKING
|
|
35
36
|
|
|
36
37
|
if TYPE_CHECKING:
|
|
37
38
|
from typing import Optional, Set
|
|
39
|
+
import sentry_sdk.opentelemetry.scope as scope
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
class SentryPropagator(TextMapPropagator):
|
|
@@ -47,6 +49,7 @@ class SentryPropagator(TextMapPropagator):
|
|
|
47
49
|
if context is None:
|
|
48
50
|
context = get_current()
|
|
49
51
|
|
|
52
|
+
# TODO-neel-potel cleanup with continue_trace / isolation_scope
|
|
50
53
|
sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME)
|
|
51
54
|
if not sentry_trace:
|
|
52
55
|
return context
|
|
@@ -89,27 +92,15 @@ class SentryPropagator(TextMapPropagator):
|
|
|
89
92
|
if context is None:
|
|
90
93
|
context = get_current()
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
|
|
98
|
-
span_id = trace.format_span_id(current_span_context.span_id)
|
|
99
|
-
|
|
100
|
-
span_map = SentrySpanProcessor().otel_span_map
|
|
101
|
-
sentry_span = span_map.get(span_id, None)
|
|
102
|
-
if not sentry_span:
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_span.to_traceparent())
|
|
95
|
+
scopes = get_value(SENTRY_SCOPES_KEY, context)
|
|
96
|
+
if scopes:
|
|
97
|
+
scopes = cast("tuple[scope.PotelScope, scope.PotelScope]", scopes)
|
|
98
|
+
(current_scope, _) = scopes
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if baggage_data:
|
|
112
|
-
setter.set(carrier, BAGGAGE_HEADER_NAME, baggage_data)
|
|
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)
|
|
113
104
|
|
|
114
105
|
@property
|
|
115
106
|
def fields(self):
|