Skip to content

Commit d4b1d00

Browse files
authored
Merge branch 'master' into potel-base
2 parents de1b0e3 + b7fd54a commit d4b1d00

20 files changed

+419
-73
lines changed

sentry_sdk/_types.py

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
"profile_chunk",
156156
"metric_bucket",
157157
"monitor",
158+
"span",
158159
]
159160
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
160161

sentry_sdk/client.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ def _prepare_event(
448448

449449
if scope is not None:
450450
is_transaction = event.get("type") == "transaction"
451+
spans_before = len(event.get("spans", []))
451452
event_ = scope.apply_to_event(event, hint, self.options)
452453

453454
# one of the event/error processors returned None
@@ -457,10 +458,22 @@ def _prepare_event(
457458
"event_processor",
458459
data_category=("transaction" if is_transaction else "error"),
459460
)
461+
if is_transaction:
462+
self.transport.record_lost_event(
463+
"event_processor",
464+
data_category="span",
465+
quantity=spans_before + 1, # +1 for the transaction itself
466+
)
460467
return None
461468

462469
event = event_
463470

471+
spans_delta = spans_before - len(event.get("spans", []))
472+
if is_transaction and spans_delta > 0 and self.transport is not None:
473+
self.transport.record_lost_event(
474+
"event_processor", data_category="span", quantity=spans_delta
475+
)
476+
464477
if (
465478
self.options["attach_stacktrace"]
466479
and "exception" not in event
@@ -541,14 +554,27 @@ def _prepare_event(
541554
and event.get("type") == "transaction"
542555
):
543556
new_event = None
557+
spans_before = len(event.get("spans", []))
544558
with capture_internal_exceptions():
545559
new_event = before_send_transaction(event, hint or {})
546560
if new_event is None:
547561
logger.info("before send transaction dropped event")
548562
if self.transport:
549563
self.transport.record_lost_event(
550-
"before_send", data_category="transaction"
564+
reason="before_send", data_category="transaction"
565+
)
566+
self.transport.record_lost_event(
567+
reason="before_send",
568+
data_category="span",
569+
quantity=spans_before + 1, # +1 for the transaction itself
551570
)
571+
else:
572+
spans_delta = spans_before - len(new_event.get("spans", []))
573+
if spans_delta > 0 and self.transport is not None:
574+
self.transport.record_lost_event(
575+
reason="before_send", data_category="span", quantity=spans_delta
576+
)
577+
552578
event = new_event # type: ignore
553579

554580
return event

sentry_sdk/consts.py

+26
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,32 @@ class SPANDATA:
386386
"""
387387

388388

389+
class SPANSTATUS:
390+
"""
391+
The status of a Sentry span.
392+
393+
See: https://develop.sentry.dev/sdk/event-payloads/contexts/#trace-context
394+
"""
395+
396+
ABORTED = "aborted"
397+
ALREADY_EXISTS = "already_exists"
398+
CANCELLED = "cancelled"
399+
DATA_LOSS = "data_loss"
400+
DEADLINE_EXCEEDED = "deadline_exceeded"
401+
FAILED_PRECONDITION = "failed_precondition"
402+
INTERNAL_ERROR = "internal_error"
403+
INVALID_ARGUMENT = "invalid_argument"
404+
NOT_FOUND = "not_found"
405+
OK = "ok"
406+
OUT_OF_RANGE = "out_of_range"
407+
PERMISSION_DENIED = "permission_denied"
408+
RESOURCE_EXHAUSTED = "resource_exhausted"
409+
UNAUTHENTICATED = "unauthenticated"
410+
UNAVAILABLE = "unavailable"
411+
UNIMPLEMENTED = "unimplemented"
412+
UNKNOWN_ERROR = "unknown_error"
413+
414+
389415
class OP:
390416
ANTHROPIC_MESSAGES_CREATE = "ai.messages.create.anthropic"
391417
CACHE_GET = "cache.get"

sentry_sdk/integrations/aiohttp.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import sentry_sdk
55
from sentry_sdk.api import continue_trace
6-
from sentry_sdk.consts import OP, SPANDATA
6+
from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
77
from sentry_sdk.integrations import Integration, DidNotEnable
88
from sentry_sdk.integrations.logging import ignore_logger
99
from sentry_sdk.scope import Scope
@@ -133,7 +133,7 @@ async def sentry_app_handle(self, request, *args, **kwargs):
133133
transaction.set_http_status(e.status_code)
134134
raise
135135
except (asyncio.CancelledError, ConnectionResetError):
136-
transaction.set_status("cancelled")
136+
transaction.set_status(SPANSTATUS.CANCELLED)
137137
raise
138138
except Exception:
139139
# This will probably map to a 500 but seems like we

sentry_sdk/integrations/arq.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import sentry_sdk
44
from sentry_sdk._types import TYPE_CHECKING
5-
from sentry_sdk.consts import OP
5+
from sentry_sdk.consts import OP, SPANSTATUS
66
from sentry_sdk.integrations import DidNotEnable, Integration
77
from sentry_sdk.integrations.logging import ignore_logger
88
from sentry_sdk.scope import Scope, should_send_default_pii
@@ -119,10 +119,10 @@ def _capture_exception(exc_info):
119119

120120
if scope.transaction is not None:
121121
if exc_info[0] in ARQ_CONTROL_FLOW_EXCEPTIONS:
122-
scope.transaction.set_status("aborted")
122+
scope.transaction.set_status(SPANSTATUS.ABORTED)
123123
return
124124

125-
scope.transaction.set_status("internal_error")
125+
scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
126126

127127
event, hint = event_from_exception(
128128
exc_info,

sentry_sdk/integrations/celery/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sentry_sdk
66
from sentry_sdk import isolation_scope
77
from sentry_sdk.api import continue_trace
8-
from sentry_sdk.consts import OP, SPANDATA
8+
from sentry_sdk.consts import OP, SPANSTATUS, SPANDATA
99
from sentry_sdk.integrations import Integration, DidNotEnable
1010
from sentry_sdk.integrations.celery.beat import (
1111
_patch_beat_apply_entry,
@@ -317,7 +317,7 @@ def _inner(*args, **kwargs):
317317
origin=CeleryIntegration.origin,
318318
)
319319
transaction.name = task.name
320-
transaction.set_status("ok")
320+
transaction.set_status(SPANSTATUS.OK)
321321

322322
if transaction is None:
323323
return f(*args, **kwargs)

sentry_sdk/integrations/huey.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sentry_sdk
55
from sentry_sdk._types import TYPE_CHECKING
66
from sentry_sdk.api import continue_trace, get_baggage, get_traceparent
7-
from sentry_sdk.consts import OP
7+
from sentry_sdk.consts import OP, SPANSTATUS
88
from sentry_sdk.integrations import DidNotEnable, Integration
99
from sentry_sdk.scope import Scope, should_send_default_pii
1010
from sentry_sdk.tracing import (
@@ -109,10 +109,10 @@ def _capture_exception(exc_info):
109109
scope = Scope.get_current_scope()
110110

111111
if exc_info[0] in HUEY_CONTROL_FLOW_EXCEPTIONS:
112-
scope.transaction.set_status("aborted")
112+
scope.transaction.set_status(SPANSTATUS.ABORTED)
113113
return
114114

115-
scope.transaction.set_status("internal_error")
115+
scope.transaction.set_status(SPANSTATUS.INTERNAL_ERROR)
116116
event, hint = event_from_exception(
117117
exc_info,
118118
client_options=Scope.get_client().options,
@@ -161,7 +161,7 @@ def _sentry_execute(self, task, timestamp=None):
161161
source=TRANSACTION_SOURCE_TASK,
162162
origin=HueyIntegration.origin,
163163
)
164-
transaction.set_status("ok")
164+
transaction.set_status(SPANSTATUS.OK)
165165

166166
if not getattr(task, "_sentry_is_patched", False):
167167
task.execute = _wrap_task_execute(task.execute)

sentry_sdk/integrations/opentelemetry/span_processor.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
INVALID_TRACE_ID,
1515
)
1616
from sentry_sdk import get_client, start_transaction
17-
from sentry_sdk.consts import INSTRUMENTER
17+
from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS
1818
from sentry_sdk.integrations.opentelemetry.consts import (
1919
SENTRY_BAGGAGE_KEY,
2020
SENTRY_TRACE_KEY,
@@ -273,10 +273,10 @@ def _update_span_with_otel_status(self, sentry_span, otel_span):
273273
return
274274

275275
if otel_span.status.is_ok:
276-
sentry_span.set_status("ok")
276+
sentry_span.set_status(SPANSTATUS.OK)
277277
return
278278

279-
sentry_span.set_status("internal_error")
279+
sentry_span.set_status(SPANSTATUS.INTERNAL_ERROR)
280280

281281
def _update_span_with_otel_data(self, sentry_span, otel_span):
282282
# type: (SentrySpan, OTelSpan) -> None

sentry_sdk/integrations/pymongo.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import copy
22

33
import sentry_sdk
4-
from sentry_sdk.consts import SPANDATA, OP
4+
from sentry_sdk.consts import SPANSTATUS, SPANDATA, OP
55
from sentry_sdk.integrations import DidNotEnable, Integration
66
from sentry_sdk.scope import should_send_default_pii
77
from sentry_sdk.tracing import Span
@@ -181,7 +181,7 @@ def failed(self, event):
181181

182182
try:
183183
span = self._ongoing_operations.pop(self._operation_key(event))
184-
span.set_status("internal_error")
184+
span.set_status(SPANSTATUS.INTERNAL_ERROR)
185185
span.__exit__(None, None, None)
186186
except KeyError:
187187
return
@@ -193,7 +193,7 @@ def succeeded(self, event):
193193

194194
try:
195195
span = self._ongoing_operations.pop(self._operation_key(event))
196-
span.set_status("ok")
196+
span.set_status(SPANSTATUS.OK)
197197
span.__exit__(None, None, None)
198198
except KeyError:
199199
pass

sentry_sdk/integrations/sqlalchemy.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sentry_sdk
22
from sentry_sdk._types import TYPE_CHECKING
3-
from sentry_sdk.consts import SPANDATA
3+
from sentry_sdk.consts import SPANSTATUS, SPANDATA
44
from sentry_sdk.db.explain_plan.sqlalchemy import attach_explain_plan_to_span
55
from sentry_sdk.integrations import Integration, DidNotEnable
66
from sentry_sdk.tracing_utils import add_query_source, record_sql_queries
@@ -107,7 +107,7 @@ def _handle_error(context, *args):
107107
span = getattr(execution_context, "_sentry_sql_span", None) # type: Optional[Span]
108108

109109
if span is not None:
110-
span.set_status("internal_error")
110+
span.set_status(SPANSTATUS.INTERNAL_ERROR)
111111

112112
# _after_cursor_execute does not get called for crashing SQL stmts. Judging
113113
# from SQLAlchemy codebase it does seem like any error coming into this

sentry_sdk/tracing.py

+45-34
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import datetime, timedelta, timezone
44

55
import sentry_sdk
6-
from sentry_sdk.consts import INSTRUMENTER, SPANDATA
6+
from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA
77
from sentry_sdk.profiler.continuous_profiler import get_profiler_id
88
from sentry_sdk.utils import (
99
get_current_thread_meta,
@@ -119,11 +119,9 @@ class TransactionKwargs(SpanKwargs, total=False):
119119
},
120120
)
121121

122-
123122
BAGGAGE_HEADER_NAME = "baggage"
124123
SENTRY_TRACE_HEADER_NAME = "sentry-trace"
125124

126-
127125
# Transaction source
128126
# see https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
129127
TRANSACTION_SOURCE_CUSTOM = "custom"
@@ -151,6 +149,45 @@ class TransactionKwargs(SpanKwargs, total=False):
151149
}
152150

153151

152+
def get_span_status_from_http_code(http_status_code):
153+
# type: (int) -> str
154+
"""
155+
Returns the Sentry status corresponding to the given HTTP status code.
156+
157+
See: https://develop.sentry.dev/sdk/event-payloads/contexts/#trace-context
158+
"""
159+
if http_status_code < 400:
160+
return SPANSTATUS.OK
161+
162+
elif 400 <= http_status_code < 500:
163+
if http_status_code == 403:
164+
return SPANSTATUS.PERMISSION_DENIED
165+
elif http_status_code == 404:
166+
return SPANSTATUS.NOT_FOUND
167+
elif http_status_code == 429:
168+
return SPANSTATUS.RESOURCE_EXHAUSTED
169+
elif http_status_code == 413:
170+
return SPANSTATUS.FAILED_PRECONDITION
171+
elif http_status_code == 401:
172+
return SPANSTATUS.UNAUTHENTICATED
173+
elif http_status_code == 409:
174+
return SPANSTATUS.ALREADY_EXISTS
175+
else:
176+
return SPANSTATUS.INVALID_ARGUMENT
177+
178+
elif 500 <= http_status_code < 600:
179+
if http_status_code == 504:
180+
return SPANSTATUS.DEADLINE_EXCEEDED
181+
elif http_status_code == 501:
182+
return SPANSTATUS.UNIMPLEMENTED
183+
elif http_status_code == 503:
184+
return SPANSTATUS.UNAVAILABLE
185+
else:
186+
return SPANSTATUS.INTERNAL_ERROR
187+
188+
return SPANSTATUS.UNKNOWN_ERROR
189+
190+
154191
class _SpanRecorder:
155192
"""Limits the number of spans recorded in a transaction."""
156193

@@ -319,7 +356,7 @@ def __enter__(self):
319356
def __exit__(self, ty, value, tb):
320357
# type: (Optional[Any], Optional[Any], Optional[Any]) -> None
321358
if value is not None:
322-
self.set_status("internal_error")
359+
self.set_status(SPANSTATUS.INTERNAL_ERROR)
323360

324361
scope, old_span = self._context_manager_state
325362
del self._context_manager_state
@@ -542,37 +579,9 @@ def set_http_status(self, http_status):
542579
# type: (int) -> None
543580
self.set_tag(
544581
"http.status_code", str(http_status)
545-
) # we keep this for backwards compatability
582+
) # we keep this for backwards compatibility
546583
self.set_data(SPANDATA.HTTP_STATUS_CODE, http_status)
547-
548-
if http_status < 400:
549-
self.set_status("ok")
550-
elif 400 <= http_status < 500:
551-
if http_status == 403:
552-
self.set_status("permission_denied")
553-
elif http_status == 404:
554-
self.set_status("not_found")
555-
elif http_status == 429:
556-
self.set_status("resource_exhausted")
557-
elif http_status == 413:
558-
self.set_status("failed_precondition")
559-
elif http_status == 401:
560-
self.set_status("unauthenticated")
561-
elif http_status == 409:
562-
self.set_status("already_exists")
563-
else:
564-
self.set_status("invalid_argument")
565-
elif 500 <= http_status < 600:
566-
if http_status == 504:
567-
self.set_status("deadline_exceeded")
568-
elif http_status == 501:
569-
self.set_status("unimplemented")
570-
elif http_status == 503:
571-
self.set_status("unavailable")
572-
else:
573-
self.set_status("internal_error")
574-
else:
575-
self.set_status("unknown_error")
584+
self.set_status(get_span_status_from_http_code(http_status))
576585

577586
def is_success(self):
578587
# type: () -> bool
@@ -858,6 +867,8 @@ def finish(self, hub=None, end_timestamp=None):
858867

859868
client.transport.record_lost_event(reason, data_category="transaction")
860869

870+
# Only one span (the transaction itself) is discarded, since we did not record any spans here.
871+
client.transport.record_lost_event(reason, data_category="span")
861872
return None
862873

863874
if not self.name:

0 commit comments

Comments
 (0)