1- from collections .abc import MutableMapping
21from typing import Any , cast
32
43import orjson
1312I64_MAX = 2 ** 63 - 1
1413
1514FIELD_TO_ATTRIBUTE = {
16- "description" : "sentry.raw_description" ,
17- "duration_ms" : "sentry.duration_ms" ,
18- "is_segment" : "sentry.is_segment" ,
19- "exclusive_time_ms" : "sentry.exclusive_time_ms" ,
20- "start_timestamp_precise" : "sentry.start_timestamp_precise" ,
21- "end_timestamp_precise" : "sentry.end_timestamp_precise" ,
15+ "end_timestamp" : "sentry.end_timestamp_precise" ,
16+ "event_id" : "sentry.event_id" ,
17+ "hash" : "sentry.hash" ,
2218 "is_remote" : "sentry.is_remote" ,
19+ "kind" : "sentry.kind" ,
20+ "name" : "sentry.name" ,
2321 "parent_span_id" : "sentry.parent_span_id" ,
24- "profile_id" : "sentry.profile_id" ,
25- "segment_id" : "sentry.segment_id" ,
2622 "received" : "sentry.received" ,
27- "origin" : "sentry.origin" ,
28- "kind" : "sentry.kind" ,
29- "hash" : "sentry.hash" ,
30- "event_id" : "sentry.event_id" ,
23+ "start_timestamp" : "sentry.start_timestamp_precise" ,
24+ }
25+
26+ RENAME_ATTRIBUTES = {
27+ "sentry.description" : "sentry.raw_description" ,
28+ "sentry.segment.id" : "sentry.segment_id" ,
3129}
3230
3331
3432def convert_span_to_item (span : CompatibleSpan ) -> TraceItem :
35- attributes : MutableMapping [str , AnyValue ] = {} # TODO
33+ attributes : dict [str , AnyValue ] = {}
3634
3735 client_sample_rate = 1.0
3836 server_sample_rate = 1.0
3937
40- # This key is ambiguous. sentry-conventions and relay interpret it as "raw description",
41- # sentry interprets it as normalized_description.
42- # See https://github.com/getsentry/sentry/blob/7f2ccd1d03e8845a833fe1ee6784bce0c7f0b935/src/sentry/search/eap/spans/attributes.py#L596.
43- # Delete it and relay on top-level `description` for now.
44- (span .get ("data" ) or {}).pop ("sentry.description" , None )
45-
46- for k , v in (span .get ("data" ) or {}).items ():
47- if v is not None :
48- try :
49- attributes [k ] = _anyvalue (v )
50- except Exception :
51- sentry_sdk .capture_exception ()
52- else :
53- if k == "sentry.client_sample_rate" :
54- try :
55- client_sample_rate = float (v )
56- except ValueError :
57- pass
58- elif k == "sentry.server_sample_rate" :
59- try :
60- server_sample_rate = float (v )
61- except ValueError :
62- pass
38+ for k , attribute in (span .get ("attributes" ) or {}).items ():
39+ if attribute is None :
40+ continue
41+ if (value := attribute .get ("value" )) is None :
42+ continue
43+ try :
44+ # NOTE: This ignores the `type` field of the attribute itself
45+ attributes [k ] = _anyvalue (value )
46+ except Exception :
47+ sentry_sdk .capture_exception ()
48+ else :
49+ if k == "sentry.client_sample_rate" :
50+ try :
51+ client_sample_rate = float (value ) # type:ignore[arg-type]
52+ except ValueError :
53+ pass
54+ elif k == "sentry.server_sample_rate" :
55+ try :
56+ server_sample_rate = float (value ) # type:ignore[arg-type]
57+ except ValueError :
58+ pass
6359
6460 for field_name , attribute_name in FIELD_TO_ATTRIBUTE .items ():
65- v = span .get (field_name )
66- if v is not None :
67- attributes [attribute_name ] = _anyvalue (v )
61+ attribute = span .get (field_name ) # type:ignore[assignment]
62+ if attribute is not None :
63+ attributes [attribute_name ] = _anyvalue (attribute )
64+
65+ # Rename some attributes from their sentry-conventions name to what the product currently expects.
66+ # Eventually this should all be handled by deprecation policies in sentry-conventions.
67+ for convention_name , eap_name in RENAME_ATTRIBUTES .items ():
68+ if convention_name in attributes :
69+ attributes [eap_name ] = attributes .pop (convention_name )
70+
71+ try :
72+ # TODO: Move this to Relay
73+ attributes ["sentry.duration_ms" ] = AnyValue (
74+ int_value = int (1000 * (span ["end_timestamp" ] - span ["start_timestamp" ]))
75+ )
76+ except Exception :
77+ sentry_sdk .capture_exception ()
6878
6979 if links := span .get ("links" ):
7080 try :
@@ -80,7 +90,7 @@ def convert_span_to_item(span: CompatibleSpan) -> TraceItem:
8090 trace_id = span ["trace_id" ],
8191 item_id = int (span ["span_id" ], 16 ).to_bytes (16 , "little" ),
8292 item_type = TraceItemType .TRACE_ITEM_TYPE_SPAN ,
83- timestamp = _timestamp (span ["start_timestamp_precise " ]),
93+ timestamp = _timestamp (span ["start_timestamp " ]),
8494 attributes = attributes ,
8595 client_sample_rate = client_sample_rate ,
8696 server_sample_rate = server_sample_rate ,
@@ -132,7 +142,10 @@ def _sanitize_span_link(link: SpanLink) -> SpanLink:
132142 # might be an intermediary state where there is a pre-existing dropped
133143 # attributes count. Respect that count, if it's present. It should always be
134144 # an integer.
135- dropped_attributes_count = attributes .get ("sentry.dropped_attributes_count" , 0 )
145+ try :
146+ dropped_attributes_count = int (attributes ["sentry.dropped_attributes_count" ]["value" ]) # type: ignore[arg-type,index]
147+ except (KeyError , ValueError , TypeError ):
148+ dropped_attributes_count = 0
136149
137150 for key , value in attributes .items ():
138151 if key in ALLOWED_LINK_ATTRIBUTE_KEYS :
@@ -141,7 +154,10 @@ def _sanitize_span_link(link: SpanLink) -> SpanLink:
141154 dropped_attributes_count += 1
142155
143156 if dropped_attributes_count > 0 :
144- allowed_attributes ["sentry.dropped_attributes_count" ] = dropped_attributes_count
157+ allowed_attributes ["sentry.dropped_attributes_count" ] = {
158+ "type" : "integer" ,
159+ "value" : dropped_attributes_count ,
160+ }
145161
146162 # Only include the `attributes` key if the key was present in the original
147163 # link, don't create a an empty object, since there is a semantic difference
0 commit comments