diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index d78abe14c5..7a1275b852 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -14,6 +14,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - You can no longer change the sampled status of a span with `span.sampled = False` after starting it. - The `Span()` constructor does not accept a `hub` parameter anymore. - `Span.finish()` does not accept a `hub` parameter anymore. +- `Span.finish()` no longer returns the `event_id` if the event is sent to sentry. - The `Profile()` constructor does not accept a `hub` parameter anymore. - A `Profile` object does not have a `.hub` property anymore. - `sentry_sdk.continue_trace` no longer returns a `Transaction` and is now a context manager. @@ -146,6 +147,7 @@ Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of wh - `continue_from_headers`, `continue_from_environ` and `from_traceparent` have been removed, please use top-level API `sentry_sdk.continue_trace` instead. - `PropagationContext` constructor no longer takes a `dynamic_sampling_context` but takes a `baggage` object instead. - `ThreadingIntegration` no longer takes the `propagate_hub` argument. +- `Baggage.populate_from_transaction` has been removed. ### Deprecated diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 796da2614e..85623d8056 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -449,7 +449,7 @@ def _prepare_event( ) return None - event = event_ + event = event_ # type: Optional[Event] # type: ignore[no-redef] spans_delta = spans_before - len(event.get("spans", [])) if is_transaction and spans_delta > 0 and self.transport is not None: @@ -483,7 +483,7 @@ def _prepare_event( for key in "release", "environment", "server_name", "dist": if event.get(key) is None and self.options[key] is not None: - event[key] = str(self.options[key]).strip() + event[key] = str(self.options[key]).strip() # type: ignore[literal-required] if event.get("sdk") is None: sdk_info = dict(SDK_INFO) sdk_info["integrations"] = sorted(self.integrations.keys()) @@ -523,7 +523,7 @@ def _prepare_event( and event is not None and event.get("type") != "transaction" ): - new_event = None + new_event = None # type: Optional[Event] with capture_internal_exceptions(): new_event = before_send(event, hint or {}) if new_event is None: @@ -532,7 +532,7 @@ def _prepare_event( self.transport.record_lost_event( "before_send", data_category="error" ) - event = new_event + event = new_event # type: Optional[Event] # type: ignore[no-redef] before_send_transaction = self.options["before_send_transaction"] if ( @@ -562,7 +562,7 @@ def _prepare_event( reason="before_send", data_category="span", quantity=spans_delta ) - event = new_event + event = new_event # type: Optional[Event] # type: ignore[no-redef] return event diff --git a/sentry_sdk/integrations/asyncpg.py b/sentry_sdk/integrations/asyncpg.py index 35726658ed..65f4d30e0d 100644 --- a/sentry_sdk/integrations/asyncpg.py +++ b/sentry_sdk/integrations/asyncpg.py @@ -189,7 +189,7 @@ async def _inner(*args: Any, **kwargs: Any) -> T: def _get_db_data( conn: Any = None, - addr: Optional[tuple[str]] = None, + addr: Optional[tuple[str, ...]] = None, database: Optional[str] = None, user: Optional[str] = None, ) -> dict[str, str]: @@ -218,6 +218,6 @@ def _get_db_data( return data -def _set_on_span(span: Span, data: dict[str, Any]): +def _set_on_span(span: Span, data: dict[str, Any]) -> None: for key, value in data.items(): span.set_attribute(key, value) diff --git a/sentry_sdk/integrations/clickhouse_driver.py b/sentry_sdk/integrations/clickhouse_driver.py index d43603ba2f..7c908b7d6d 100644 --- a/sentry_sdk/integrations/clickhouse_driver.py +++ b/sentry_sdk/integrations/clickhouse_driver.py @@ -9,7 +9,7 @@ ensure_integration_enabled, ) -from typing import TYPE_CHECKING, Any, Dict, TypeVar +from typing import TYPE_CHECKING, cast, Any, Dict, TypeVar # Hack to get new Python features working in older versions # without introducing a hard dependency on `typing_extensions` @@ -94,6 +94,7 @@ def _inner(*args: P.args, **kwargs: P.kwargs) -> T: connection._sentry_span = span # type: ignore[attr-defined] data = _get_db_data(connection) + data = cast("dict[str, Any]", data) data["db.query.text"] = query if query_id: @@ -116,9 +117,10 @@ def _inner(*args: P.args, **kwargs: P.kwargs) -> T: def _wrap_end(f: Callable[P, T]) -> Callable[P, T]: def _inner_end(*args: P.args, **kwargs: P.kwargs) -> T: res = f(*args, **kwargs) - connection = args[0].connection + client = cast("clickhouse_driver.client.Client", args[0]) + connection = client.connection - span = getattr(connection, "_sentry_span", None) # type: ignore[attr-defined] + span = getattr(connection, "_sentry_span", None) if span is not None: data = getattr(connection, "_sentry_db_data", {}) @@ -148,8 +150,9 @@ def _inner_end(*args: P.args, **kwargs: P.kwargs) -> T: def _wrap_send_data(f: Callable[P, T]) -> Callable[P, T]: def _inner_send_data(*args: P.args, **kwargs: P.kwargs) -> T: - connection = args[0].connection - db_params_data = args[2] + client = cast("clickhouse_driver.client.Client", args[0]) + connection = client.connection + db_params_data = cast("list[Any]", args[2]) span = getattr(connection, "_sentry_span", None) if span is not None: @@ -157,8 +160,10 @@ def _inner_send_data(*args: P.args, **kwargs: P.kwargs) -> T: _set_on_span(span, data) if should_send_default_pii(): - saved_db_data = getattr(connection, "_sentry_db_data", {}) - db_params = saved_db_data.get("db.params") or [] + saved_db_data = getattr( + connection, "_sentry_db_data", {} + ) # type: dict[str, Any] + db_params = saved_db_data.get("db.params") or [] # type: list[Any] db_params.extend(db_params_data) saved_db_data["db.params"] = db_params span.set_attribute("db.params", _serialize_span_attribute(db_params)) @@ -178,6 +183,6 @@ def _get_db_data(connection: clickhouse_driver.connection.Connection) -> Dict[st } -def _set_on_span(span: Span, data: Dict[str, Any]): +def _set_on_span(span: Span, data: Dict[str, Any]) -> None: for key, value in data.items(): span.set_attribute(key, _serialize_span_attribute(value)) diff --git a/sentry_sdk/integrations/opentelemetry/integration.py b/sentry_sdk/integrations/opentelemetry/integration.py index 551ef48891..1124e736ed 100644 --- a/sentry_sdk/integrations/opentelemetry/integration.py +++ b/sentry_sdk/integrations/opentelemetry/integration.py @@ -59,10 +59,10 @@ def _patch_readable_span(): def sentry_patched_readable_span(self): # type: (Span) -> ReadableSpan readable_span = old_readable_span(self) - readable_span._sentry_meta = getattr(self, "_sentry_meta", {}) + readable_span._sentry_meta = getattr(self, "_sentry_meta", {}) # type: ignore[attr-defined] return readable_span - Span._readable_span = sentry_patched_readable_span + Span._readable_span = sentry_patched_readable_span # type: ignore[method-assign] def _setup_sentry_tracing(): diff --git a/sentry_sdk/integrations/opentelemetry/sampler.py b/sentry_sdk/integrations/opentelemetry/sampler.py index 8d886add09..0b7004dc34 100644 --- a/sentry_sdk/integrations/opentelemetry/sampler.py +++ b/sentry_sdk/integrations/opentelemetry/sampler.py @@ -132,10 +132,15 @@ def should_sample( # parent_span_context.is_valid means this span has a parent, remote or local is_root_span = not parent_span_context.is_valid or parent_span_context.is_remote + sample_rate = None + # Explicit sampled value provided at start_span - if attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED) is not None: + custom_sampled = cast( + "Optional[bool]", attributes.get(SentrySpanAttribute.CUSTOM_SAMPLED) + ) + if custom_sampled is not None: if is_root_span: - sample_rate = float(attributes[SentrySpanAttribute.CUSTOM_SAMPLED]) + sample_rate = float(custom_sampled) if sample_rate > 0: return sampled_result(parent_span_context, attributes, sample_rate) else: @@ -145,8 +150,6 @@ def should_sample( f"[Tracing] Ignoring sampled param for non-root span {name}" ) - sample_rate = None - # Check if there is a traces_sampler # Traces_sampler is responsible to check parent sampled to have full transactions. has_traces_sampler = callable(client.options.get("traces_sampler")) @@ -190,16 +193,19 @@ def get_description(self) -> str: def create_sampling_context(name, attributes, parent_span_context, trace_id): - # type: (str, Attributes, SpanContext, str) -> dict[str, Any] + # type: (str, Attributes, Optional[SpanContext], int) -> dict[str, Any] sampling_context = { "transaction_context": { "name": name, - "op": attributes.get(SentrySpanAttribute.OP), - "source": attributes.get(SentrySpanAttribute.SOURCE), + "op": attributes.get(SentrySpanAttribute.OP) if attributes else None, + "source": ( + attributes.get(SentrySpanAttribute.SOURCE) if attributes else None + ), }, "parent_sampled": get_parent_sampled(parent_span_context, trace_id), - } + } # type: dict[str, Any] - sampling_context.update(attributes) + if attributes is not None: + sampling_context.update(attributes) return sampling_context diff --git a/sentry_sdk/integrations/opentelemetry/utils.py b/sentry_sdk/integrations/opentelemetry/utils.py index 1db3f65da1..d890849c31 100644 --- a/sentry_sdk/integrations/opentelemetry/utils.py +++ b/sentry_sdk/integrations/opentelemetry/utils.py @@ -18,8 +18,12 @@ import sentry_sdk from sentry_sdk.utils import Dsn from sentry_sdk.consts import SPANSTATUS, OP, SPANDATA -from sentry_sdk.tracing import get_span_status_from_http_code, DEFAULT_SPAN_ORIGIN -from sentry_sdk.tracing_utils import Baggage, LOW_QUALITY_TRANSACTION_SOURCES +from sentry_sdk.tracing import ( + get_span_status_from_http_code, + DEFAULT_SPAN_ORIGIN, + LOW_QUALITY_TRANSACTION_SOURCES, +) +from sentry_sdk.tracing_utils import Baggage from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute from sentry_sdk._types import TYPE_CHECKING diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index e67301e1a7..7b534d7efd 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -266,7 +266,7 @@ def finish( scope=None, # type: Optional[sentry_sdk.Scope] end_timestamp=None, # type: Optional[Union[float, datetime]] ): - # type: (...) -> Optional[str] + # type: (...) -> None pass def set_measurement(self, name, value, unit=""): @@ -375,7 +375,9 @@ def __init__( self.set_status(status) def __eq__(self, other): - # type: (Span) -> bool + # type: (object) -> bool + if not isinstance(other, Span): + return False return self._otel_span == other._otel_span def __repr__(self): @@ -526,7 +528,6 @@ def sample_rate(self): sample_rate = self._otel_span.get_span_context().trace_state.get( TRACESTATE_SAMPLE_RATE_KEY ) - sample_rate = cast("Optional[str]", sample_rate) return float(sample_rate) if sample_rate is not None else None @property @@ -668,18 +669,24 @@ def set_data(self, key, value): def get_attribute(self, name): # type: (str) -> Optional[Any] - if not isinstance(self._otel_span, ReadableSpan): + if ( + not isinstance(self._otel_span, ReadableSpan) + or not self._otel_span.attributes + ): return None return self._otel_span.attributes.get(name) def set_attribute(self, key, value): # type: (str, Any) -> None + # otel doesn't support None as values, preferring to not set the key + # at all instead if value is None: - # otel doesn't support None as values, preferring to not set the key - # at all instead + return + serialized_value = _serialize_span_attribute(value) + if serialized_value is None: return - self._otel_span.set_attribute(key, _serialize_span_attribute(value)) + self._otel_span.set_attribute(key, serialized_value) @property def status(self): @@ -690,7 +697,7 @@ def status(self): Sentry `SPANSTATUS` it can not be guaranteed that the status set in `set_status()` will be the same as the one returned here. """ - if not hasattr(self._otel_span, "status"): + if not isinstance(self._otel_span, ReadableSpan): return None if self._otel_span.status.status_code == StatusCode.UNSET: @@ -740,10 +747,10 @@ def set_http_status(self, http_status): def is_success(self): # type: () -> bool - return self._otel_span.status.code == StatusCode.OK + return self.status == SPANSTATUS.OK def finish(self, end_timestamp=None): - # type: (Optional[Union[float, datetime]]) -> Optional[str] + # type: (Optional[Union[float, datetime]]) -> None if end_timestamp is not None: from sentry_sdk.integrations.opentelemetry.utils import ( convert_to_otel_timestamp, diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 6ebe7e0322..952ffccc4c 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -525,52 +525,6 @@ def from_options(cls, scope): return Baggage(sentry_items, third_party_items, mutable) - @classmethod - def populate_from_transaction(cls, transaction): - # type: (sentry_sdk.tracing.Transaction) -> Baggage - """ - Populate fresh baggage entry with sentry_items and make it immutable - if this is the head SDK which originates traces. - """ - client = sentry_sdk.get_client() - sentry_items = {} # type: Dict[str, str] - - if not client.is_active(): - return Baggage(sentry_items) - - options = client.options or {} - - sentry_items["trace_id"] = transaction.trace_id - - if options.get("environment"): - sentry_items["environment"] = options["environment"] - - if options.get("release"): - sentry_items["release"] = options["release"] - - if options.get("dsn"): - sentry_items["public_key"] = Dsn(options["dsn"]).public_key - - if ( - transaction.name - and transaction.source not in LOW_QUALITY_TRANSACTION_SOURCES - ): - sentry_items["transaction"] = transaction.name - - if transaction.sample_rate is not None: - sentry_items["sample_rate"] = str(transaction.sample_rate) - - if transaction.sampled is not None: - sentry_items["sampled"] = "true" if transaction.sampled else "false" - - # there's an existing baggage but it was mutable, - # which is why we are creating this new baggage. - # However, if by chance the user put some sentry items in there, give them precedence. - if transaction._baggage and transaction._baggage.sentry_items: - sentry_items.update(transaction._baggage.sentry_items) - - return Baggage(sentry_items, mutable=False) - def freeze(self): # type: () -> None self.mutable = False @@ -722,6 +676,5 @@ def get_current_span(scope=None): # Circular imports from sentry_sdk.tracing import ( BAGGAGE_HEADER_NAME, - LOW_QUALITY_TRANSACTION_SOURCES, SENTRY_TRACE_HEADER_NAME, ) diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py index 278a0a43d0..99251cf439 100644 --- a/sentry_sdk/transport.py +++ b/sentry_sdk/transport.py @@ -23,7 +23,7 @@ from sentry_sdk.worker import BackgroundWorker from sentry_sdk.envelope import Envelope, Item, PayloadRef -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast if TYPE_CHECKING: from typing import Any @@ -179,6 +179,7 @@ def _parse_rate_limits(header, now=None): retry_after = now + timedelta(seconds=int(retry_after_val)) for category in categories and categories.split(";") or (None,): + category = cast("Optional[EventDataCategory]", category) yield category, retry_after except (LookupError, ValueError): continue