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