1
1
from __future__ import annotations
2
2
import re
3
3
from datetime import datetime , timezone
4
+ from dataclasses import dataclass
4
5
5
6
from urllib3 .util import parse_url as urlparse
6
7
from urllib .parse import quote , unquote
30
31
from sentry_sdk ._types import TYPE_CHECKING
31
32
32
33
if TYPE_CHECKING :
33
- from typing import Any , Optional , Mapping , Sequence , Union , Type , TypeVar
34
- from sentry_sdk ._types import OtelExtractedSpanData
34
+ from typing import Any , Optional , Mapping , Union , Type , TypeVar
35
35
36
36
T = TypeVar ("T" )
37
37
@@ -111,115 +111,111 @@ def extract_transaction_name_source(
111
111
)
112
112
113
113
114
- def extract_span_data (span : ReadableSpan ) -> OtelExtractedSpanData :
115
- op = span .name
116
- description = span .name
117
- status , http_status = extract_span_status (span )
114
+ @dataclass
115
+ class ExtractedSpanData :
116
+ description : str
117
+ op : Optional [str ] = None
118
+ status : Optional [str ] = None
119
+ http_status : Optional [int ] = None
120
+ origin : Optional [str ] = None
121
+
122
+
123
+ def extract_span_data (span : ReadableSpan ) -> ExtractedSpanData :
124
+ """
125
+ Try to populate sane values for op, description and statuses based on what we have.
126
+ The op and description mapping is fundamentally janky because otel only has a single `name`.
127
+
128
+ Priority is given first to attributes explicitly defined by us via the SDK.
129
+ Otherwise we try to infer sane values from other attributes.
130
+ """
131
+ op = None
132
+ description = None
118
133
origin = None
119
- if span .attributes is None :
120
- return (op , description , status , http_status , origin )
121
134
122
- attribute_op = get_typed_attribute (span .attributes , SentrySpanAttribute .OP , str )
123
- op = attribute_op or op
124
- description = (
125
- get_typed_attribute (span .attributes , SentrySpanAttribute .DESCRIPTION , str )
126
- or description
127
- )
128
- origin = get_typed_attribute (span .attributes , SentrySpanAttribute .ORIGIN , str )
129
-
130
- http_method = get_typed_attribute (span .attributes , SpanAttributes .HTTP_METHOD , str )
131
- if http_method :
132
- return span_data_for_http_method (span )
133
-
134
- db_query = span .attributes .get (SpanAttributes .DB_SYSTEM )
135
- if db_query :
136
- return span_data_for_db_query (span )
137
-
138
- rpc_service = span .attributes .get (SpanAttributes .RPC_SERVICE )
139
- if rpc_service :
140
- return (
141
- attribute_op or "rpc" ,
142
- description ,
143
- status ,
144
- http_status ,
145
- origin ,
146
- )
135
+ if span .attributes is not None :
136
+ op = get_typed_attribute (
137
+ span .attributes , SentrySpanAttribute .OP , str
138
+ ) or infer_op (span )
147
139
148
- messaging_system = span .attributes .get (SpanAttributes .MESSAGING_SYSTEM )
149
- if messaging_system :
150
- return (
151
- attribute_op or "message" ,
152
- description ,
153
- status ,
154
- http_status ,
155
- origin ,
140
+ description = (
141
+ get_typed_attribute (span .attributes , SentrySpanAttribute .DESCRIPTION , str )
142
+ or get_typed_attribute (span .attributes , SentrySpanAttribute .NAME , str )
143
+ or infer_description (span )
156
144
)
157
145
158
- faas_trigger = span .attributes .get (SpanAttributes .FAAS_TRIGGER )
159
- if faas_trigger :
160
- return (str (faas_trigger ), description , status , http_status , origin )
146
+ origin = get_typed_attribute (span .attributes , SentrySpanAttribute .ORIGIN , str )
161
147
162
- return (op , description , status , http_status , origin )
148
+ # TODO status cleanup
149
+ (status , http_status ) = extract_span_status (span )
163
150
151
+ return ExtractedSpanData (
152
+ description = description or span .name ,
153
+ op = op ,
154
+ status = status ,
155
+ http_status = http_status ,
156
+ origin = origin ,
157
+ )
164
158
165
- def span_data_for_http_method (span : ReadableSpan ) -> OtelExtractedSpanData :
166
- span_attributes = span .attributes or {}
167
159
168
- op = get_typed_attribute (span_attributes , SentrySpanAttribute .OP , str )
169
- if op is None :
170
- op = "http"
160
+ def infer_op (span : ReadableSpan ) -> Optional [str ]:
161
+ """
162
+ Try to infer op for the various types of instrumentation.
163
+ """
164
+ if span .attributes is None :
165
+ return None
171
166
167
+ if SpanAttributes .HTTP_METHOD in span .attributes :
168
+ op = "http"
172
169
if span .kind == SpanKind .SERVER :
173
170
op += ".server"
174
171
elif span .kind == SpanKind .CLIENT :
175
172
op += ".client"
173
+ return op
174
+ elif SpanAttributes .DB_SYSTEM in span .attributes :
175
+ return OP .DB
176
+ elif SpanAttributes .RPC_SERVICE in span .attributes :
177
+ return OP .RPC
178
+ elif SpanAttributes .MESSAGING_SYSTEM in span .attributes :
179
+ return OP .MESSAGE
180
+ elif SpanAttributes .FAAS_TRIGGER in span .attributes :
181
+ return get_typed_attribute (span .attributes , SpanAttributes .FAAS_TRIGGER , str )
182
+ else :
183
+ return None
176
184
177
- http_method = span_attributes .get (SpanAttributes .HTTP_METHOD )
178
- route = span_attributes .get (SpanAttributes .HTTP_ROUTE )
179
- target = span_attributes .get (SpanAttributes .HTTP_TARGET )
180
- peer_name = span_attributes .get (SpanAttributes .NET_PEER_NAME )
181
185
182
- # TODO-neel-potel remove description completely
183
- description = get_typed_attribute (
184
- span_attributes , SentrySpanAttribute .DESCRIPTION , str
185
- ) or get_typed_attribute (span_attributes , SentrySpanAttribute .NAME , str )
186
- if description is None :
187
- description = f"{ http_method } "
186
+ def infer_description (span : ReadableSpan ) -> Optional [str ]:
187
+ if span .attributes is None :
188
+ return None
189
+
190
+ if SpanAttributes .HTTP_METHOD in span .attributes :
191
+ http_method = get_typed_attribute (
192
+ span .attributes , SpanAttributes .HTTP_METHOD , str
193
+ )
194
+ route = get_typed_attribute (span .attributes , SpanAttributes .HTTP_ROUTE , str )
195
+ target = get_typed_attribute (span .attributes , SpanAttributes .HTTP_TARGET , str )
196
+ peer_name = get_typed_attribute (
197
+ span .attributes , SpanAttributes .NET_PEER_NAME , str
198
+ )
199
+ url = get_typed_attribute (span .attributes , SpanAttributes .HTTP_URL , str )
188
200
189
201
if route :
190
- description = f"{ http_method } { route } "
202
+ return f"{ http_method } { route } "
191
203
elif target :
192
- description = f"{ http_method } { target } "
204
+ return f"{ http_method } { target } "
193
205
elif peer_name :
194
- description = f"{ http_method } { peer_name } "
206
+ return f"{ http_method } { peer_name } "
207
+ elif url :
208
+ parsed_url = urlparse (url )
209
+ url = "{}://{}{}" .format (
210
+ parsed_url .scheme , parsed_url .netloc , parsed_url .path
211
+ )
212
+ return f"{ http_method } { url } "
195
213
else :
196
- url = span_attributes .get (SpanAttributes .HTTP_URL )
197
- url = get_typed_attribute (span_attributes , SpanAttributes .HTTP_URL , str )
198
-
199
- if url :
200
- parsed_url = urlparse (url )
201
- url = "{}://{}{}" .format (
202
- parsed_url .scheme , parsed_url .netloc , parsed_url .path
203
- )
204
- description = f"{ http_method } { url } "
205
-
206
- status , http_status = extract_span_status (span )
207
-
208
- origin = get_typed_attribute (span_attributes , SentrySpanAttribute .ORIGIN , str )
209
-
210
- return (op , description , status , http_status , origin )
211
-
212
-
213
- def span_data_for_db_query (span : ReadableSpan ) -> OtelExtractedSpanData :
214
- span_attributes = span .attributes or {}
215
-
216
- op = get_typed_attribute (span_attributes , SentrySpanAttribute .OP , str ) or OP .DB
217
- statement = get_typed_attribute (span_attributes , SpanAttributes .DB_STATEMENT , str )
218
-
219
- description = statement or span .name
220
- origin = get_typed_attribute (span_attributes , SentrySpanAttribute .ORIGIN , str )
221
-
222
- return (op , description , None , None , origin )
214
+ return http_method
215
+ elif SpanAttributes .DB_SYSTEM in span .attributes :
216
+ return get_typed_attribute (span .attributes , SpanAttributes .DB_STATEMENT , str )
217
+ else :
218
+ return None
223
219
224
220
225
221
def extract_span_status (span : ReadableSpan ) -> tuple [Optional [str ], Optional [int ]]:
@@ -258,17 +254,7 @@ def extract_span_status(span: ReadableSpan) -> tuple[Optional[str], Optional[int
258
254
259
255
260
256
def infer_status_from_attributes (
261
- span_attributes : Mapping [
262
- str ,
263
- str
264
- | bool
265
- | int
266
- | float
267
- | Sequence [str ]
268
- | Sequence [bool ]
269
- | Sequence [int ]
270
- | Sequence [float ],
271
- ],
257
+ span_attributes : Mapping [str , Any ],
272
258
) -> tuple [Optional [str ], Optional [int ]]:
273
259
http_status = get_http_status_code (span_attributes )
274
260
@@ -282,19 +268,7 @@ def infer_status_from_attributes(
282
268
return (None , None )
283
269
284
270
285
- def get_http_status_code (
286
- span_attributes : Mapping [
287
- str ,
288
- str
289
- | bool
290
- | int
291
- | float
292
- | Sequence [str ]
293
- | Sequence [bool ]
294
- | Sequence [int ]
295
- | Sequence [float ],
296
- ],
297
- ) -> Optional [int ]:
271
+ def get_http_status_code (span_attributes : Mapping [str , Any ]) -> Optional [int ]:
298
272
try :
299
273
http_status = get_typed_attribute (
300
274
span_attributes , SpanAttributes .HTTP_RESPONSE_STATUS_CODE , int
@@ -329,7 +303,7 @@ def extract_span_attributes(span: ReadableSpan, namespace: str) -> dict[str, Any
329
303
330
304
331
305
def get_trace_context (
332
- span : ReadableSpan , span_data : Optional [OtelExtractedSpanData ] = None
306
+ span : ReadableSpan , span_data : Optional [ExtractedSpanData ] = None
333
307
) -> dict [str , Any ]:
334
308
if not span .context :
335
309
return {}
@@ -341,27 +315,23 @@ def get_trace_context(
341
315
if span_data is None :
342
316
span_data = extract_span_data (span )
343
317
344
- (op , _ , status , _ , origin ) = span_data
345
-
346
318
trace_context : dict [str , Any ] = {
347
319
"trace_id" : trace_id ,
348
320
"span_id" : span_id ,
349
321
"parent_span_id" : parent_span_id ,
350
- "op" : op ,
351
- "origin" : origin or DEFAULT_SPAN_ORIGIN ,
322
+ "origin" : span_data .origin or DEFAULT_SPAN_ORIGIN ,
352
323
}
353
324
354
- if status :
355
- trace_context ["status" ] = status
356
-
325
+ if span_data .op :
326
+ trace_context ["op" ] = span_data .op
327
+ if span_data .status :
328
+ trace_context ["status" ] = span_data .status
357
329
if span .attributes :
358
330
trace_context ["data" ] = dict (span .attributes )
359
331
360
332
trace_state = get_trace_state (span )
361
333
trace_context ["dynamic_sampling_context" ] = dsc_from_trace_state (trace_state )
362
334
363
- # TODO-neel-potel profiler thread_id, thread_name
364
-
365
335
return trace_context
366
336
367
337
0 commit comments