Skip to content

Commit 842255d

Browse files
committed
Cleanup op, description and status mapping
1 parent 1bb3dcb commit 842255d

File tree

5 files changed

+108
-137
lines changed

5 files changed

+108
-137
lines changed

sentry_sdk/_types.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,5 +262,3 @@ class SDKInfo(TypedDict):
262262
)
263263

264264
HttpStatusCodeRange = Union[int, Container[int]]
265-
266-
OtelExtractedSpanData = tuple[str, str, Optional[str], Optional[int], Optional[str]]

sentry_sdk/consts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ class OP:
674674
HTTP_CLIENT = "http.client"
675675
HTTP_CLIENT_STREAM = "http.client.stream"
676676
HTTP_SERVER = "http.server"
677+
MESSAGE = "message"
677678
MIDDLEWARE_DJANGO = "middleware.django"
678679
MIDDLEWARE_LITESTAR = "middleware.litestar"
679680
MIDDLEWARE_LITESTAR_RECEIVE = "middleware.litestar.receive"
@@ -705,6 +706,7 @@ class OP:
705706
QUEUE_TASK_HUEY = "queue.task.huey"
706707
QUEUE_SUBMIT_RAY = "queue.submit.ray"
707708
QUEUE_TASK_RAY = "queue.task.ray"
709+
RPC = "rpc"
708710
SUBPROCESS = "subprocess"
709711
SUBPROCESS_WAIT = "subprocess.wait"
710712
SUBPROCESS_COMMUNICATE = "subprocess.communicate"

sentry_sdk/opentelemetry/span_processor.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -219,18 +219,16 @@ def _root_span_to_transaction_event(self, span: ReadableSpan) -> Optional[Event]
219219
if profile_context:
220220
contexts["profile"] = profile_context
221221

222-
(_, description, _, http_status, _) = span_data
223-
224-
if http_status:
225-
contexts["response"] = {"status_code": http_status}
222+
if span_data.http_status:
223+
contexts["response"] = {"status_code": span_data.http_status}
226224

227225
if span.resource.attributes:
228226
contexts[OTEL_SENTRY_CONTEXT] = {"resource": dict(span.resource.attributes)}
229227

230228
event.update(
231229
{
232230
"type": "transaction",
233-
"transaction": transaction_name or description,
231+
"transaction": transaction_name or span_data.description,
234232
"transaction_info": {"source": transaction_source or "custom"},
235233
"contexts": contexts,
236234
}
@@ -257,19 +255,21 @@ def _span_to_json(self, span: ReadableSpan) -> Optional[dict[str, Any]]:
257255
span_id = format_span_id(span.context.span_id)
258256
parent_span_id = format_span_id(span.parent.span_id) if span.parent else None
259257

260-
(op, description, status, _, origin) = extract_span_data(span)
258+
span_data = extract_span_data(span)
261259

262260
span_json.update(
263261
{
264262
"trace_id": trace_id,
265263
"span_id": span_id,
266-
"op": op,
267-
"description": description,
268-
"status": status,
269-
"origin": origin or DEFAULT_SPAN_ORIGIN,
264+
"description": span_data.description,
265+
"origin": span_data.origin or DEFAULT_SPAN_ORIGIN,
270266
}
271267
)
272268

269+
if span_data.op:
270+
span_json["op"] = span_data.op
271+
if span_data.status:
272+
span_json["status"] = span_data.status
273273
if parent_span_id:
274274
span_json["parent_span_id"] = parent_span_id
275275

sentry_sdk/opentelemetry/utils.py

Lines changed: 95 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22
import re
33
from datetime import datetime, timezone
4+
from dataclasses import dataclass
45

56
from urllib3.util import parse_url as urlparse
67
from urllib.parse import quote, unquote
@@ -30,8 +31,7 @@
3031
from sentry_sdk._types import TYPE_CHECKING
3132

3233
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
3535

3636
T = TypeVar("T")
3737

@@ -111,115 +111,112 @@ def extract_transaction_name_source(
111111
)
112112

113113

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
118133
origin = None
119-
if span.attributes is None:
120-
return (op, description, status, http_status, origin)
121134

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)
147139

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 infer_description(span)
143+
# this is if someone used the setter span.name= later after starting it
144+
or get_typed_attribute(span.attributes, SentrySpanAttribute.NAME, str)
156145
)
157146

158-
faas_trigger = span.attributes.get(SpanAttributes.FAAS_TRIGGER)
159-
if faas_trigger:
160-
return (str(faas_trigger), description, status, http_status, origin)
147+
origin = get_typed_attribute(span.attributes, SentrySpanAttribute.ORIGIN, str)
161148

162-
return (op, description, status, http_status, origin)
149+
# TODO status cleanup
150+
(status, http_status) = extract_span_status(span)
163151

152+
return ExtractedSpanData(
153+
description=description or span.name,
154+
op=op,
155+
status=status,
156+
http_status=http_status,
157+
origin=origin,
158+
)
164159

165-
def span_data_for_http_method(span: ReadableSpan) -> OtelExtractedSpanData:
166-
span_attributes = span.attributes or {}
167160

168-
op = get_typed_attribute(span_attributes, SentrySpanAttribute.OP, str)
169-
if op is None:
170-
op = "http"
161+
def infer_op(span: ReadableSpan) -> Optional[str]:
162+
"""
163+
Try to infer op for the various types of instrumentation.
164+
"""
165+
if span.attributes is None:
166+
return None
171167

168+
if SpanAttributes.HTTP_METHOD in span.attributes:
169+
op = "http"
172170
if span.kind == SpanKind.SERVER:
173171
op += ".server"
174172
elif span.kind == SpanKind.CLIENT:
175173
op += ".client"
174+
return op
175+
elif SpanAttributes.DB_SYSTEM in span.attributes:
176+
return OP.DB
177+
elif SpanAttributes.RPC_SERVICE in span.attributes:
178+
return OP.RPC
179+
elif SpanAttributes.MESSAGING_SYSTEM in span.attributes:
180+
return OP.MESSAGE
181+
elif SpanAttributes.FAAS_TRIGGER in span.attributes:
182+
return get_typed_attribute(span.attributes, SpanAttributes.FAAS_TRIGGER, str)
183+
else:
184+
return None
176185

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)
181186

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}"
187+
def infer_description(span: ReadableSpan) -> Optional[str]:
188+
if span.attributes is None:
189+
return None
190+
191+
if SpanAttributes.HTTP_METHOD in span.attributes:
192+
http_method = get_typed_attribute(
193+
span.attributes, SpanAttributes.HTTP_METHOD, str
194+
)
195+
route = get_typed_attribute(span.attributes, SpanAttributes.HTTP_ROUTE, str)
196+
target = get_typed_attribute(span.attributes, SpanAttributes.HTTP_TARGET, str)
197+
peer_name = get_typed_attribute(
198+
span.attributes, SpanAttributes.NET_PEER_NAME, str
199+
)
200+
url = get_typed_attribute(span.attributes, SpanAttributes.HTTP_URL, str)
188201

189202
if route:
190-
description = f"{http_method} {route}"
203+
return f"{http_method} {route}"
191204
elif target:
192-
description = f"{http_method} {target}"
205+
return f"{http_method} {target}"
193206
elif peer_name:
194-
description = f"{http_method} {peer_name}"
207+
return f"{http_method} {peer_name}"
208+
elif url:
209+
parsed_url = urlparse(url)
210+
url = "{}://{}{}".format(
211+
parsed_url.scheme, parsed_url.netloc, parsed_url.path
212+
)
213+
return f"{http_method} {url}"
195214
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)
215+
return http_method
216+
elif SpanAttributes.DB_SYSTEM in span.attributes:
217+
return get_typed_attribute(span.attributes, SpanAttributes.DB_STATEMENT, str)
218+
else:
219+
return None
223220

224221

225222
def extract_span_status(span: ReadableSpan) -> tuple[Optional[str], Optional[int]]:
@@ -258,17 +255,7 @@ def extract_span_status(span: ReadableSpan) -> tuple[Optional[str], Optional[int
258255

259256

260257
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-
],
258+
span_attributes: Mapping[str, Any],
272259
) -> tuple[Optional[str], Optional[int]]:
273260
http_status = get_http_status_code(span_attributes)
274261

@@ -282,19 +269,7 @@ def infer_status_from_attributes(
282269
return (None, None)
283270

284271

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]:
272+
def get_http_status_code(span_attributes: Mapping[str, Any]) -> Optional[int]:
298273
try:
299274
http_status = get_typed_attribute(
300275
span_attributes, SpanAttributes.HTTP_RESPONSE_STATUS_CODE, int
@@ -329,7 +304,7 @@ def extract_span_attributes(span: ReadableSpan, namespace: str) -> dict[str, Any
329304

330305

331306
def get_trace_context(
332-
span: ReadableSpan, span_data: Optional[OtelExtractedSpanData] = None
307+
span: ReadableSpan, span_data: Optional[ExtractedSpanData] = None
333308
) -> dict[str, Any]:
334309
if not span.context:
335310
return {}
@@ -341,27 +316,23 @@ def get_trace_context(
341316
if span_data is None:
342317
span_data = extract_span_data(span)
343318

344-
(op, _, status, _, origin) = span_data
345-
346319
trace_context: dict[str, Any] = {
347320
"trace_id": trace_id,
348321
"span_id": span_id,
349322
"parent_span_id": parent_span_id,
350-
"op": op,
351-
"origin": origin or DEFAULT_SPAN_ORIGIN,
323+
"origin": span_data.origin or DEFAULT_SPAN_ORIGIN,
352324
}
353325

354-
if status:
355-
trace_context["status"] = status
356-
326+
if span_data.op:
327+
trace_context["op"] = span_data.op
328+
if span_data.status:
329+
trace_context["status"] = span_data.status
357330
if span.attributes:
358331
trace_context["data"] = dict(span.attributes)
359332

360333
trace_state = get_trace_state(span)
361334
trace_context["dynamic_sampling_context"] = dsc_from_trace_state(trace_state)
362335

363-
# TODO-neel-potel profiler thread_id, thread_name
364-
365336
return trace_context
366337

367338

sentry_sdk/tracing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def __init__(
187187
# OTel timestamps have nanosecond precision
188188
start_timestamp = convert_to_otel_timestamp(start_timestamp)
189189

190-
span_name = name or description or op or DEFAULT_SPAN_NAME
190+
span_name = name or description or DEFAULT_SPAN_NAME
191191

192192
# Prepopulate some attrs so that they're accessible in traces_sampler
193193
attributes = attributes or {}

0 commit comments

Comments
 (0)