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
sentry_sdk/tracing.py
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
|
-
import
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
import json
|
|
2
3
|
import warnings
|
|
3
|
-
|
|
4
|
-
from
|
|
4
|
+
|
|
5
|
+
from opentelemetry import trace as otel_trace, context
|
|
6
|
+
from opentelemetry.trace import (
|
|
7
|
+
format_trace_id,
|
|
8
|
+
format_span_id,
|
|
9
|
+
Span as OtelSpan,
|
|
10
|
+
TraceState,
|
|
11
|
+
get_current_span,
|
|
12
|
+
INVALID_SPAN,
|
|
13
|
+
)
|
|
14
|
+
from opentelemetry.trace.status import Status, StatusCode
|
|
15
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
16
|
+
from opentelemetry.version import __version__ as otel_version
|
|
5
17
|
|
|
6
18
|
import sentry_sdk
|
|
7
|
-
from sentry_sdk.consts import
|
|
8
|
-
|
|
19
|
+
from sentry_sdk.consts import (
|
|
20
|
+
DEFAULT_SPAN_NAME,
|
|
21
|
+
DEFAULT_SPAN_ORIGIN,
|
|
22
|
+
BAGGAGE_HEADER_NAME,
|
|
23
|
+
SENTRY_TRACE_HEADER_NAME,
|
|
24
|
+
SPANSTATUS,
|
|
25
|
+
SPANDATA,
|
|
26
|
+
TransactionSource,
|
|
27
|
+
)
|
|
28
|
+
from sentry_sdk.opentelemetry.consts import (
|
|
29
|
+
TRACESTATE_SAMPLE_RATE_KEY,
|
|
30
|
+
SentrySpanAttribute,
|
|
31
|
+
)
|
|
32
|
+
from sentry_sdk.opentelemetry.utils import (
|
|
33
|
+
baggage_from_trace_state,
|
|
34
|
+
convert_from_otel_timestamp,
|
|
35
|
+
convert_to_otel_timestamp,
|
|
36
|
+
get_trace_context,
|
|
37
|
+
get_trace_state,
|
|
38
|
+
get_sentry_meta,
|
|
39
|
+
serialize_trace_state,
|
|
40
|
+
)
|
|
41
|
+
from sentry_sdk.tracing_utils import get_span_status_from_http_code
|
|
9
42
|
from sentry_sdk.utils import (
|
|
43
|
+
_serialize_span_attribute,
|
|
10
44
|
get_current_thread_meta,
|
|
11
|
-
|
|
12
|
-
logger,
|
|
13
|
-
nanosecond_time,
|
|
45
|
+
parse_version,
|
|
14
46
|
should_be_treated_as_error,
|
|
15
47
|
)
|
|
16
48
|
|
|
17
|
-
from typing import TYPE_CHECKING
|
|
49
|
+
from typing import TYPE_CHECKING, cast
|
|
18
50
|
|
|
19
51
|
|
|
20
52
|
if TYPE_CHECKING:
|
|
21
|
-
from collections.abc import Callable
|
|
53
|
+
from collections.abc import Callable
|
|
22
54
|
from typing import Any
|
|
23
55
|
from typing import Dict
|
|
24
56
|
from typing import Iterator
|
|
25
|
-
from typing import List
|
|
26
57
|
from typing import Optional
|
|
27
58
|
from typing import overload
|
|
28
59
|
from typing import ParamSpec
|
|
@@ -30,342 +61,208 @@ if TYPE_CHECKING:
|
|
|
30
61
|
from typing import Union
|
|
31
62
|
from typing import TypeVar
|
|
32
63
|
|
|
33
|
-
from typing_extensions import TypedDict, Unpack
|
|
34
|
-
|
|
35
64
|
P = ParamSpec("P")
|
|
36
65
|
R = TypeVar("R")
|
|
37
66
|
|
|
38
|
-
from sentry_sdk.profiler.continuous_profiler import ContinuousProfile
|
|
39
|
-
from sentry_sdk.profiler.transaction_profiler import Profile
|
|
40
67
|
from sentry_sdk._types import (
|
|
41
|
-
Event,
|
|
42
|
-
MeasurementUnit,
|
|
43
68
|
SamplingContext,
|
|
44
|
-
MeasurementValue,
|
|
45
69
|
)
|
|
46
70
|
|
|
47
|
-
|
|
48
|
-
trace_id: str
|
|
49
|
-
"""
|
|
50
|
-
The trace ID of the root span. If this new span is to be the root span,
|
|
51
|
-
omit this parameter, and a new trace ID will be generated.
|
|
52
|
-
"""
|
|
53
|
-
|
|
54
|
-
span_id: str
|
|
55
|
-
"""The span ID of this span. If omitted, a new span ID will be generated."""
|
|
56
|
-
|
|
57
|
-
parent_span_id: str
|
|
58
|
-
"""The span ID of the parent span, if applicable."""
|
|
59
|
-
|
|
60
|
-
same_process_as_parent: bool
|
|
61
|
-
"""Whether this span is in the same process as the parent span."""
|
|
62
|
-
|
|
63
|
-
sampled: bool
|
|
64
|
-
"""
|
|
65
|
-
Whether the span should be sampled. Overrides the default sampling decision
|
|
66
|
-
for this span when provided.
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
op: str
|
|
70
|
-
"""
|
|
71
|
-
The span's operation. A list of recommended values is available here:
|
|
72
|
-
https://develop.sentry.dev/sdk/performance/span-operations/
|
|
73
|
-
"""
|
|
74
|
-
|
|
75
|
-
description: str
|
|
76
|
-
"""A description of what operation is being performed within the span. This argument is DEPRECATED. Please use the `name` parameter, instead."""
|
|
77
|
-
|
|
78
|
-
hub: Optional["sentry_sdk.Hub"]
|
|
79
|
-
"""The hub to use for this span. This argument is DEPRECATED. Please use the `scope` parameter, instead."""
|
|
71
|
+
from sentry_sdk.tracing_utils import Baggage
|
|
80
72
|
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
_FLAGS_CAPACITY = 10
|
|
74
|
+
_OTEL_VERSION = parse_version(otel_version)
|
|
83
75
|
|
|
84
|
-
|
|
85
|
-
"""The transaction that this span belongs to."""
|
|
76
|
+
tracer = otel_trace.get_tracer(__name__)
|
|
86
77
|
|
|
87
|
-
start_timestamp: Optional[Union[datetime, float]]
|
|
88
|
-
"""
|
|
89
|
-
The timestamp when the span started. If omitted, the current time
|
|
90
|
-
will be used.
|
|
91
|
-
"""
|
|
92
78
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"""
|
|
98
|
-
The origin of the span.
|
|
99
|
-
See https://develop.sentry.dev/sdk/performance/trace-origin/
|
|
100
|
-
Default "manual".
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
name: str
|
|
104
|
-
"""A string describing what operation is being performed within the span/transaction."""
|
|
105
|
-
|
|
106
|
-
class TransactionKwargs(SpanKwargs, total=False):
|
|
107
|
-
source: str
|
|
108
|
-
"""
|
|
109
|
-
A string describing the source of the transaction name. This will be used to determine the transaction's type.
|
|
110
|
-
See https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations for more information.
|
|
111
|
-
Default "custom".
|
|
112
|
-
"""
|
|
79
|
+
class NoOpSpan:
|
|
80
|
+
def __init__(self, **kwargs):
|
|
81
|
+
# type: (Any) -> None
|
|
82
|
+
pass
|
|
113
83
|
|
|
114
|
-
|
|
115
|
-
|
|
84
|
+
def __repr__(self):
|
|
85
|
+
# type: () -> str
|
|
86
|
+
return "<%s>" % self.__class__.__name__
|
|
116
87
|
|
|
117
|
-
|
|
118
|
-
|
|
88
|
+
@property
|
|
89
|
+
def root_span(self):
|
|
90
|
+
# type: () -> Optional[Span]
|
|
91
|
+
return None
|
|
119
92
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"profiler_id": str,
|
|
124
|
-
},
|
|
125
|
-
)
|
|
93
|
+
def start_child(self, **kwargs):
|
|
94
|
+
# type: (**Any) -> NoOpSpan
|
|
95
|
+
return NoOpSpan()
|
|
126
96
|
|
|
127
|
-
|
|
128
|
-
|
|
97
|
+
def to_traceparent(self):
|
|
98
|
+
# type: () -> str
|
|
99
|
+
return ""
|
|
129
100
|
|
|
101
|
+
def to_baggage(self):
|
|
102
|
+
# type: () -> Optional[Baggage]
|
|
103
|
+
return None
|
|
130
104
|
|
|
131
|
-
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
COMPONENT = "component"
|
|
135
|
-
CUSTOM = "custom"
|
|
136
|
-
ROUTE = "route"
|
|
137
|
-
TASK = "task"
|
|
138
|
-
URL = "url"
|
|
139
|
-
VIEW = "view"
|
|
105
|
+
def get_baggage(self):
|
|
106
|
+
# type: () -> Optional[Baggage]
|
|
107
|
+
return None
|
|
140
108
|
|
|
141
|
-
def
|
|
142
|
-
# type: () -> str
|
|
143
|
-
return
|
|
109
|
+
def iter_headers(self):
|
|
110
|
+
# type: () -> Iterator[Tuple[str, str]]
|
|
111
|
+
return iter(())
|
|
144
112
|
|
|
113
|
+
def set_tag(self, key, value):
|
|
114
|
+
# type: (str, Any) -> None
|
|
115
|
+
pass
|
|
145
116
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
]
|
|
117
|
+
def set_data(self, key, value):
|
|
118
|
+
# type: (str, Any) -> None
|
|
119
|
+
pass
|
|
150
120
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
"handler_name": TransactionSource.COMPONENT,
|
|
155
|
-
"method_and_path_pattern": TransactionSource.ROUTE,
|
|
156
|
-
"path": TransactionSource.URL,
|
|
157
|
-
"route_name": TransactionSource.COMPONENT,
|
|
158
|
-
"route_pattern": TransactionSource.ROUTE,
|
|
159
|
-
"uri_template": TransactionSource.ROUTE,
|
|
160
|
-
"url": TransactionSource.ROUTE,
|
|
161
|
-
}
|
|
121
|
+
def set_status(self, value):
|
|
122
|
+
# type: (str) -> None
|
|
123
|
+
pass
|
|
162
124
|
|
|
125
|
+
def set_http_status(self, http_status):
|
|
126
|
+
# type: (int) -> None
|
|
127
|
+
pass
|
|
163
128
|
|
|
164
|
-
def
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
Returns the Sentry status corresponding to the given HTTP status code.
|
|
129
|
+
def is_success(self):
|
|
130
|
+
# type: () -> bool
|
|
131
|
+
return True
|
|
168
132
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return SPANSTATUS.OK
|
|
173
|
-
|
|
174
|
-
elif 400 <= http_status_code < 500:
|
|
175
|
-
if http_status_code == 403:
|
|
176
|
-
return SPANSTATUS.PERMISSION_DENIED
|
|
177
|
-
elif http_status_code == 404:
|
|
178
|
-
return SPANSTATUS.NOT_FOUND
|
|
179
|
-
elif http_status_code == 429:
|
|
180
|
-
return SPANSTATUS.RESOURCE_EXHAUSTED
|
|
181
|
-
elif http_status_code == 413:
|
|
182
|
-
return SPANSTATUS.FAILED_PRECONDITION
|
|
183
|
-
elif http_status_code == 401:
|
|
184
|
-
return SPANSTATUS.UNAUTHENTICATED
|
|
185
|
-
elif http_status_code == 409:
|
|
186
|
-
return SPANSTATUS.ALREADY_EXISTS
|
|
187
|
-
else:
|
|
188
|
-
return SPANSTATUS.INVALID_ARGUMENT
|
|
189
|
-
|
|
190
|
-
elif 500 <= http_status_code < 600:
|
|
191
|
-
if http_status_code == 504:
|
|
192
|
-
return SPANSTATUS.DEADLINE_EXCEEDED
|
|
193
|
-
elif http_status_code == 501:
|
|
194
|
-
return SPANSTATUS.UNIMPLEMENTED
|
|
195
|
-
elif http_status_code == 503:
|
|
196
|
-
return SPANSTATUS.UNAVAILABLE
|
|
197
|
-
else:
|
|
198
|
-
return SPANSTATUS.INTERNAL_ERROR
|
|
133
|
+
def to_json(self):
|
|
134
|
+
# type: () -> Dict[str, Any]
|
|
135
|
+
return {}
|
|
199
136
|
|
|
200
|
-
|
|
137
|
+
def get_trace_context(self):
|
|
138
|
+
# type: () -> Any
|
|
139
|
+
return {}
|
|
201
140
|
|
|
141
|
+
def get_profile_context(self):
|
|
142
|
+
# type: () -> Any
|
|
143
|
+
return {}
|
|
202
144
|
|
|
203
|
-
|
|
204
|
-
|
|
145
|
+
def finish(
|
|
146
|
+
self,
|
|
147
|
+
end_timestamp=None, # type: Optional[Union[float, datetime]]
|
|
148
|
+
):
|
|
149
|
+
# type: (...) -> None
|
|
150
|
+
pass
|
|
205
151
|
|
|
206
|
-
|
|
152
|
+
def set_context(self, key, value):
|
|
153
|
+
# type: (str, dict[str, Any]) -> None
|
|
154
|
+
pass
|
|
207
155
|
|
|
208
|
-
def
|
|
156
|
+
def init_span_recorder(self, maxlen):
|
|
209
157
|
# type: (int) -> None
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
#
|
|
214
|
-
|
|
215
|
-
self.maxlen = maxlen - 1
|
|
216
|
-
self.spans = [] # type: List[Span]
|
|
217
|
-
self.dropped_spans = 0 # type: int
|
|
218
|
-
|
|
219
|
-
def add(self, span):
|
|
220
|
-
# type: (Span) -> None
|
|
221
|
-
if len(self.spans) > self.maxlen:
|
|
222
|
-
span._span_recorder = None
|
|
223
|
-
self.dropped_spans += 1
|
|
224
|
-
else:
|
|
225
|
-
self.spans.append(span)
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
def _set_initial_sampling_decision(self, sampling_context):
|
|
161
|
+
# type: (SamplingContext) -> None
|
|
162
|
+
pass
|
|
226
163
|
|
|
227
164
|
|
|
228
165
|
class Span:
|
|
229
|
-
"""A span holds timing information of a block of code.
|
|
230
|
-
Spans can have multiple child spans thus forming a span tree.
|
|
231
|
-
|
|
232
|
-
:param trace_id: The trace ID of the root span. If this new span is to be the root span,
|
|
233
|
-
omit this parameter, and a new trace ID will be generated.
|
|
234
|
-
:param span_id: The span ID of this span. If omitted, a new span ID will be generated.
|
|
235
|
-
:param parent_span_id: The span ID of the parent span, if applicable.
|
|
236
|
-
:param same_process_as_parent: Whether this span is in the same process as the parent span.
|
|
237
|
-
:param sampled: Whether the span should be sampled. Overrides the default sampling decision
|
|
238
|
-
for this span when provided.
|
|
239
|
-
:param op: The span's operation. A list of recommended values is available here:
|
|
240
|
-
https://develop.sentry.dev/sdk/performance/span-operations/
|
|
241
|
-
:param description: A description of what operation is being performed within the span.
|
|
242
|
-
|
|
243
|
-
.. deprecated:: 2.15.0
|
|
244
|
-
Please use the `name` parameter, instead.
|
|
245
|
-
:param name: A string describing what operation is being performed within the span.
|
|
246
|
-
:param hub: The hub to use for this span.
|
|
247
|
-
|
|
248
|
-
.. deprecated:: 2.0.0
|
|
249
|
-
Please use the `scope` parameter, instead.
|
|
250
|
-
:param status: The span's status. Possible values are listed at
|
|
251
|
-
https://develop.sentry.dev/sdk/event-payloads/span/
|
|
252
|
-
:param containing_transaction: The transaction that this span belongs to.
|
|
253
|
-
:param start_timestamp: The timestamp when the span started. If omitted, the current time
|
|
254
|
-
will be used.
|
|
255
|
-
:param scope: The scope to use for this span. If not provided, we use the current scope.
|
|
256
166
|
"""
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
"trace_id",
|
|
260
|
-
"span_id",
|
|
261
|
-
"parent_span_id",
|
|
262
|
-
"same_process_as_parent",
|
|
263
|
-
"sampled",
|
|
264
|
-
"op",
|
|
265
|
-
"description",
|
|
266
|
-
"_measurements",
|
|
267
|
-
"start_timestamp",
|
|
268
|
-
"_start_timestamp_monotonic_ns",
|
|
269
|
-
"status",
|
|
270
|
-
"timestamp",
|
|
271
|
-
"_tags",
|
|
272
|
-
"_data",
|
|
273
|
-
"_span_recorder",
|
|
274
|
-
"hub",
|
|
275
|
-
"_context_manager_state",
|
|
276
|
-
"_containing_transaction",
|
|
277
|
-
"_local_aggregator",
|
|
278
|
-
"scope",
|
|
279
|
-
"origin",
|
|
280
|
-
"name",
|
|
281
|
-
)
|
|
167
|
+
OTel span wrapper providing compatibility with the old span interface.
|
|
168
|
+
"""
|
|
282
169
|
|
|
283
170
|
def __init__(
|
|
284
171
|
self,
|
|
285
|
-
|
|
286
|
-
span_id=None, # type: Optional[str]
|
|
287
|
-
parent_span_id=None, # type: Optional[str]
|
|
288
|
-
same_process_as_parent=True, # type: bool
|
|
289
|
-
sampled=None, # type: Optional[bool]
|
|
172
|
+
*,
|
|
290
173
|
op=None, # type: Optional[str]
|
|
291
174
|
description=None, # type: Optional[str]
|
|
292
|
-
hub=None, # type: Optional[sentry_sdk.Hub] # deprecated
|
|
293
175
|
status=None, # type: Optional[str]
|
|
294
|
-
|
|
176
|
+
sampled=None, # type: Optional[bool]
|
|
295
177
|
start_timestamp=None, # type: Optional[Union[datetime, float]]
|
|
296
|
-
|
|
297
|
-
origin="manual", # type: str
|
|
178
|
+
origin=None, # type: Optional[str]
|
|
298
179
|
name=None, # type: Optional[str]
|
|
180
|
+
source=TransactionSource.CUSTOM, # type: str
|
|
181
|
+
attributes=None, # type: Optional[dict[str, Any]]
|
|
182
|
+
only_if_parent=False, # type: bool
|
|
183
|
+
parent_span=None, # type: Optional[Span]
|
|
184
|
+
otel_span=None, # type: Optional[OtelSpan]
|
|
185
|
+
span=None, # type: Optional[Span]
|
|
299
186
|
):
|
|
300
187
|
# type: (...) -> None
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
self.parent_span_id = parent_span_id
|
|
304
|
-
self.same_process_as_parent = same_process_as_parent
|
|
305
|
-
self.sampled = sampled
|
|
306
|
-
self.op = op
|
|
307
|
-
self.description = name or description
|
|
308
|
-
self.status = status
|
|
309
|
-
self.hub = hub # backwards compatibility
|
|
310
|
-
self.scope = scope
|
|
311
|
-
self.origin = origin
|
|
312
|
-
self._measurements = {} # type: Dict[str, MeasurementValue]
|
|
313
|
-
self._tags = {} # type: MutableMapping[str, str]
|
|
314
|
-
self._data = {} # type: Dict[str, Any]
|
|
315
|
-
self._containing_transaction = containing_transaction
|
|
316
|
-
|
|
317
|
-
if hub is not None:
|
|
318
|
-
warnings.warn(
|
|
319
|
-
"The `hub` parameter is deprecated. Please use `scope` instead.",
|
|
320
|
-
DeprecationWarning,
|
|
321
|
-
stacklevel=2,
|
|
322
|
-
)
|
|
188
|
+
"""
|
|
189
|
+
If otel_span is passed explicitly, just acts as a proxy.
|
|
323
190
|
|
|
324
|
-
|
|
191
|
+
If span is passed explicitly, use it. The only purpose of this param
|
|
192
|
+
if backwards compatibility with start_transaction(transaction=...).
|
|
325
193
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
194
|
+
If only_if_parent is True, just return an INVALID_SPAN
|
|
195
|
+
and avoid instrumentation if there's no active parent span.
|
|
196
|
+
"""
|
|
197
|
+
if otel_span is not None:
|
|
198
|
+
self._otel_span = otel_span
|
|
199
|
+
elif span is not None:
|
|
200
|
+
self._otel_span = span._otel_span
|
|
201
|
+
else:
|
|
202
|
+
skip_span = False
|
|
203
|
+
if only_if_parent and parent_span is None:
|
|
204
|
+
parent_span_context = get_current_span().get_span_context()
|
|
205
|
+
skip_span = (
|
|
206
|
+
not parent_span_context.is_valid or parent_span_context.is_remote
|
|
207
|
+
)
|
|
337
208
|
|
|
338
|
-
|
|
339
|
-
|
|
209
|
+
if skip_span:
|
|
210
|
+
self._otel_span = INVALID_SPAN
|
|
211
|
+
else:
|
|
340
212
|
|
|
341
|
-
|
|
342
|
-
|
|
213
|
+
if start_timestamp is not None:
|
|
214
|
+
# OTel timestamps have nanosecond precision
|
|
215
|
+
start_timestamp = convert_to_otel_timestamp(start_timestamp)
|
|
216
|
+
|
|
217
|
+
span_name = name or description or op or DEFAULT_SPAN_NAME
|
|
218
|
+
|
|
219
|
+
# Prepopulate some attrs so that they're accessible in traces_sampler
|
|
220
|
+
attributes = attributes or {}
|
|
221
|
+
if op is not None:
|
|
222
|
+
attributes[SentrySpanAttribute.OP] = op
|
|
223
|
+
if source is not None:
|
|
224
|
+
attributes[SentrySpanAttribute.SOURCE] = source
|
|
225
|
+
if description is not None:
|
|
226
|
+
attributes[SentrySpanAttribute.DESCRIPTION] = description
|
|
227
|
+
if sampled is not None:
|
|
228
|
+
attributes[SentrySpanAttribute.CUSTOM_SAMPLED] = sampled
|
|
229
|
+
|
|
230
|
+
parent_context = None
|
|
231
|
+
if parent_span is not None:
|
|
232
|
+
parent_context = otel_trace.set_span_in_context(
|
|
233
|
+
parent_span._otel_span
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
self._otel_span = tracer.start_span(
|
|
237
|
+
span_name,
|
|
238
|
+
context=parent_context,
|
|
239
|
+
start_time=start_timestamp,
|
|
240
|
+
attributes=attributes,
|
|
241
|
+
)
|
|
343
242
|
|
|
344
|
-
|
|
345
|
-
|
|
243
|
+
self.origin = origin or DEFAULT_SPAN_ORIGIN
|
|
244
|
+
self.description = description
|
|
245
|
+
self.name = span_name
|
|
346
246
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if self._span_recorder is None:
|
|
352
|
-
self._span_recorder = _SpanRecorder(maxlen)
|
|
247
|
+
if status is not None:
|
|
248
|
+
self.set_status(status)
|
|
249
|
+
|
|
250
|
+
self.update_active_thread()
|
|
353
251
|
|
|
354
|
-
def
|
|
355
|
-
# type: (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
return rv
|
|
252
|
+
def __eq__(self, other):
|
|
253
|
+
# type: (object) -> bool
|
|
254
|
+
if not isinstance(other, Span):
|
|
255
|
+
return False
|
|
256
|
+
return self._otel_span == other._otel_span
|
|
360
257
|
|
|
361
258
|
def __repr__(self):
|
|
362
259
|
# type: () -> str
|
|
363
260
|
return (
|
|
364
|
-
"<%s(op=%r,
|
|
261
|
+
"<%s(op=%r, name:%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, origin=%r)>"
|
|
365
262
|
% (
|
|
366
263
|
self.__class__.__name__,
|
|
367
264
|
self.op,
|
|
368
|
-
self.
|
|
265
|
+
self.name,
|
|
369
266
|
self.trace_id,
|
|
370
267
|
self.span_id,
|
|
371
268
|
self.parent_span_id,
|
|
@@ -376,194 +273,172 @@ class Span:
|
|
|
376
273
|
|
|
377
274
|
def __enter__(self):
|
|
378
275
|
# type: () -> Span
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
276
|
+
# XXX use_span? https://github.com/open-telemetry/opentelemetry-python/blob/3836da8543ce9751051e38a110c0468724042e62/opentelemetry-api/src/opentelemetry/trace/__init__.py#L547
|
|
277
|
+
#
|
|
278
|
+
# create a Context object with parent set as current span
|
|
279
|
+
ctx = otel_trace.set_span_in_context(self._otel_span)
|
|
280
|
+
# set as the implicit current context
|
|
281
|
+
self._ctx_token = context.attach(ctx)
|
|
282
|
+
|
|
283
|
+
# get the new scope that was forked on context.attach
|
|
284
|
+
self.scope = sentry_sdk.get_current_scope()
|
|
285
|
+
self.scope.span = self
|
|
286
|
+
|
|
383
287
|
return self
|
|
384
288
|
|
|
385
289
|
def __exit__(self, ty, value, tb):
|
|
386
290
|
# type: (Optional[Any], Optional[Any], Optional[Any]) -> None
|
|
387
291
|
if value is not None and should_be_treated_as_error(ty, value):
|
|
388
292
|
self.set_status(SPANSTATUS.INTERNAL_ERROR)
|
|
293
|
+
else:
|
|
294
|
+
status_unset = (
|
|
295
|
+
hasattr(self._otel_span, "status")
|
|
296
|
+
and self._otel_span.status.status_code == StatusCode.UNSET
|
|
297
|
+
)
|
|
298
|
+
if status_unset:
|
|
299
|
+
self.set_status(SPANSTATUS.OK)
|
|
389
300
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
self.
|
|
393
|
-
scope.span = old_span
|
|
301
|
+
self.finish()
|
|
302
|
+
context.detach(self._ctx_token)
|
|
303
|
+
del self._ctx_token
|
|
394
304
|
|
|
395
305
|
@property
|
|
396
|
-
def
|
|
397
|
-
# type: () -> Optional[
|
|
398
|
-
|
|
399
|
-
The ``Transaction`` is the root of the span tree,
|
|
400
|
-
so one could also think of this ``Transaction`` as the "root span"."""
|
|
401
|
-
|
|
402
|
-
# this is a getter rather than a regular attribute so that transactions
|
|
403
|
-
# can return `self` here instead (as a way to prevent them circularly
|
|
404
|
-
# referencing themselves)
|
|
405
|
-
return self._containing_transaction
|
|
406
|
-
|
|
407
|
-
def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
|
|
408
|
-
# type: (str, **Any) -> Span
|
|
409
|
-
"""
|
|
410
|
-
Start a sub-span from the current span or transaction.
|
|
411
|
-
|
|
412
|
-
Takes the same arguments as the initializer of :py:class:`Span`. The
|
|
413
|
-
trace id, sampling decision, transaction pointer, and span recorder are
|
|
414
|
-
inherited from the current span/transaction.
|
|
306
|
+
def description(self):
|
|
307
|
+
# type: () -> Optional[str]
|
|
308
|
+
return self.get_attribute(SentrySpanAttribute.DESCRIPTION)
|
|
415
309
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if kwargs.get("description") is not None:
|
|
421
|
-
warnings.warn(
|
|
422
|
-
"The `description` parameter is deprecated. Please use `name` instead.",
|
|
423
|
-
DeprecationWarning,
|
|
424
|
-
stacklevel=2,
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
configuration_instrumenter = sentry_sdk.get_client().options["instrumenter"]
|
|
428
|
-
|
|
429
|
-
if instrumenter != configuration_instrumenter:
|
|
430
|
-
return NoOpSpan()
|
|
310
|
+
@description.setter
|
|
311
|
+
def description(self, value):
|
|
312
|
+
# type: (Optional[str]) -> None
|
|
313
|
+
self.set_attribute(SentrySpanAttribute.DESCRIPTION, value)
|
|
431
314
|
|
|
432
|
-
|
|
315
|
+
@property
|
|
316
|
+
def origin(self):
|
|
317
|
+
# type: () -> Optional[str]
|
|
318
|
+
return self.get_attribute(SentrySpanAttribute.ORIGIN)
|
|
433
319
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
**kwargs,
|
|
439
|
-
)
|
|
320
|
+
@origin.setter
|
|
321
|
+
def origin(self, value):
|
|
322
|
+
# type: (Optional[str]) -> None
|
|
323
|
+
self.set_attribute(SentrySpanAttribute.ORIGIN, value)
|
|
440
324
|
|
|
441
|
-
|
|
442
|
-
|
|
325
|
+
@property
|
|
326
|
+
def root_span(self):
|
|
327
|
+
# type: () -> Optional[Span]
|
|
328
|
+
root_otel_span = cast(
|
|
329
|
+
"Optional[OtelSpan]", get_sentry_meta(self._otel_span, "root_span")
|
|
443
330
|
)
|
|
444
|
-
if
|
|
445
|
-
span_recorder.add(child)
|
|
331
|
+
return Span(otel_span=root_otel_span) if root_otel_span else None
|
|
446
332
|
|
|
447
|
-
|
|
333
|
+
@property
|
|
334
|
+
def is_root_span(self):
|
|
335
|
+
# type: () -> bool
|
|
336
|
+
return self.root_span == self
|
|
448
337
|
|
|
449
|
-
@
|
|
450
|
-
def
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
the ``sentry-trace`` and ``baggage`` headers from the environ (if any)
|
|
459
|
-
before returning the Transaction.
|
|
338
|
+
@property
|
|
339
|
+
def parent_span_id(self):
|
|
340
|
+
# type: () -> Optional[str]
|
|
341
|
+
if (
|
|
342
|
+
not isinstance(self._otel_span, ReadableSpan)
|
|
343
|
+
or self._otel_span.parent is None
|
|
344
|
+
):
|
|
345
|
+
return None
|
|
346
|
+
return format_span_id(self._otel_span.parent.span_id)
|
|
460
347
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
348
|
+
@property
|
|
349
|
+
def trace_id(self):
|
|
350
|
+
# type: () -> str
|
|
351
|
+
return format_trace_id(self._otel_span.get_span_context().trace_id)
|
|
465
352
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
"Deprecated: use Transaction.continue_from_environ "
|
|
471
|
-
"instead of Span.continue_from_environ."
|
|
472
|
-
)
|
|
473
|
-
return Transaction.continue_from_headers(EnvironHeaders(environ), **kwargs)
|
|
353
|
+
@property
|
|
354
|
+
def span_id(self):
|
|
355
|
+
# type: () -> str
|
|
356
|
+
return format_span_id(self._otel_span.get_span_context().span_id)
|
|
474
357
|
|
|
475
|
-
@
|
|
476
|
-
def
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
**kwargs, # type: Any
|
|
482
|
-
):
|
|
483
|
-
# type: (...) -> Transaction
|
|
484
|
-
"""
|
|
485
|
-
Create a transaction with the given params (including any data pulled from
|
|
486
|
-
the ``sentry-trace`` and ``baggage`` headers).
|
|
358
|
+
@property
|
|
359
|
+
def is_valid(self):
|
|
360
|
+
# type: () -> bool
|
|
361
|
+
return self._otel_span.get_span_context().is_valid and isinstance(
|
|
362
|
+
self._otel_span, ReadableSpan
|
|
363
|
+
)
|
|
487
364
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
# TODO move this to the Transaction class
|
|
493
|
-
if cls is Span:
|
|
494
|
-
logger.warning(
|
|
495
|
-
"Deprecated: use Transaction.continue_from_headers "
|
|
496
|
-
"instead of Span.continue_from_headers."
|
|
497
|
-
)
|
|
365
|
+
@property
|
|
366
|
+
def sampled(self):
|
|
367
|
+
# type: () -> Optional[bool]
|
|
368
|
+
return self._otel_span.get_span_context().trace_flags.sampled
|
|
498
369
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
370
|
+
@property
|
|
371
|
+
def sample_rate(self):
|
|
372
|
+
# type: () -> Optional[float]
|
|
373
|
+
sample_rate = self._otel_span.get_span_context().trace_state.get(
|
|
374
|
+
TRACESTATE_SAMPLE_RATE_KEY
|
|
503
375
|
)
|
|
504
|
-
|
|
376
|
+
return float(sample_rate) if sample_rate is not None else None
|
|
505
377
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
)
|
|
378
|
+
@property
|
|
379
|
+
def op(self):
|
|
380
|
+
# type: () -> Optional[str]
|
|
381
|
+
return self.get_attribute(SentrySpanAttribute.OP)
|
|
509
382
|
|
|
510
|
-
|
|
511
|
-
|
|
383
|
+
@op.setter
|
|
384
|
+
def op(self, value):
|
|
385
|
+
# type: (Optional[str]) -> None
|
|
386
|
+
self.set_attribute(SentrySpanAttribute.OP, value)
|
|
512
387
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
388
|
+
@property
|
|
389
|
+
def name(self):
|
|
390
|
+
# type: () -> Optional[str]
|
|
391
|
+
return self.get_attribute(SentrySpanAttribute.NAME)
|
|
517
392
|
|
|
518
|
-
|
|
519
|
-
|
|
393
|
+
@name.setter
|
|
394
|
+
def name(self, value):
|
|
395
|
+
# type: (Optional[str]) -> None
|
|
396
|
+
self.set_attribute(SentrySpanAttribute.NAME, value)
|
|
520
397
|
|
|
521
|
-
|
|
398
|
+
@property
|
|
399
|
+
def source(self):
|
|
400
|
+
# type: () -> str
|
|
401
|
+
return (
|
|
402
|
+
self.get_attribute(SentrySpanAttribute.SOURCE) or TransactionSource.CUSTOM
|
|
403
|
+
)
|
|
522
404
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
If the span's containing transaction doesn't yet have a ``baggage`` value,
|
|
528
|
-
this will cause one to be generated and stored.
|
|
529
|
-
"""
|
|
530
|
-
if not self.containing_transaction:
|
|
531
|
-
# Do not propagate headers if there is no containing transaction. Otherwise, this
|
|
532
|
-
# span ends up being the root span of a new trace, and since it does not get sent
|
|
533
|
-
# to Sentry, the trace will be missing a root transaction. The dynamic sampling
|
|
534
|
-
# context will also be missing, breaking dynamic sampling & traces.
|
|
535
|
-
return
|
|
405
|
+
@source.setter
|
|
406
|
+
def source(self, value):
|
|
407
|
+
# type: (str) -> None
|
|
408
|
+
self.set_attribute(SentrySpanAttribute.SOURCE, value)
|
|
536
409
|
|
|
537
|
-
|
|
410
|
+
@property
|
|
411
|
+
def start_timestamp(self):
|
|
412
|
+
# type: () -> Optional[datetime]
|
|
413
|
+
if not isinstance(self._otel_span, ReadableSpan):
|
|
414
|
+
return None
|
|
538
415
|
|
|
539
|
-
|
|
540
|
-
if
|
|
541
|
-
|
|
416
|
+
start_time = self._otel_span.start_time
|
|
417
|
+
if start_time is None:
|
|
418
|
+
return None
|
|
542
419
|
|
|
543
|
-
|
|
544
|
-
def from_traceparent(
|
|
545
|
-
cls,
|
|
546
|
-
traceparent, # type: Optional[str]
|
|
547
|
-
**kwargs, # type: Any
|
|
548
|
-
):
|
|
549
|
-
# type: (...) -> Optional[Transaction]
|
|
550
|
-
"""
|
|
551
|
-
DEPRECATED: Use :py:meth:`sentry_sdk.tracing.Span.continue_from_headers`.
|
|
420
|
+
return convert_from_otel_timestamp(start_time)
|
|
552
421
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
"instead of from_traceparent(traceparent, **kwargs)"
|
|
559
|
-
)
|
|
422
|
+
@property
|
|
423
|
+
def timestamp(self):
|
|
424
|
+
# type: () -> Optional[datetime]
|
|
425
|
+
if not isinstance(self._otel_span, ReadableSpan):
|
|
426
|
+
return None
|
|
560
427
|
|
|
561
|
-
|
|
428
|
+
end_time = self._otel_span.end_time
|
|
429
|
+
if end_time is None:
|
|
562
430
|
return None
|
|
563
431
|
|
|
564
|
-
return
|
|
565
|
-
|
|
566
|
-
|
|
432
|
+
return convert_from_otel_timestamp(end_time)
|
|
433
|
+
|
|
434
|
+
def start_child(self, **kwargs):
|
|
435
|
+
# type: (**Any) -> Span
|
|
436
|
+
return Span(parent_span=self, **kwargs)
|
|
437
|
+
|
|
438
|
+
def iter_headers(self):
|
|
439
|
+
# type: () -> Iterator[Tuple[str, str]]
|
|
440
|
+
yield SENTRY_TRACE_HEADER_NAME, self.to_traceparent()
|
|
441
|
+
yield BAGGAGE_HEADER_NAME, serialize_trace_state(self.trace_state)
|
|
567
442
|
|
|
568
443
|
def to_traceparent(self):
|
|
569
444
|
# type: () -> str
|
|
@@ -580,723 +455,149 @@ class Span:
|
|
|
580
455
|
|
|
581
456
|
return traceparent
|
|
582
457
|
|
|
458
|
+
@property
|
|
459
|
+
def trace_state(self):
|
|
460
|
+
# type: () -> TraceState
|
|
461
|
+
return get_trace_state(self._otel_span)
|
|
462
|
+
|
|
583
463
|
def to_baggage(self):
|
|
584
|
-
# type: () ->
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
return None
|
|
464
|
+
# type: () -> Baggage
|
|
465
|
+
return self.get_baggage()
|
|
466
|
+
|
|
467
|
+
def get_baggage(self):
|
|
468
|
+
# type: () -> Baggage
|
|
469
|
+
return baggage_from_trace_state(self.trace_state)
|
|
591
470
|
|
|
592
471
|
def set_tag(self, key, value):
|
|
593
472
|
# type: (str, Any) -> None
|
|
594
|
-
self.
|
|
473
|
+
self.set_attribute(f"{SentrySpanAttribute.TAG}.{key}", value)
|
|
595
474
|
|
|
596
475
|
def set_data(self, key, value):
|
|
597
476
|
# type: (str, Any) -> None
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
def set_measurement(self, name, value, unit=""):
|
|
605
|
-
# type: (str, float, MeasurementUnit) -> None
|
|
606
|
-
self._measurements[name] = {"value": value, "unit": unit}
|
|
607
|
-
|
|
608
|
-
def set_thread(self, thread_id, thread_name):
|
|
609
|
-
# type: (Optional[int], Optional[str]) -> None
|
|
610
|
-
|
|
611
|
-
if thread_id is not None:
|
|
612
|
-
self.set_data(SPANDATA.THREAD_ID, str(thread_id))
|
|
477
|
+
warnings.warn(
|
|
478
|
+
"`Span.set_data` is deprecated. Please use `Span.set_attribute` instead.",
|
|
479
|
+
DeprecationWarning,
|
|
480
|
+
stacklevel=2,
|
|
481
|
+
)
|
|
613
482
|
|
|
614
|
-
|
|
615
|
-
|
|
483
|
+
# TODO-neel-potel we cannot add dicts here
|
|
484
|
+
self.set_attribute(key, value)
|
|
616
485
|
|
|
617
|
-
def
|
|
618
|
-
# type: (
|
|
619
|
-
if
|
|
620
|
-
self.
|
|
486
|
+
def get_attribute(self, name):
|
|
487
|
+
# type: (str) -> Optional[Any]
|
|
488
|
+
if (
|
|
489
|
+
not isinstance(self._otel_span, ReadableSpan)
|
|
490
|
+
or not self._otel_span.attributes
|
|
491
|
+
):
|
|
492
|
+
return None
|
|
493
|
+
return self._otel_span.attributes.get(name)
|
|
621
494
|
|
|
622
|
-
def
|
|
623
|
-
# type: (
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
495
|
+
def set_attribute(self, key, value):
|
|
496
|
+
# type: (str, Any) -> None
|
|
497
|
+
# otel doesn't support None as values, preferring to not set the key
|
|
498
|
+
# at all instead
|
|
499
|
+
if value is None:
|
|
500
|
+
return
|
|
501
|
+
serialized_value = _serialize_span_attribute(value)
|
|
502
|
+
if serialized_value is None:
|
|
503
|
+
return
|
|
629
504
|
|
|
630
|
-
|
|
631
|
-
# type: () -> bool
|
|
632
|
-
return self.status == "ok"
|
|
505
|
+
self._otel_span.set_attribute(key, serialized_value)
|
|
633
506
|
|
|
634
|
-
|
|
635
|
-
|
|
507
|
+
@property
|
|
508
|
+
def status(self):
|
|
509
|
+
# type: () -> Optional[str]
|
|
636
510
|
"""
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
:param scope: The scope to use for this transaction.
|
|
643
|
-
If not provided, the current scope will be used.
|
|
644
|
-
:param end_timestamp: Optional timestamp that should
|
|
645
|
-
be used as timestamp instead of the current time.
|
|
646
|
-
|
|
647
|
-
:return: Always ``None``. The type is ``Optional[str]`` to match
|
|
648
|
-
the return value of :py:meth:`sentry_sdk.tracing.Transaction.finish`.
|
|
511
|
+
Return the Sentry `SPANSTATUS` corresponding to the underlying OTel status.
|
|
512
|
+
Because differences in possible values in OTel `StatusCode` and
|
|
513
|
+
Sentry `SPANSTATUS` it can not be guaranteed that the status
|
|
514
|
+
set in `set_status()` will be the same as the one returned here.
|
|
649
515
|
"""
|
|
650
|
-
if self.
|
|
651
|
-
# This span is already finished, ignore.
|
|
516
|
+
if not isinstance(self._otel_span, ReadableSpan):
|
|
652
517
|
return None
|
|
653
518
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
elapsed = nanosecond_time() - self._start_timestamp_monotonic_ns
|
|
661
|
-
self.timestamp = self.start_timestamp + timedelta(
|
|
662
|
-
microseconds=elapsed / 1000
|
|
663
|
-
)
|
|
664
|
-
except AttributeError:
|
|
665
|
-
self.timestamp = datetime.now(timezone.utc)
|
|
666
|
-
|
|
667
|
-
scope = scope or sentry_sdk.get_current_scope()
|
|
668
|
-
maybe_create_breadcrumbs_from_span(scope, self)
|
|
669
|
-
|
|
670
|
-
return None
|
|
671
|
-
|
|
672
|
-
def to_json(self):
|
|
673
|
-
# type: () -> Dict[str, Any]
|
|
674
|
-
"""Returns a JSON-compatible representation of the span."""
|
|
675
|
-
|
|
676
|
-
rv = {
|
|
677
|
-
"trace_id": self.trace_id,
|
|
678
|
-
"span_id": self.span_id,
|
|
679
|
-
"parent_span_id": self.parent_span_id,
|
|
680
|
-
"same_process_as_parent": self.same_process_as_parent,
|
|
681
|
-
"op": self.op,
|
|
682
|
-
"description": self.description,
|
|
683
|
-
"start_timestamp": self.start_timestamp,
|
|
684
|
-
"timestamp": self.timestamp,
|
|
685
|
-
"origin": self.origin,
|
|
686
|
-
} # type: Dict[str, Any]
|
|
687
|
-
|
|
688
|
-
if self.status:
|
|
689
|
-
self._tags["status"] = self.status
|
|
690
|
-
|
|
691
|
-
if self._local_aggregator is not None:
|
|
692
|
-
metrics_summary = self._local_aggregator.to_json()
|
|
693
|
-
if metrics_summary:
|
|
694
|
-
rv["_metrics_summary"] = metrics_summary
|
|
695
|
-
|
|
696
|
-
if len(self._measurements) > 0:
|
|
697
|
-
rv["measurements"] = self._measurements
|
|
698
|
-
|
|
699
|
-
tags = self._tags
|
|
700
|
-
if tags:
|
|
701
|
-
rv["tags"] = tags
|
|
702
|
-
|
|
703
|
-
data = self._data
|
|
704
|
-
if data:
|
|
705
|
-
rv["data"] = data
|
|
706
|
-
|
|
707
|
-
return rv
|
|
519
|
+
if self._otel_span.status.status_code == StatusCode.UNSET:
|
|
520
|
+
return None
|
|
521
|
+
elif self._otel_span.status.status_code == StatusCode.OK:
|
|
522
|
+
return SPANSTATUS.OK
|
|
523
|
+
else:
|
|
524
|
+
return SPANSTATUS.UNKNOWN_ERROR
|
|
708
525
|
|
|
709
|
-
def
|
|
710
|
-
# type: () ->
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
"origin": self.origin,
|
|
718
|
-
} # type: Dict[str, Any]
|
|
719
|
-
if self.status:
|
|
720
|
-
rv["status"] = self.status
|
|
721
|
-
|
|
722
|
-
if self.containing_transaction:
|
|
723
|
-
rv["dynamic_sampling_context"] = (
|
|
724
|
-
self.containing_transaction.get_baggage().dynamic_sampling_context()
|
|
725
|
-
)
|
|
526
|
+
def set_status(self, status):
|
|
527
|
+
# type: (str) -> None
|
|
528
|
+
if status == SPANSTATUS.OK:
|
|
529
|
+
otel_status = StatusCode.OK
|
|
530
|
+
otel_description = None
|
|
531
|
+
else:
|
|
532
|
+
otel_status = StatusCode.ERROR
|
|
533
|
+
otel_description = status
|
|
726
534
|
|
|
727
|
-
|
|
535
|
+
if _OTEL_VERSION is None or _OTEL_VERSION >= (1, 12, 0):
|
|
536
|
+
self._otel_span.set_status(otel_status, otel_description)
|
|
537
|
+
else:
|
|
538
|
+
self._otel_span.set_status(Status(otel_status, otel_description))
|
|
728
539
|
|
|
729
|
-
|
|
540
|
+
def set_thread(self, thread_id, thread_name):
|
|
541
|
+
# type: (Optional[int], Optional[str]) -> None
|
|
730
542
|
if thread_id is not None:
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
thread_name = self._data.get(SPANDATA.THREAD_NAME)
|
|
734
|
-
if thread_name is not None:
|
|
735
|
-
data["thread.name"] = thread_name
|
|
543
|
+
self.set_attribute(SPANDATA.THREAD_ID, str(thread_id))
|
|
736
544
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
return rv
|
|
741
|
-
|
|
742
|
-
def get_profile_context(self):
|
|
743
|
-
# type: () -> Optional[ProfileContext]
|
|
744
|
-
profiler_id = self._data.get(SPANDATA.PROFILER_ID)
|
|
745
|
-
if profiler_id is None:
|
|
746
|
-
return None
|
|
747
|
-
|
|
748
|
-
return {
|
|
749
|
-
"profiler_id": profiler_id,
|
|
750
|
-
}
|
|
545
|
+
if thread_name is not None:
|
|
546
|
+
self.set_attribute(SPANDATA.THREAD_NAME, thread_name)
|
|
751
547
|
|
|
752
548
|
def update_active_thread(self):
|
|
753
549
|
# type: () -> None
|
|
754
550
|
thread_id, thread_name = get_current_thread_meta()
|
|
755
551
|
self.set_thread(thread_id, thread_name)
|
|
756
552
|
|
|
757
|
-
|
|
758
|
-
class Transaction(Span):
|
|
759
|
-
"""The Transaction is the root element that holds all the spans
|
|
760
|
-
for Sentry performance instrumentation.
|
|
761
|
-
|
|
762
|
-
:param name: Identifier of the transaction.
|
|
763
|
-
Will show up in the Sentry UI.
|
|
764
|
-
:param parent_sampled: Whether the parent transaction was sampled.
|
|
765
|
-
If True this transaction will be kept, if False it will be discarded.
|
|
766
|
-
:param baggage: The W3C baggage header value.
|
|
767
|
-
(see https://www.w3.org/TR/baggage/)
|
|
768
|
-
:param source: A string describing the source of the transaction name.
|
|
769
|
-
This will be used to determine the transaction's type.
|
|
770
|
-
See https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
|
|
771
|
-
for more information. Default "custom".
|
|
772
|
-
:param kwargs: Additional arguments to be passed to the Span constructor.
|
|
773
|
-
See :py:class:`sentry_sdk.tracing.Span` for available arguments.
|
|
774
|
-
"""
|
|
775
|
-
|
|
776
|
-
__slots__ = (
|
|
777
|
-
"name",
|
|
778
|
-
"source",
|
|
779
|
-
"parent_sampled",
|
|
780
|
-
# used to create baggage value for head SDKs in dynamic sampling
|
|
781
|
-
"sample_rate",
|
|
782
|
-
"_measurements",
|
|
783
|
-
"_contexts",
|
|
784
|
-
"_profile",
|
|
785
|
-
"_continuous_profile",
|
|
786
|
-
"_baggage",
|
|
787
|
-
"_sample_rand",
|
|
788
|
-
)
|
|
789
|
-
|
|
790
|
-
def __init__( # type: ignore[misc]
|
|
791
|
-
self,
|
|
792
|
-
name="", # type: str
|
|
793
|
-
parent_sampled=None, # type: Optional[bool]
|
|
794
|
-
baggage=None, # type: Optional[Baggage]
|
|
795
|
-
source=TransactionSource.CUSTOM, # type: str
|
|
796
|
-
**kwargs, # type: Unpack[SpanKwargs]
|
|
797
|
-
):
|
|
798
|
-
# type: (...) -> None
|
|
799
|
-
|
|
800
|
-
super().__init__(**kwargs)
|
|
801
|
-
|
|
802
|
-
self.name = name
|
|
803
|
-
self.source = source
|
|
804
|
-
self.sample_rate = None # type: Optional[float]
|
|
805
|
-
self.parent_sampled = parent_sampled
|
|
806
|
-
self._measurements = {} # type: Dict[str, MeasurementValue]
|
|
807
|
-
self._contexts = {} # type: Dict[str, Any]
|
|
808
|
-
self._profile = None # type: Optional[Profile]
|
|
809
|
-
self._continuous_profile = None # type: Optional[ContinuousProfile]
|
|
810
|
-
self._baggage = baggage
|
|
811
|
-
|
|
812
|
-
baggage_sample_rand = (
|
|
813
|
-
None if self._baggage is None else self._baggage._sample_rand()
|
|
814
|
-
)
|
|
815
|
-
if baggage_sample_rand is not None:
|
|
816
|
-
self._sample_rand = baggage_sample_rand
|
|
817
|
-
else:
|
|
818
|
-
self._sample_rand = _generate_sample_rand(self.trace_id)
|
|
819
|
-
|
|
820
|
-
def __repr__(self):
|
|
821
|
-
# type: () -> str
|
|
822
|
-
return (
|
|
823
|
-
"<%s(name=%r, op=%r, trace_id=%r, span_id=%r, parent_span_id=%r, sampled=%r, source=%r, origin=%r)>"
|
|
824
|
-
% (
|
|
825
|
-
self.__class__.__name__,
|
|
826
|
-
self.name,
|
|
827
|
-
self.op,
|
|
828
|
-
self.trace_id,
|
|
829
|
-
self.span_id,
|
|
830
|
-
self.parent_span_id,
|
|
831
|
-
self.sampled,
|
|
832
|
-
self.source,
|
|
833
|
-
self.origin,
|
|
834
|
-
)
|
|
835
|
-
)
|
|
836
|
-
|
|
837
|
-
def _possibly_started(self):
|
|
838
|
-
# type: () -> bool
|
|
839
|
-
"""Returns whether the transaction might have been started.
|
|
840
|
-
|
|
841
|
-
If this returns False, we know that the transaction was not started
|
|
842
|
-
with sentry_sdk.start_transaction, and therefore the transaction will
|
|
843
|
-
be discarded.
|
|
844
|
-
"""
|
|
845
|
-
|
|
846
|
-
# We must explicitly check self.sampled is False since self.sampled can be None
|
|
847
|
-
return self._span_recorder is not None or self.sampled is False
|
|
848
|
-
|
|
849
|
-
def __enter__(self):
|
|
850
|
-
# type: () -> Transaction
|
|
851
|
-
if not self._possibly_started():
|
|
852
|
-
logger.debug(
|
|
853
|
-
"Transaction was entered without being started with sentry_sdk.start_transaction."
|
|
854
|
-
"The transaction will not be sent to Sentry. To fix, start the transaction by"
|
|
855
|
-
"passing it to sentry_sdk.start_transaction."
|
|
856
|
-
)
|
|
857
|
-
|
|
858
|
-
super().__enter__()
|
|
859
|
-
|
|
860
|
-
if self._profile is not None:
|
|
861
|
-
self._profile.__enter__()
|
|
862
|
-
|
|
863
|
-
return self
|
|
864
|
-
|
|
865
|
-
def __exit__(self, ty, value, tb):
|
|
866
|
-
# type: (Optional[Any], Optional[Any], Optional[Any]) -> None
|
|
867
|
-
if self._profile is not None:
|
|
868
|
-
self._profile.__exit__(ty, value, tb)
|
|
869
|
-
|
|
870
|
-
if self._continuous_profile is not None:
|
|
871
|
-
self._continuous_profile.stop()
|
|
872
|
-
|
|
873
|
-
super().__exit__(ty, value, tb)
|
|
874
|
-
|
|
875
|
-
@property
|
|
876
|
-
def containing_transaction(self):
|
|
877
|
-
# type: () -> Transaction
|
|
878
|
-
"""The root element of the span tree.
|
|
879
|
-
In the case of a transaction it is the transaction itself.
|
|
880
|
-
"""
|
|
881
|
-
|
|
882
|
-
# Transactions (as spans) belong to themselves (as transactions). This
|
|
883
|
-
# is a getter rather than a regular attribute to avoid having a circular
|
|
884
|
-
# reference.
|
|
885
|
-
return self
|
|
886
|
-
|
|
887
|
-
def _get_scope_from_finish_args(
|
|
888
|
-
self,
|
|
889
|
-
scope_arg, # type: Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]]
|
|
890
|
-
hub_arg, # type: Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]]
|
|
891
|
-
):
|
|
892
|
-
# type: (...) -> Optional[sentry_sdk.Scope]
|
|
893
|
-
"""
|
|
894
|
-
Logic to get the scope from the arguments passed to finish. This
|
|
895
|
-
function exists for backwards compatibility with the old finish.
|
|
896
|
-
|
|
897
|
-
TODO: Remove this function in the next major version.
|
|
898
|
-
"""
|
|
899
|
-
scope_or_hub = scope_arg
|
|
900
|
-
if hub_arg is not None:
|
|
901
|
-
warnings.warn(
|
|
902
|
-
"The `hub` parameter is deprecated. Please use the `scope` parameter, instead.",
|
|
903
|
-
DeprecationWarning,
|
|
904
|
-
stacklevel=3,
|
|
905
|
-
)
|
|
906
|
-
|
|
907
|
-
scope_or_hub = hub_arg
|
|
908
|
-
|
|
909
|
-
if isinstance(scope_or_hub, sentry_sdk.Hub):
|
|
910
|
-
warnings.warn(
|
|
911
|
-
"Passing a Hub to finish is deprecated. Please pass a Scope, instead.",
|
|
912
|
-
DeprecationWarning,
|
|
913
|
-
stacklevel=3,
|
|
914
|
-
)
|
|
915
|
-
|
|
916
|
-
return scope_or_hub.scope
|
|
917
|
-
|
|
918
|
-
return scope_or_hub
|
|
919
|
-
|
|
920
|
-
def finish(
|
|
921
|
-
self,
|
|
922
|
-
scope=None, # type: Optional[sentry_sdk.Scope]
|
|
923
|
-
end_timestamp=None, # type: Optional[Union[float, datetime]]
|
|
924
|
-
*,
|
|
925
|
-
hub=None, # type: Optional[sentry_sdk.Hub]
|
|
926
|
-
):
|
|
927
|
-
# type: (...) -> Optional[str]
|
|
928
|
-
"""Finishes the transaction and sends it to Sentry.
|
|
929
|
-
All finished spans in the transaction will also be sent to Sentry.
|
|
930
|
-
|
|
931
|
-
:param scope: The Scope to use for this transaction.
|
|
932
|
-
If not provided, the current Scope will be used.
|
|
933
|
-
:param end_timestamp: Optional timestamp that should
|
|
934
|
-
be used as timestamp instead of the current time.
|
|
935
|
-
:param hub: The hub to use for this transaction.
|
|
936
|
-
This argument is DEPRECATED. Please use the `scope`
|
|
937
|
-
parameter, instead.
|
|
938
|
-
|
|
939
|
-
:return: The event ID if the transaction was sent to Sentry,
|
|
940
|
-
otherwise None.
|
|
941
|
-
"""
|
|
942
|
-
if self.timestamp is not None:
|
|
943
|
-
# This transaction is already finished, ignore.
|
|
944
|
-
return None
|
|
945
|
-
|
|
946
|
-
# For backwards compatibility, we must handle the case where `scope`
|
|
947
|
-
# or `hub` could both either be a `Scope` or a `Hub`.
|
|
948
|
-
scope = self._get_scope_from_finish_args(
|
|
949
|
-
scope, hub
|
|
950
|
-
) # type: Optional[sentry_sdk.Scope]
|
|
951
|
-
|
|
952
|
-
scope = scope or self.scope or sentry_sdk.get_current_scope()
|
|
953
|
-
client = sentry_sdk.get_client()
|
|
954
|
-
|
|
955
|
-
if not client.is_active():
|
|
956
|
-
# We have no active client and therefore nowhere to send this transaction.
|
|
957
|
-
return None
|
|
958
|
-
|
|
959
|
-
if self._span_recorder is None:
|
|
960
|
-
# Explicit check against False needed because self.sampled might be None
|
|
961
|
-
if self.sampled is False:
|
|
962
|
-
logger.debug("Discarding transaction because sampled = False")
|
|
963
|
-
else:
|
|
964
|
-
logger.debug(
|
|
965
|
-
"Discarding transaction because it was not started with sentry_sdk.start_transaction"
|
|
966
|
-
)
|
|
967
|
-
|
|
968
|
-
# This is not entirely accurate because discards here are not
|
|
969
|
-
# exclusively based on sample rate but also traces sampler, but
|
|
970
|
-
# we handle this the same here.
|
|
971
|
-
if client.transport and has_tracing_enabled(client.options):
|
|
972
|
-
if client.monitor and client.monitor.downsample_factor > 0:
|
|
973
|
-
reason = "backpressure"
|
|
974
|
-
else:
|
|
975
|
-
reason = "sample_rate"
|
|
976
|
-
|
|
977
|
-
client.transport.record_lost_event(reason, data_category="transaction")
|
|
978
|
-
|
|
979
|
-
# Only one span (the transaction itself) is discarded, since we did not record any spans here.
|
|
980
|
-
client.transport.record_lost_event(reason, data_category="span")
|
|
981
|
-
return None
|
|
982
|
-
|
|
983
|
-
if not self.name:
|
|
984
|
-
logger.warning(
|
|
985
|
-
"Transaction has no name, falling back to `<unlabeled transaction>`."
|
|
986
|
-
)
|
|
987
|
-
self.name = "<unlabeled transaction>"
|
|
988
|
-
|
|
989
|
-
super().finish(scope, end_timestamp)
|
|
990
|
-
|
|
991
|
-
if not self.sampled:
|
|
992
|
-
# At this point a `sampled = None` should have already been resolved
|
|
993
|
-
# to a concrete decision.
|
|
994
|
-
if self.sampled is None:
|
|
995
|
-
logger.warning("Discarding transaction without sampling decision.")
|
|
996
|
-
|
|
997
|
-
return None
|
|
998
|
-
|
|
999
|
-
finished_spans = [
|
|
1000
|
-
span.to_json()
|
|
1001
|
-
for span in self._span_recorder.spans
|
|
1002
|
-
if span.timestamp is not None
|
|
1003
|
-
]
|
|
1004
|
-
|
|
1005
|
-
len_diff = len(self._span_recorder.spans) - len(finished_spans)
|
|
1006
|
-
dropped_spans = len_diff + self._span_recorder.dropped_spans
|
|
1007
|
-
|
|
1008
|
-
# we do this to break the circular reference of transaction -> span
|
|
1009
|
-
# recorder -> span -> containing transaction (which is where we started)
|
|
1010
|
-
# before either the spans or the transaction goes out of scope and has
|
|
1011
|
-
# to be garbage collected
|
|
1012
|
-
self._span_recorder = None
|
|
1013
|
-
|
|
1014
|
-
contexts = {}
|
|
1015
|
-
contexts.update(self._contexts)
|
|
1016
|
-
contexts.update({"trace": self.get_trace_context()})
|
|
1017
|
-
profile_context = self.get_profile_context()
|
|
1018
|
-
if profile_context is not None:
|
|
1019
|
-
contexts.update({"profile": profile_context})
|
|
1020
|
-
|
|
1021
|
-
event = {
|
|
1022
|
-
"type": "transaction",
|
|
1023
|
-
"transaction": self.name,
|
|
1024
|
-
"transaction_info": {"source": self.source},
|
|
1025
|
-
"contexts": contexts,
|
|
1026
|
-
"tags": self._tags,
|
|
1027
|
-
"timestamp": self.timestamp,
|
|
1028
|
-
"start_timestamp": self.start_timestamp,
|
|
1029
|
-
"spans": finished_spans,
|
|
1030
|
-
} # type: Event
|
|
1031
|
-
|
|
1032
|
-
if dropped_spans > 0:
|
|
1033
|
-
event["_dropped_spans"] = dropped_spans
|
|
1034
|
-
|
|
1035
|
-
if self._profile is not None and self._profile.valid():
|
|
1036
|
-
event["profile"] = self._profile
|
|
1037
|
-
self._profile = None
|
|
1038
|
-
|
|
1039
|
-
event["measurements"] = self._measurements
|
|
1040
|
-
|
|
1041
|
-
# This is here since `to_json` is not invoked. This really should
|
|
1042
|
-
# be gone when we switch to onlyspans.
|
|
1043
|
-
if self._local_aggregator is not None:
|
|
1044
|
-
metrics_summary = self._local_aggregator.to_json()
|
|
1045
|
-
if metrics_summary:
|
|
1046
|
-
event["_metrics_summary"] = metrics_summary
|
|
1047
|
-
|
|
1048
|
-
return scope.capture_event(event)
|
|
1049
|
-
|
|
1050
|
-
def set_measurement(self, name, value, unit=""):
|
|
1051
|
-
# type: (str, float, MeasurementUnit) -> None
|
|
1052
|
-
self._measurements[name] = {"value": value, "unit": unit}
|
|
1053
|
-
|
|
1054
|
-
def set_context(self, key, value):
|
|
1055
|
-
# type: (str, dict[str, Any]) -> None
|
|
1056
|
-
"""Sets a context. Transactions can have multiple contexts
|
|
1057
|
-
and they should follow the format described in the "Contexts Interface"
|
|
1058
|
-
documentation.
|
|
1059
|
-
|
|
1060
|
-
:param key: The name of the context.
|
|
1061
|
-
:param value: The information about the context.
|
|
1062
|
-
"""
|
|
1063
|
-
self._contexts[key] = value
|
|
1064
|
-
|
|
1065
|
-
def set_http_status(self, http_status):
|
|
1066
|
-
# type: (int) -> None
|
|
1067
|
-
"""Sets the status of the Transaction according to the given HTTP status.
|
|
1068
|
-
|
|
1069
|
-
:param http_status: The HTTP status code."""
|
|
1070
|
-
super().set_http_status(http_status)
|
|
1071
|
-
self.set_context("response", {"status_code": http_status})
|
|
1072
|
-
|
|
1073
|
-
def to_json(self):
|
|
1074
|
-
# type: () -> Dict[str, Any]
|
|
1075
|
-
"""Returns a JSON-compatible representation of the transaction."""
|
|
1076
|
-
rv = super().to_json()
|
|
1077
|
-
|
|
1078
|
-
rv["name"] = self.name
|
|
1079
|
-
rv["source"] = self.source
|
|
1080
|
-
rv["sampled"] = self.sampled
|
|
1081
|
-
|
|
1082
|
-
return rv
|
|
1083
|
-
|
|
1084
|
-
def get_trace_context(self):
|
|
1085
|
-
# type: () -> Any
|
|
1086
|
-
trace_context = super().get_trace_context()
|
|
1087
|
-
|
|
1088
|
-
if self._data:
|
|
1089
|
-
trace_context["data"] = self._data
|
|
1090
|
-
|
|
1091
|
-
return trace_context
|
|
1092
|
-
|
|
1093
|
-
def get_baggage(self):
|
|
1094
|
-
# type: () -> Baggage
|
|
1095
|
-
"""Returns the :py:class:`~sentry_sdk.tracing_utils.Baggage`
|
|
1096
|
-
associated with the Transaction.
|
|
1097
|
-
|
|
1098
|
-
The first time a new baggage with Sentry items is made,
|
|
1099
|
-
it will be frozen."""
|
|
1100
|
-
if not self._baggage or self._baggage.mutable:
|
|
1101
|
-
self._baggage = Baggage.populate_from_transaction(self)
|
|
1102
|
-
|
|
1103
|
-
return self._baggage
|
|
1104
|
-
|
|
1105
|
-
def _set_initial_sampling_decision(self, sampling_context):
|
|
1106
|
-
# type: (SamplingContext) -> None
|
|
1107
|
-
"""
|
|
1108
|
-
Sets the transaction's sampling decision, according to the following
|
|
1109
|
-
precedence rules:
|
|
1110
|
-
|
|
1111
|
-
1. If a sampling decision is passed to `start_transaction`
|
|
1112
|
-
(`start_transaction(name: "my transaction", sampled: True)`), that
|
|
1113
|
-
decision will be used, regardless of anything else
|
|
1114
|
-
|
|
1115
|
-
2. If `traces_sampler` is defined, its decision will be used. It can
|
|
1116
|
-
choose to keep or ignore any parent sampling decision, or use the
|
|
1117
|
-
sampling context data to make its own decision or to choose a sample
|
|
1118
|
-
rate for the transaction.
|
|
1119
|
-
|
|
1120
|
-
3. If `traces_sampler` is not defined, but there's a parent sampling
|
|
1121
|
-
decision, the parent sampling decision will be used.
|
|
1122
|
-
|
|
1123
|
-
4. If `traces_sampler` is not defined and there's no parent sampling
|
|
1124
|
-
decision, `traces_sample_rate` will be used.
|
|
1125
|
-
"""
|
|
1126
|
-
client = sentry_sdk.get_client()
|
|
1127
|
-
|
|
1128
|
-
transaction_description = "{op}transaction <{name}>".format(
|
|
1129
|
-
op=("<" + self.op + "> " if self.op else ""), name=self.name
|
|
1130
|
-
)
|
|
1131
|
-
|
|
1132
|
-
# nothing to do if tracing is disabled
|
|
1133
|
-
if not has_tracing_enabled(client.options):
|
|
1134
|
-
self.sampled = False
|
|
1135
|
-
return
|
|
1136
|
-
|
|
1137
|
-
# if the user has forced a sampling decision by passing a `sampled`
|
|
1138
|
-
# value when starting the transaction, go with that
|
|
1139
|
-
if self.sampled is not None:
|
|
1140
|
-
self.sample_rate = float(self.sampled)
|
|
1141
|
-
return
|
|
1142
|
-
|
|
1143
|
-
# we would have bailed already if neither `traces_sampler` nor
|
|
1144
|
-
# `traces_sample_rate` were defined, so one of these should work; prefer
|
|
1145
|
-
# the hook if so
|
|
1146
|
-
sample_rate = (
|
|
1147
|
-
client.options["traces_sampler"](sampling_context)
|
|
1148
|
-
if callable(client.options.get("traces_sampler"))
|
|
1149
|
-
else (
|
|
1150
|
-
# default inheritance behavior
|
|
1151
|
-
sampling_context["parent_sampled"]
|
|
1152
|
-
if sampling_context["parent_sampled"] is not None
|
|
1153
|
-
else client.options["traces_sample_rate"]
|
|
1154
|
-
)
|
|
1155
|
-
)
|
|
1156
|
-
|
|
1157
|
-
# Since this is coming from the user (or from a function provided by the
|
|
1158
|
-
# user), who knows what we might get. (The only valid values are
|
|
1159
|
-
# booleans or numbers between 0 and 1.)
|
|
1160
|
-
if not is_valid_sample_rate(sample_rate, source="Tracing"):
|
|
1161
|
-
logger.warning(
|
|
1162
|
-
"[Tracing] Discarding {transaction_description} because of invalid sample rate.".format(
|
|
1163
|
-
transaction_description=transaction_description,
|
|
1164
|
-
)
|
|
1165
|
-
)
|
|
1166
|
-
self.sampled = False
|
|
1167
|
-
return
|
|
1168
|
-
|
|
1169
|
-
self.sample_rate = float(sample_rate)
|
|
1170
|
-
|
|
1171
|
-
if client.monitor:
|
|
1172
|
-
self.sample_rate /= 2**client.monitor.downsample_factor
|
|
1173
|
-
|
|
1174
|
-
# if the function returned 0 (or false), or if `traces_sample_rate` is
|
|
1175
|
-
# 0, it's a sign the transaction should be dropped
|
|
1176
|
-
if not self.sample_rate:
|
|
1177
|
-
logger.debug(
|
|
1178
|
-
"[Tracing] Discarding {transaction_description} because {reason}".format(
|
|
1179
|
-
transaction_description=transaction_description,
|
|
1180
|
-
reason=(
|
|
1181
|
-
"traces_sampler returned 0 or False"
|
|
1182
|
-
if callable(client.options.get("traces_sampler"))
|
|
1183
|
-
else "traces_sample_rate is set to 0"
|
|
1184
|
-
),
|
|
1185
|
-
)
|
|
1186
|
-
)
|
|
1187
|
-
self.sampled = False
|
|
1188
|
-
return
|
|
1189
|
-
|
|
1190
|
-
# Now we roll the dice. self._sample_rand is inclusive of 0, but not of 1,
|
|
1191
|
-
# so strict < is safe here. In case sample_rate is a boolean, cast it
|
|
1192
|
-
# to a float (True becomes 1.0 and False becomes 0.0)
|
|
1193
|
-
self.sampled = self._sample_rand < self.sample_rate
|
|
1194
|
-
|
|
1195
|
-
if self.sampled:
|
|
1196
|
-
logger.debug(
|
|
1197
|
-
"[Tracing] Starting {transaction_description}".format(
|
|
1198
|
-
transaction_description=transaction_description,
|
|
1199
|
-
)
|
|
1200
|
-
)
|
|
1201
|
-
else:
|
|
1202
|
-
logger.debug(
|
|
1203
|
-
"[Tracing] Discarding {transaction_description} because it's not included in the random sample (sampling rate = {sample_rate})".format(
|
|
1204
|
-
transaction_description=transaction_description,
|
|
1205
|
-
sample_rate=self.sample_rate,
|
|
1206
|
-
)
|
|
1207
|
-
)
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
class NoOpSpan(Span):
|
|
1211
|
-
def __repr__(self):
|
|
1212
|
-
# type: () -> str
|
|
1213
|
-
return "<%s>" % self.__class__.__name__
|
|
1214
|
-
|
|
1215
|
-
@property
|
|
1216
|
-
def containing_transaction(self):
|
|
1217
|
-
# type: () -> Optional[Transaction]
|
|
1218
|
-
return None
|
|
1219
|
-
|
|
1220
|
-
def start_child(self, instrumenter=INSTRUMENTER.SENTRY, **kwargs):
|
|
1221
|
-
# type: (str, **Any) -> NoOpSpan
|
|
1222
|
-
return NoOpSpan()
|
|
1223
|
-
|
|
1224
|
-
def to_traceparent(self):
|
|
1225
|
-
# type: () -> str
|
|
1226
|
-
return ""
|
|
1227
|
-
|
|
1228
|
-
def to_baggage(self):
|
|
1229
|
-
# type: () -> Optional[Baggage]
|
|
1230
|
-
return None
|
|
1231
|
-
|
|
1232
|
-
def get_baggage(self):
|
|
1233
|
-
# type: () -> Optional[Baggage]
|
|
1234
|
-
return None
|
|
1235
|
-
|
|
1236
|
-
def iter_headers(self):
|
|
1237
|
-
# type: () -> Iterator[Tuple[str, str]]
|
|
1238
|
-
return iter(())
|
|
1239
|
-
|
|
1240
|
-
def set_tag(self, key, value):
|
|
1241
|
-
# type: (str, Any) -> None
|
|
1242
|
-
pass
|
|
1243
|
-
|
|
1244
|
-
def set_data(self, key, value):
|
|
1245
|
-
# type: (str, Any) -> None
|
|
1246
|
-
pass
|
|
1247
|
-
|
|
1248
|
-
def set_status(self, value):
|
|
1249
|
-
# type: (str) -> None
|
|
1250
|
-
pass
|
|
1251
|
-
|
|
1252
553
|
def set_http_status(self, http_status):
|
|
1253
554
|
# type: (int) -> None
|
|
1254
|
-
|
|
555
|
+
self.set_attribute(SPANDATA.HTTP_STATUS_CODE, http_status)
|
|
556
|
+
self.set_status(get_span_status_from_http_code(http_status))
|
|
1255
557
|
|
|
1256
558
|
def is_success(self):
|
|
1257
559
|
# type: () -> bool
|
|
1258
|
-
return
|
|
560
|
+
return self.status == SPANSTATUS.OK
|
|
561
|
+
|
|
562
|
+
def finish(self, end_timestamp=None):
|
|
563
|
+
# type: (Optional[Union[float, datetime]]) -> None
|
|
564
|
+
if end_timestamp is not None:
|
|
565
|
+
self._otel_span.end(convert_to_otel_timestamp(end_timestamp))
|
|
566
|
+
else:
|
|
567
|
+
self._otel_span.end()
|
|
1259
568
|
|
|
1260
569
|
def to_json(self):
|
|
1261
|
-
# type: () ->
|
|
1262
|
-
|
|
570
|
+
# type: () -> dict[str, Any]
|
|
571
|
+
"""
|
|
572
|
+
Only meant for testing. Not used internally anymore.
|
|
573
|
+
"""
|
|
574
|
+
if not isinstance(self._otel_span, ReadableSpan):
|
|
575
|
+
return {}
|
|
576
|
+
return json.loads(self._otel_span.to_json())
|
|
1263
577
|
|
|
1264
578
|
def get_trace_context(self):
|
|
1265
|
-
# type: () -> Any
|
|
1266
|
-
|
|
579
|
+
# type: () -> dict[str, Any]
|
|
580
|
+
if not isinstance(self._otel_span, ReadableSpan):
|
|
581
|
+
return {}
|
|
1267
582
|
|
|
1268
|
-
|
|
1269
|
-
# type: () -> Any
|
|
1270
|
-
return {}
|
|
583
|
+
return get_trace_context(self._otel_span)
|
|
1271
584
|
|
|
1272
|
-
def
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
end_timestamp=None, # type: Optional[Union[float, datetime]]
|
|
1276
|
-
*,
|
|
1277
|
-
hub=None, # type: Optional[sentry_sdk.Hub]
|
|
1278
|
-
):
|
|
1279
|
-
# type: (...) -> Optional[str]
|
|
1280
|
-
"""
|
|
1281
|
-
The `hub` parameter is deprecated. Please use the `scope` parameter, instead.
|
|
1282
|
-
"""
|
|
1283
|
-
pass
|
|
585
|
+
def set_context(self, key, value):
|
|
586
|
+
# type: (str, Any) -> None
|
|
587
|
+
# TODO-neel-potel we cannot add dicts here
|
|
1284
588
|
|
|
1285
|
-
|
|
1286
|
-
# type: (str, float, MeasurementUnit) -> None
|
|
1287
|
-
pass
|
|
589
|
+
self.set_attribute(f"{SentrySpanAttribute.CONTEXT}.{key}", value)
|
|
1288
590
|
|
|
1289
|
-
def
|
|
1290
|
-
# type: (str,
|
|
1291
|
-
|
|
591
|
+
def set_flag(self, flag, value):
|
|
592
|
+
# type: (str, bool) -> None
|
|
593
|
+
flag_count = self.get_attribute("_flag.count") or 0
|
|
594
|
+
if flag_count < _FLAGS_CAPACITY:
|
|
595
|
+
self.set_attribute(f"flag.evaluation.{flag}", value)
|
|
596
|
+
self.set_attribute("_flag.count", flag_count + 1)
|
|
1292
597
|
|
|
1293
|
-
def init_span_recorder(self, maxlen):
|
|
1294
|
-
# type: (int) -> None
|
|
1295
|
-
pass
|
|
1296
598
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
pass
|
|
599
|
+
# TODO-neel-potel add deprecation
|
|
600
|
+
Transaction = Span
|
|
1300
601
|
|
|
1301
602
|
|
|
1302
603
|
if TYPE_CHECKING:
|
|
@@ -1339,20 +640,3 @@ def trace(func=None):
|
|
|
1339
640
|
return start_child_span_decorator(func)
|
|
1340
641
|
else:
|
|
1341
642
|
return start_child_span_decorator
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
# Circular imports
|
|
1345
|
-
|
|
1346
|
-
from sentry_sdk.tracing_utils import (
|
|
1347
|
-
Baggage,
|
|
1348
|
-
EnvironHeaders,
|
|
1349
|
-
extract_sentrytrace_data,
|
|
1350
|
-
_generate_sample_rand,
|
|
1351
|
-
has_tracing_enabled,
|
|
1352
|
-
maybe_create_breadcrumbs_from_span,
|
|
1353
|
-
)
|
|
1354
|
-
|
|
1355
|
-
with warnings.catch_warnings():
|
|
1356
|
-
# The code in this file which uses `LocalAggregator` is only called from the deprecated `metrics` module.
|
|
1357
|
-
warnings.simplefilter("ignore", DeprecationWarning)
|
|
1358
|
-
from sentry_sdk.metrics import LocalAggregator
|