|
1 | | -import json |
2 | 1 | import os |
3 | | -import platform |
4 | 2 | import random |
5 | 3 | import socket |
6 | | -import sys |
7 | 4 | import uuid |
8 | | -from collections.abc import Iterable, Mapping |
| 5 | +from collections.abc import Mapping |
9 | 6 | from contextvars import ContextVar |
10 | 7 | from datetime import datetime, timezone |
11 | 8 | from importlib import import_module |
12 | | -from typing import TYPE_CHECKING, Dict, List, cast, overload |
| 9 | +from typing import TYPE_CHECKING, Dict, cast, overload |
13 | 10 |
|
14 | 11 | from sentry_sdk._compat import check_uwsgi_thread_support |
15 | 12 | from sentry_sdk._log_batcher import LogBatcher |
|
18 | 15 | from sentry_sdk.consts import ( |
19 | 16 | DEFAULT_MAX_VALUE_LENGTH, |
20 | 17 | DEFAULT_OPTIONS, |
21 | | - SPANSTATUS, |
22 | 18 | VERSION, |
23 | 19 | ClientConstructor, |
24 | 20 | ) |
|
34 | 30 | from sentry_sdk.scrubber import EventScrubber |
35 | 31 | from sentry_sdk.serializer import serialize |
36 | 32 | from sentry_sdk.sessions import SessionFlusher |
37 | | -from sentry_sdk.traces import SpanStatus, StreamedSpan, trace |
| 33 | +from sentry_sdk.traces import StreamedSpan, trace |
38 | 34 | from sentry_sdk.transport import ( |
39 | 35 | AsyncHttpTransport, |
40 | 36 | HttpTransportCore, |
|
44 | 40 | AnnotatedValue, |
45 | 41 | capture_internal_exceptions, |
46 | 42 | current_stacktrace, |
47 | | - datetime_from_isoformat, |
48 | 43 | env_to_bool, |
49 | 44 | format_timestamp, |
50 | 45 | get_before_send_log, |
|
70 | 65 | Log, |
71 | 66 | Metric, |
72 | 67 | SDKInfo, |
73 | | - SerializedAttributeValue, |
74 | 68 | ) |
75 | 69 | from sentry_sdk.integrations import Integration |
76 | 70 | from sentry_sdk.scope import Scope |
|
92 | 86 | } |
93 | 87 |
|
94 | 88 |
|
95 | | -def _serialized_v1_attribute_to_serialized_v2_attribute( |
96 | | - attribute_value: "Any", |
97 | | -) -> "Optional[SerializedAttributeValue]": |
98 | | - if isinstance(attribute_value, bool): |
99 | | - return { |
100 | | - "value": attribute_value, |
101 | | - "type": "boolean", |
102 | | - } |
103 | | - |
104 | | - if isinstance(attribute_value, int): |
105 | | - return { |
106 | | - "value": attribute_value, |
107 | | - "type": "integer", |
108 | | - } |
109 | | - |
110 | | - if isinstance(attribute_value, float): |
111 | | - return { |
112 | | - "value": attribute_value, |
113 | | - "type": "double", |
114 | | - } |
115 | | - |
116 | | - if isinstance(attribute_value, str): |
117 | | - return { |
118 | | - "value": attribute_value, |
119 | | - "type": "string", |
120 | | - } |
121 | | - |
122 | | - if isinstance(attribute_value, list): |
123 | | - if not attribute_value: |
124 | | - return {"value": [], "type": "array"} |
125 | | - |
126 | | - ty = type(attribute_value[0]) |
127 | | - if ty in (int, str, bool, float) and all( |
128 | | - type(v) is ty for v in attribute_value |
129 | | - ): |
130 | | - return { |
131 | | - "value": attribute_value, |
132 | | - "type": "array", |
133 | | - } |
134 | | - |
135 | | - # Types returned when the serializer for V1 span attributes recurses into some container types. |
136 | | - if isinstance(attribute_value, (dict, list)): |
137 | | - return { |
138 | | - "value": json.dumps(attribute_value), |
139 | | - "type": "string", |
140 | | - } |
141 | | - |
142 | | - return None |
143 | | - |
144 | | - |
145 | | -def _serialized_v1_span_to_serialized_v2_span( |
146 | | - span: "dict[str, Any]", event: "Event" |
147 | | -) -> "dict[str, Any]": |
148 | | - # See SpanBatcher._to_transport_format() for analogous population of all entries except "attributes". |
149 | | - res: "dict[str, Any]" = { |
150 | | - "status": SpanStatus.OK.value, |
151 | | - "is_segment": False, |
152 | | - } |
153 | | - |
154 | | - if "trace_id" in span: |
155 | | - res["trace_id"] = span["trace_id"] |
156 | | - |
157 | | - if "span_id" in span: |
158 | | - res["span_id"] = span["span_id"] |
159 | | - |
160 | | - if "description" in span: |
161 | | - description = span["description"] |
162 | | - |
163 | | - if description is None and "op" in span: |
164 | | - description = span["op"] |
165 | | - |
166 | | - res["name"] = description |
167 | | - |
168 | | - if "start_timestamp" in span: |
169 | | - start_timestamp = None |
170 | | - try: |
171 | | - start_timestamp = datetime_from_isoformat(span["start_timestamp"]) |
172 | | - except Exception: |
173 | | - pass |
174 | | - |
175 | | - if start_timestamp is not None: |
176 | | - res["start_timestamp"] = start_timestamp.timestamp() |
177 | | - |
178 | | - if "timestamp" in span: |
179 | | - end_timestamp = None |
180 | | - try: |
181 | | - end_timestamp = datetime_from_isoformat(span["timestamp"]) |
182 | | - except Exception: |
183 | | - pass |
184 | | - |
185 | | - if end_timestamp is not None: |
186 | | - res["end_timestamp"] = end_timestamp.timestamp() |
187 | | - |
188 | | - if "parent_span_id" in span: |
189 | | - res["parent_span_id"] = span["parent_span_id"] |
190 | | - |
191 | | - if "status" in span and span["status"] != SPANSTATUS.OK: |
192 | | - res["status"] = "error" |
193 | | - |
194 | | - attributes: "Dict[str, Any]" = {} |
195 | | - |
196 | | - if "op" in span: |
197 | | - attributes["sentry.op"] = span["op"] |
198 | | - if "origin" in span: |
199 | | - attributes["sentry.origin"] = span["origin"] |
200 | | - |
201 | | - span_data = span.get("data") |
202 | | - if isinstance(span_data, dict): |
203 | | - attributes.update(span_data) |
204 | | - |
205 | | - span_tags = span.get("tags") |
206 | | - if isinstance(span_tags, dict): |
207 | | - attributes.update(span_tags) |
208 | | - |
209 | | - # See Scope._apply_user_attributes_to_telemetry() for user attributes. |
210 | | - user = event.get("user") |
211 | | - if isinstance(user, dict): |
212 | | - if "id" in user: |
213 | | - attributes["user.id"] = user["id"] |
214 | | - if "username" in user: |
215 | | - attributes["user.name"] = user["username"] |
216 | | - if "email" in user: |
217 | | - attributes["user.email"] = user["email"] |
218 | | - |
219 | | - # See Scope.set_global_attributes() for release, environment, and SDK metadata. |
220 | | - if "release" in event: |
221 | | - attributes["sentry.release"] = event["release"] |
222 | | - if "environment" in event: |
223 | | - attributes["sentry.environment"] = event["environment"] |
224 | | - if "server_name" in event: |
225 | | - attributes["server.address"] = event["server_name"] |
226 | | - if "transaction" in event: |
227 | | - attributes["sentry.segment.name"] = event["transaction"] |
228 | | - |
229 | | - trace_context = event.get("contexts", {}).get("trace", {}) |
230 | | - if "span_id" in trace_context: |
231 | | - attributes["sentry.segment.id"] = trace_context["span_id"] |
232 | | - |
233 | | - sdk_info = event.get("sdk") |
234 | | - if isinstance(sdk_info, dict): |
235 | | - if "name" in sdk_info: |
236 | | - attributes["sentry.sdk.name"] = sdk_info["name"] |
237 | | - if "version" in sdk_info: |
238 | | - attributes["sentry.sdk.version"] = sdk_info["version"] |
239 | | - |
240 | | - attributes["process.runtime.name"] = platform.python_implementation() |
241 | | - attributes["process.runtime.version"] = ( |
242 | | - f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" |
243 | | - ) |
244 | | - |
245 | | - if not attributes: |
246 | | - return res |
247 | | - |
248 | | - res["attributes"] = {} |
249 | | - for key, value in attributes.items(): |
250 | | - converted_value = _serialized_v1_attribute_to_serialized_v2_attribute(value) |
251 | | - if converted_value is None: |
252 | | - continue |
253 | | - |
254 | | - res["attributes"][key] = converted_value |
255 | | - |
256 | | - # Remove redundant attribute, as status is stored in the status field. |
257 | | - if "status" in res["attributes"]: |
258 | | - del res["attributes"]["status"] |
259 | | - |
260 | | - return res |
261 | | - |
262 | | - |
263 | | -def _split_gen_ai_spans( |
264 | | - event_opt: "Event", |
265 | | -) -> "Optional[tuple[List[Dict[str, object]], List[Dict[str, object]]]]": |
266 | | - if "spans" not in event_opt: |
267 | | - return None |
268 | | - |
269 | | - spans: "Any" = event_opt["spans"] |
270 | | - if isinstance(spans, AnnotatedValue): |
271 | | - spans = spans.value |
272 | | - |
273 | | - if not isinstance(spans, Iterable): |
274 | | - return None |
275 | | - |
276 | | - non_gen_ai_spans = [] |
277 | | - gen_ai_spans = [] |
278 | | - for span in spans: |
279 | | - if not isinstance(span, dict): |
280 | | - non_gen_ai_spans.append(span) |
281 | | - continue |
282 | | - |
283 | | - span_op = span.get("op") |
284 | | - if isinstance(span_op, str) and span_op.startswith("gen_ai."): |
285 | | - gen_ai_spans.append(span) |
286 | | - else: |
287 | | - non_gen_ai_spans.append(span) |
288 | | - |
289 | | - return non_gen_ai_spans, gen_ai_spans |
290 | | - |
291 | | - |
292 | 89 | def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]": |
293 | 90 | if args and (isinstance(args[0], (bytes, str)) or args[0] is None): |
294 | 91 | dsn: "Optional[str]" = args[0] |
|
0 commit comments