diff --git a/.github/workflows/test-integrations-misc.yml b/.github/workflows/test-integrations-misc.yml
index ebec367f0b..1de39fcadc 100644
--- a/.github/workflows/test-integrations-misc.yml
+++ b/.github/workflows/test-integrations-misc.yml
@@ -41,18 +41,10 @@ jobs:
run: |
set -x # print commands that are executed
./scripts/runtox.sh "py${{ matrix.python-version }}-loguru"
- - name: Test opentelemetry
- run: |
- set -x # print commands that are executed
- ./scripts/runtox.sh "py${{ matrix.python-version }}-opentelemetry"
- name: Test otlp
run: |
set -x # print commands that are executed
./scripts/runtox.sh "py${{ matrix.python-version }}-otlp"
- - name: Test potel
- run: |
- set -x # print commands that are executed
- ./scripts/runtox.sh "py${{ matrix.python-version }}-potel"
- name: Test pure_eval
run: |
set -x # print commands that are executed
diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md
index 53396a37ba..3c297ea9fd 100644
--- a/MIGRATION_GUIDE.md
+++ b/MIGRATION_GUIDE.md
@@ -1,3 +1,47 @@
+# Sentry SDK 3.0 Migration Guide
+
+
+Looking to upgrade from Sentry SDK 2.x to 3.x? Here's a comprehensive list of what's changed. Looking for a more digestable summary? See the [guide in the docs](https://docs.sentry.io/platforms/python/migration/2.x-to-3.x) with the most common migration patterns.
+
+## New Features
+
+
+## Changed
+
+- The Strawberry integration won't auto-enable anymore if we detect `strawberry-graphql` is installed. Set it up manually, setting the `async_execution` integration option to either `True` or `False` depending on if your app is async or sync.
+
+ ```python
+ from sentry_sdk.integrations.strawberry import StrawberryIntegration
+
+ sentry_sdk.init(
+ integrations=[
+ StrawberryIntegration(async_execution=True), # or False
+ ],
+ ...
+ )
+ ```
+
+- The UnraisableHookIntegration is now enabled by default.
+- We now don't suppress chained exceptions in the ASGI and asyncio integrations by default. The related `suppress_asgi_chained_exceptions` experimental option was removed.
+
+## Removed
+
+- The `enable_tracing` option was removed. Use `traces_sample_rate=1.0` instead.
+- The deprecated `push_scope` and `configure_scope` APIs have been removed. Use `with new_scope():` to push a new scope and `scope = get_current_scope()` to retrieve the current scope instead.
+- Transaction profiling and related code was removed.
+- Removed the deprecated Hub class and all uses of hub throughout the SDK in arguments, options, etc. Use a scope instead.
+- The `SentrySpanProcessor`, `SentryPropagator`, `instrumenter`, and associated OpenTelemetry compatibility code was removed along with the `opentelemetry` extra and the `SentryPropagator` entrypoint. Use the `OTLPIntegration` instead.
+- Removed the `auto_session_tracing` decorator. Use `track_session` instead.
+- The deprecated `set_measurement` API was removed.
+- The experimental option `otel_powered_performance` has been removed together with the associated `OpenTelemetryIntegration` and `opentelemetry-experimental` extra.
+- A number of extras (installable via `sentry-sdk[extra-name]`) has been removed. Use the base package (`sentry-sdk`) instead; there is no difference in functionality. The following extras have been removed: `aiohttp`, `anthropic`, `arq`, `asyncpg`, `beam`, `bottle`, `celery`, `celery-redbeat`, `chalice`, `clickhouse-driver`, `django`, `falcon`, `fastapi`, `google-genai`, `httpx`, `huey`, `huggingface_hub`, `langchain`, `langgraph`, `launchdarkly`, `litellm`, `litestar`, `loguru`, `mcp`, `openai`, `openfeature`, `pydantic_ai`, `pymongo`, `pyspark`, `rq`, `sanic`, `sqlalchemy`, `starlette`, `starlite`, `statsig`, `tornado`, `unleash`.
+- The `failed_request_status_codes` integration option now only supports a set of integers as input. Lists of integers or containers of integers are no longer supported.
+- The deprecated `propagate_traces` option has been removed. Use `trace_propagation_targets` instead, which gives you more power over trace propagation. Note that only the top-level `init` option was removed; the `propagate_traces` option of the Celery integration remains available.
+- Removed Spotlight integration for Django. See [Spotlight 2.0](https://github.com/getsentry/spotlight/issues/891) for more context.
+
+## Deprecated
+
+
# Sentry SDK 2.0 Migration Guide
Looking to upgrade from Sentry SDK 1.x to 2.x? Here's a comprehensive list of what's changed. Looking for a more digestable summary? See the [guide in the docs](https://docs.sentry.io/platforms/python/migration/1.x-to-2.x) with the most common migration patterns.
diff --git a/docs/api.rst b/docs/api.rst
index 802abee75d..e9ed33246a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -62,7 +62,4 @@ Client Management
Managing Scope (advanced)
=========================
-.. autofunction:: sentry_sdk.api.configure_scope
-.. autofunction:: sentry_sdk.api.push_scope
-
.. autofunction:: sentry_sdk.api.new_scope
diff --git a/docs/apidocs.rst b/docs/apidocs.rst
index a3c8a6e150..4991b8ccd6 100644
--- a/docs/apidocs.rst
+++ b/docs/apidocs.rst
@@ -2,9 +2,6 @@
API Docs
========
-.. autoclass:: sentry_sdk.Hub
- :members:
-
.. autoclass:: sentry_sdk.Scope
:members:
@@ -32,9 +29,6 @@ API Docs
.. autoclass:: sentry_sdk.tracing.Span
:members:
-.. autoclass:: sentry_sdk.profiler.transaction_profiler.Profile
- :members:
-
.. autoclass:: sentry_sdk.session.Session
:members:
diff --git a/scripts/find_raise_from_none.py b/scripts/find_raise_from_none.py
index 63b2b84333..089a14a1df 100644
--- a/scripts/find_raise_from_none.py
+++ b/scripts/find_raise_from_none.py
@@ -42,17 +42,11 @@ def main():
for module_path in walk_package_modules():
scan_file(module_path)
- # TODO: Investigate why we suppress exception chains here.
- ignored_raises = {
- pathlib.Path("sentry_sdk/integrations/asgi.py"): 2,
- pathlib.Path("sentry_sdk/integrations/asyncio.py"): 1,
- }
-
raise_from_none_count = {
file: len(occurences)
for file, occurences in RaiseFromNoneVisitor.line_numbers.items()
}
- if raise_from_none_count != ignored_raises:
+ if raise_from_none_count:
exc = Exception("Detected unexpected raise ... from None.")
exc.add_note(
"Raise ... from None suppresses chained exceptions, removing valuable context."
diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py
index a8609dab50..1fa9e765c5 100644
--- a/scripts/populate_tox/populate_tox.py
+++ b/scripts/populate_tox/populate_tox.py
@@ -69,9 +69,7 @@
"shadowed_module",
"gcp",
"gevent",
- "opentelemetry",
"otlp",
- "potel",
}
# Free-threading is experimentally supported in 3.13, and officially supported in 3.14.
diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja
index 12a2038683..459fc00cfa 100644
--- a/scripts/populate_tox/tox.jinja
+++ b/scripts/populate_tox/tox.jinja
@@ -42,15 +42,9 @@ envlist =
# GCP
{py3.7}-gcp
- # OpenTelemetry (OTel)
- {py3.7,py3.9,py3.12,py3.13,py3.14,py3.14t}-opentelemetry
-
# OpenTelemetry with OTLP
{py3.7,py3.9,py3.12,py3.13,py3.14}-otlp
- # OpenTelemetry Experimental (POTel)
- {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel
-
# === Integrations - Auto-generated ===
# These come from the populate_tox.py script.
@@ -139,18 +133,10 @@ deps =
aws_lambda: uvicorn
aws_lambda: pyyaml
- # OpenTelemetry (OTel)
- opentelemetry: opentelemetry-distro
- opentelemetry: pytest-forked
-
# OpenTelemetry with OTLP
otlp: opentelemetry-distro[otlp]
otlp: responses
- # OpenTelemetry Experimental (POTel)
- potel: opentelemetry-distro
- potel: pytest-forked
-
# === Integrations - Auto-generated ===
# These come from the populate_tox.py script.
@@ -240,9 +226,7 @@ setenv =
aws_lambda: _TESTPATH=tests/integrations/aws_lambda
cloud_resource_context: _TESTPATH=tests/integrations/cloud_resource_context
gcp: _TESTPATH=tests/integrations/gcp
- opentelemetry: _TESTPATH=tests/integrations/opentelemetry
otlp: _TESTPATH=tests/integrations/otlp
- potel: _TESTPATH=tests/integrations/opentelemetry
socket: _TESTPATH=tests/integrations/socket
# These TESTPATH definitions are auto-generated by toxgen
diff --git a/scripts/split_tox_gh_actions/split_tox_gh_actions.py b/scripts/split_tox_gh_actions/split_tox_gh_actions.py
index d6b5494515..754315d72c 100755
--- a/scripts/split_tox_gh_actions/split_tox_gh_actions.py
+++ b/scripts/split_tox_gh_actions/split_tox_gh_actions.py
@@ -163,9 +163,7 @@
],
"Misc": [
"loguru",
- "opentelemetry",
"otlp",
- "potel",
"pure_eval",
"trytond",
"typer",
diff --git a/sentry_sdk/__init__.py b/sentry_sdk/__init__.py
index 9163a06641..483e80bda7 100644
--- a/sentry_sdk/__init__.py
+++ b/sentry_sdk/__init__.py
@@ -8,7 +8,6 @@
from sentry_sdk.api import * # noqa # isort: skip
__all__ = [ # noqa
- "Hub",
"Scope",
"Client",
"Transport",
@@ -22,7 +21,6 @@
"capture_event",
"capture_exception",
"capture_message",
- "configure_scope",
"continue_trace",
"flush",
"flush_async",
@@ -37,14 +35,12 @@
"isolation_scope",
"last_event_id",
"new_scope",
- "push_scope",
"remove_attribute",
"set_attribute",
"set_attributes",
"set_context",
"set_extra",
"set_level",
- "set_measurement",
"set_tag",
"set_tags",
"set_user",
@@ -66,6 +62,3 @@
init_debug_support()
del init_debug_support
-
-# circular imports
-from sentry_sdk.hub import Hub
diff --git a/sentry_sdk/_types.py b/sentry_sdk/_types.py
index e91fed097c..3e5abae915 100644
--- a/sentry_sdk/_types.py
+++ b/sentry_sdk/_types.py
@@ -138,7 +138,7 @@ def substituted_because_contains_sensitive_data(cls) -> "AnnotatedValue":
if TYPE_CHECKING:
- from collections.abc import Container, MutableMapping, Sequence
+ from collections.abc import MutableMapping, Sequence
from datetime import datetime
from types import TracebackType
from typing import Any, Callable, Dict, List, Mapping, NotRequired, Optional, Type
@@ -236,17 +236,6 @@ class DataCollection(TypedDict):
"exbibyte",
]
- FractionUnit = Literal["ratio", "percent"]
- MeasurementUnit = Union[DurationUnit, InformationUnit, FractionUnit, str]
-
- MeasurementValue = TypedDict(
- "MeasurementValue",
- {
- "value": float,
- "unit": NotRequired[Optional[MeasurementUnit]],
- },
- )
-
Event = TypedDict(
"Event",
{
@@ -268,13 +257,11 @@ class DataCollection(TypedDict):
"level": LogLevelStr,
"logentry": Mapping[str, object],
"logger": str,
- "measurements": dict[str, MeasurementValue],
"message": str,
"modules": dict[str, str],
"monitor_config": Mapping[str, object],
"monitor_slug": Optional[str],
"platform": Literal["python"],
- "profile": object, # Should be sentry_sdk.profiler.Profile, but we can't import that here due to circular imports
"release": Optional[str],
"request": dict[str, object],
"sdk": Mapping[str, object],
@@ -418,7 +405,6 @@ class DataCollection(TypedDict):
"attachment",
"session",
"internal",
- "profile",
"profile_chunk",
"monitor",
"span",
@@ -429,7 +415,7 @@ class DataCollection(TypedDict):
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
ContinuousProfilerMode = Literal["thread", "gevent", "unknown"]
- ProfilerMode = Union[ContinuousProfilerMode, Literal["sleep"]]
+ ProfilerMode = Union[ContinuousProfilerMode]
MonitorConfigScheduleType = Literal["crontab", "interval"]
MonitorConfigScheduleUnit = Literal[
@@ -466,8 +452,6 @@ class DataCollection(TypedDict):
total=False,
)
- HttpStatusCodeRange = Union[int, Container[int]]
-
class TextPart(TypedDict):
type: Literal["text"]
content: str
diff --git a/sentry_sdk/api.py b/sentry_sdk/api.py
index d5f91fa145..51faebebf7 100644
--- a/sentry_sdk/api.py
+++ b/sentry_sdk/api.py
@@ -1,13 +1,11 @@
import inspect
import warnings
-from contextlib import contextmanager
from typing import TYPE_CHECKING
from sentry_sdk import Client, tracing_utils
from sentry_sdk._init_implementation import init
-from sentry_sdk.consts import INSTRUMENTER
from sentry_sdk.crons import monitor
-from sentry_sdk.scope import Scope, _ScopeManager, isolation_scope, new_scope
+from sentry_sdk.scope import Scope, isolation_scope, new_scope
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.traces import get_current_span as _get_current_streamed_span
from sentry_sdk.tracing import NoOpSpan, Transaction, trace
@@ -17,9 +15,7 @@
from typing import (
Any,
Callable,
- ContextManager,
Dict,
- Generator,
Optional,
TypeVar,
Union,
@@ -35,7 +31,6 @@
ExcInfo,
Hint,
LogLevelStr,
- MeasurementUnit,
SamplingContext,
)
from sentry_sdk.client import BaseClient
@@ -58,7 +53,6 @@ def overload(x: "T") -> "T":
"capture_event",
"capture_exception",
"capture_message",
- "configure_scope",
"continue_trace",
"flush",
"flush_async",
@@ -73,14 +67,12 @@ def overload(x: "T") -> "T":
"isolation_scope",
"last_event_id",
"new_scope",
- "push_scope",
"remove_attribute",
"set_attribute",
"set_attributes",
"set_context",
"set_extra",
"set_level",
- "set_measurement",
"set_tag",
"set_tags",
"set_user",
@@ -206,94 +198,6 @@ def add_breadcrumb(
return get_isolation_scope().add_breadcrumb(crumb, hint, **kwargs)
-@overload
-def configure_scope() -> "ContextManager[Scope]":
- pass
-
-
-@overload
-def configure_scope( # noqa: F811
- callback: "Callable[[Scope], None]",
-) -> None:
- pass
-
-
-def configure_scope( # noqa: F811
- callback: "Optional[Callable[[Scope], None]]" = None,
-) -> "Optional[ContextManager[Scope]]":
- """
- Reconfigures the scope.
-
- :param callback: If provided, call the callback with the current scope.
-
- :returns: If no callback is provided, returns a context manager that returns the scope.
- """
- warnings.warn(
- "sentry_sdk.configure_scope is deprecated and will be removed in the next major version. "
- "Please consult our migration guide to learn how to migrate to the new API: "
- "https://docs.sentry.io/platforms/python/migration/1.x-to-2.x#scope-configuring",
- DeprecationWarning,
- stacklevel=2,
- )
-
- scope = get_isolation_scope()
- scope.generate_propagation_context()
-
- if callback is not None:
- # TODO: used to return None when client is None. Check if this changes behavior.
- callback(scope)
-
- return None
-
- @contextmanager
- def inner() -> "Generator[Scope, None, None]":
- yield scope
-
- return inner()
-
-
-@overload
-def push_scope() -> "ContextManager[Scope]":
- pass
-
-
-@overload
-def push_scope( # noqa: F811
- callback: "Callable[[Scope], None]",
-) -> None:
- pass
-
-
-def push_scope( # noqa: F811
- callback: "Optional[Callable[[Scope], None]]" = None,
-) -> "Optional[ContextManager[Scope]]":
- """
- Pushes a new layer on the scope stack.
-
- :param callback: If provided, this method pushes a scope, calls
- `callback`, and pops the scope again.
-
- :returns: If no `callback` is provided, a context manager that should
- be used to pop the scope again.
- """
- warnings.warn(
- "sentry_sdk.push_scope is deprecated and will be removed in the next major version. "
- "Please consult our migration guide to learn how to migrate to the new API: "
- "https://docs.sentry.io/platforms/python/migration/1.x-to-2.x#scope-pushing",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if callback is not None:
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
- with push_scope() as scope:
- callback(scope)
- return None
-
- return _ScopeManager()
-
-
@scopemethod
def set_attribute(attribute: str, value: "Any") -> None:
"""
@@ -383,7 +287,6 @@ def start_span(
@scopemethod
def start_transaction(
transaction: "Optional[Transaction]" = None,
- instrumenter: str = INSTRUMENTER.SENTRY,
custom_sampling_context: "Optional[SamplingContext]" = None,
**kwargs: "Unpack[TransactionKwargs]",
) -> "Union[Transaction, NoOpSpan]":
@@ -411,28 +314,16 @@ def start_transaction(
:param transaction: The transaction to start. If omitted, we create and
start a new transaction.
- :param instrumenter: This parameter is meant for internal use only. It
- will be removed in the next major version.
:param custom_sampling_context: The transaction's custom sampling context.
:param kwargs: Optional keyword arguments to be passed to the Transaction
constructor. See :py:class:`sentry_sdk.tracing.Transaction` for
available arguments.
"""
return get_current_scope().start_transaction(
- transaction, instrumenter, custom_sampling_context, **kwargs
+ transaction, custom_sampling_context, **kwargs
)
-def set_measurement(name: str, value: float, unit: "MeasurementUnit" = "") -> None:
- """
- .. deprecated:: 2.28.0
- This function is deprecated and will be removed in the next major release.
- """
- transaction = get_current_scope().transaction
- if transaction is not None:
- transaction.set_measurement(name, value, unit)
-
-
def get_current_span(
scope: "Optional[Scope]" = None,
) -> "Optional[Span]":
diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py
index e8414d3f91..f865cbac97 100644
--- a/sentry_sdk/client.py
+++ b/sentry_sdk/client.py
@@ -17,7 +17,6 @@
from sentry_sdk.consts import (
DEFAULT_MAX_VALUE_LENGTH,
DEFAULT_OPTIONS,
- INSTRUMENTER,
SPANDATA,
SPANSTATUS,
VERSION,
@@ -28,15 +27,10 @@
_resolve_data_collection,
)
from sentry_sdk.envelope import Envelope, Item, PayloadRef
-from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations
+from sentry_sdk.integrations import setup_integrations
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.monitor import Monitor
from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler
-from sentry_sdk.profiler.transaction_profiler import (
- Profile,
- has_profiling_enabled,
- setup_profiler,
-)
from sentry_sdk.scrubber import EventScrubber
from sentry_sdk.serializer import serialize
from sentry_sdk.sessions import SessionFlusher
@@ -336,9 +330,6 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
if rv["server_name"] is None and hasattr(socket, "gethostname"):
rv["server_name"] = socket.gethostname()
- if rv["instrumenter"] is None:
- rv["instrumenter"] = INSTRUMENTER.SENTRY
-
if rv["project_root"] is None:
try:
project_root = os.getcwd()
@@ -347,9 +338,6 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
rv["project_root"] = project_root
- if rv["enable_tracing"] is True and rv["traces_sample_rate"] is None:
- rv["traces_sample_rate"] = 1.0
-
rv["data_collection"] = _resolve_data_collection(rv)
# Do not add the event scrubber if data collection is enabled as it can remove data that's
@@ -378,13 +366,6 @@ def _get_options(*args: "Optional[str]", **kwargs: "Any") -> "Dict[str, Any]":
env_to_bool(os.environ.get("SENTRY_KEEP_ALIVE"), strict=True) or False
)
- if rv["enable_tracing"] is not None:
- warnings.warn(
- "The `enable_tracing` parameter is deprecated. Please use `traces_sample_rate` instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
if rv["trace_ignore_status_codes"] and has_span_streaming_enabled(rv):
warnings.warn(
"The `trace_ignore_status_codes` parameter is ignored in span streaming mode.",
@@ -632,7 +613,6 @@ def _record_lost_event(
self.options["send_default_pii"] = True
self.options["error_sampler"] = sample_all
self.options["traces_sampler"] = sample_all
- self.options["profiles_sampler"] = sample_all
# data_collection was resolved in _get_options() before this
# spotlight override flipped send_default_pii on. Re-derive it so
# data_collection agrees with should_send_default_pii() in
@@ -681,19 +661,6 @@ def _record_lost_event(
)
)
- if self.options["_experiments"].get("otel_powered_performance", False):
- logger.debug(
- "[OTel] Enabling experimental OTel-powered performance monitoring."
- )
- self.options["instrumenter"] = INSTRUMENTER.OTEL
- if (
- "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
- not in _DEFAULT_INTEGRATIONS
- ):
- _DEFAULT_INTEGRATIONS.append(
- "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration",
- )
-
self.integrations = setup_integrations(
self.options["integrations"],
with_defaults=self.options["default_integrations"],
@@ -708,20 +675,14 @@ def _record_lost_event(
SDK_INFO["name"] = sdk_name
logger.debug("Setting SDK name to '%s'", sdk_name)
- if has_profiling_enabled(self.options):
- try:
- setup_profiler(self.options)
- except Exception as e:
- logger.debug("Can not set up profiler. (%s)", e)
- else:
- try:
- setup_continuous_profiler(
- self.options,
- sdk_info=SDK_INFO,
- capture_func=_capture_envelope,
- )
- except Exception as e:
- logger.debug("Can not set up continuous profiler. (%s)", e)
+ try:
+ setup_continuous_profiler(
+ self.options,
+ sdk_info=SDK_INFO,
+ capture_func=_capture_envelope,
+ )
+ except Exception as e:
+ logger.debug("Can not set up continuous profiler. (%s)", e)
finally:
_client_init_debug.set(old_debug)
@@ -733,7 +694,6 @@ def _record_lost_event(
or self.log_batcher
or self.metrics_batcher
or self.span_batcher
- or has_profiling_enabled(self.options)
or isinstance(self.transport, HttpTransportCore)
):
# If we have anything on that could spawn a background thread, we
@@ -1121,8 +1081,6 @@ def capture_event(
if not self._should_capture(event, hint, scope):
return None
- profile = event.pop("profile", None)
-
event_id = event.get("event_id")
if event_id is None:
event["event_id"] = event_id = uuid.uuid4().hex
@@ -1163,9 +1121,6 @@ def capture_event(
envelope = Envelope(headers=headers)
- if is_transaction and isinstance(profile, Profile):
- envelope.add_profile(profile.to_json(event_opt, self.options))
-
if is_transaction and not span_recorder_has_gen_ai_span:
envelope.add_transaction(event_opt)
elif is_transaction:
diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py
index c6db94d780..1a205a31de 100644
--- a/sentry_sdk/consts.py
+++ b/sentry_sdk/consts.py
@@ -70,7 +70,6 @@ class CompressionAlgo(Enum):
"record_sql_params": Optional[bool],
"continuous_profiling_auto_start": Optional[bool],
"continuous_profiling_mode": Optional[ContinuousProfilerMode],
- "otel_powered_performance": Optional[bool],
"transport_zlib_compression_level": Optional[int],
"transport_compression_level": Optional[int],
"transport_compression_algo": Optional[CompressionAlgo],
@@ -86,7 +85,6 @@ class CompressionAlgo(Enum):
"before_send_span": Optional[
Callable[[SpanJSON, Hint], Optional[SpanJSON]]
],
- "suppress_asgi_chained_exceptions": Optional[bool],
"data_collection": Optional[DataCollectionUserOptions],
},
total=False,
@@ -115,11 +113,6 @@ def __str__(self) -> str:
return self.value
-class INSTRUMENTER:
- SENTRY = "sentry"
- OTEL = "otel"
-
-
class SPANNAME:
DB_COMMIT = "COMMIT"
DB_ROLLBACK = "ROLLBACK"
@@ -1298,12 +1291,9 @@ def __init__(
debug: "Optional[bool]" = None,
attach_stacktrace: bool = False,
ca_certs: "Optional[str]" = None,
- propagate_traces: bool = True,
traces_sample_rate: "Optional[float]" = None,
trace_lifecycle: "Optional[Literal['static', 'stream']]" = None,
traces_sampler: "Optional[TracesSampler]" = None,
- profiles_sample_rate: "Optional[float]" = None,
- profiles_sampler: "Optional[TracesSampler]" = None,
profiler_mode: "Optional[ProfilerMode]" = None,
profile_lifecycle: 'Literal["manual", "trace"]' = "manual",
profile_session_sample_rate: "Optional[float]" = None,
@@ -1313,10 +1303,8 @@ def __init__(
send_client_reports: bool = True,
_experiments: "Experiments" = {}, # noqa: B006
proxy_headers: "Optional[Dict[str, str]]" = None,
- instrumenter: "Optional[str]" = INSTRUMENTER.SENTRY,
before_send_transaction: "Optional[TransactionProcessor]" = None,
project_root: "Optional[str]" = None,
- enable_tracing: "Optional[bool]" = None,
include_local_variables: "Optional[bool]" = True,
include_source_context: "Optional[bool]" = True,
trace_propagation_targets: "Optional[Sequence[str]]" = [ # noqa: B006
@@ -1706,31 +1694,16 @@ def __init__(
Return a string for that repr value to be used or `None` to continue serializing how Sentry would have
done it anyway.
- :param profiles_sample_rate: A number between `0` and `1`, controlling the percentage chance a given sampled
- transaction will be profiled.
-
- (`0` represents 0% while `1` represents 100%.) Applies equally to all transactions created in the app.
-
- This is relative to the tracing sample rate - e.g. `0.5` means 50% of sampled transactions will be
- profiled.
-
- :param profiles_sampler:
-
:param profiler_mode:
:param profile_lifecycle:
:param profile_session_sample_rate:
- :param enable_tracing:
-
- :param propagate_traces:
-
:param auto_session_tracking:
:param spotlight:
- :param instrumenter:
:param enable_logs: Set `enable_logs` to True to enable the SDK to emit
Sentry logs. Defaults to False.
diff --git a/sentry_sdk/envelope.py b/sentry_sdk/envelope.py
index d2d4aae31a..c4e3a4d42d 100644
--- a/sentry_sdk/envelope.py
+++ b/sentry_sdk/envelope.py
@@ -59,12 +59,6 @@ def add_transaction(
) -> None:
self.add_item(Item(payload=PayloadRef(json=transaction), type="transaction"))
- def add_profile(
- self,
- profile: "Any",
- ) -> None:
- self.add_item(Item(payload=PayloadRef(json=profile), type="profile"))
-
def add_profile_chunk(
self,
profile_chunk: "Any",
@@ -259,8 +253,6 @@ def data_category(self) -> "EventDataCategory":
return "trace_metric"
elif ty == "client_report":
return "internal"
- elif ty == "profile":
- return "profile"
elif ty == "profile_chunk":
return "profile_chunk"
elif ty == "check_in":
diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py
deleted file mode 100644
index b17444d06e..0000000000
--- a/sentry_sdk/hub.py
+++ /dev/null
@@ -1,741 +0,0 @@
-import warnings
-from contextlib import contextmanager
-from typing import TYPE_CHECKING
-
-from sentry_sdk import (
- get_client,
- get_current_scope,
- get_global_scope,
- get_isolation_scope,
-)
-from sentry_sdk._compat import with_metaclass
-from sentry_sdk.client import Client
-from sentry_sdk.consts import INSTRUMENTER
-from sentry_sdk.scope import _ScopeManager
-from sentry_sdk.tracing import (
- NoOpSpan,
- Span,
- Transaction,
-)
-from sentry_sdk.utils import (
- ContextVar,
- logger,
-)
-
-if TYPE_CHECKING:
- from typing import (
- Any,
- Callable,
- ContextManager,
- Dict,
- Generator,
- List,
- Optional,
- Tuple,
- Type,
- TypeVar,
- Union,
- overload,
- )
-
- from typing_extensions import Unpack
-
- from sentry_sdk._types import (
- Breadcrumb,
- BreadcrumbHint,
- Event,
- ExcInfo,
- Hint,
- LogLevelStr,
- SamplingContext,
- )
- from sentry_sdk.client import BaseClient
- from sentry_sdk.integrations import Integration
- from sentry_sdk.scope import Scope
- from sentry_sdk.tracing import TransactionKwargs
-
- T = TypeVar("T")
-
-else:
-
- def overload(x: "T") -> "T":
- return x
-
-
-class SentryHubDeprecationWarning(DeprecationWarning):
- """
- A custom deprecation warning to inform users that the Hub is deprecated.
- """
-
- _MESSAGE = (
- "`sentry_sdk.Hub` is deprecated and will be removed in a future major release. "
- "Please consult our 1.x to 2.x migration guide for details on how to migrate "
- "`Hub` usage to the new API: "
- "https://docs.sentry.io/platforms/python/migration/1.x-to-2.x"
- )
-
- def __init__(self, *_: object) -> None:
- super().__init__(self._MESSAGE)
-
-
-@contextmanager
-def _suppress_hub_deprecation_warning() -> "Generator[None, None, None]":
- """Utility function to suppress deprecation warnings for the Hub."""
- with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=SentryHubDeprecationWarning)
- yield
-
-
-_local = ContextVar("sentry_current_hub")
-
-
-class HubMeta(type):
- @property
- def current(cls) -> "Hub":
- """Returns the current instance of the hub."""
- warnings.warn(SentryHubDeprecationWarning(), stacklevel=2)
- rv = _local.get(None)
- if rv is None:
- with _suppress_hub_deprecation_warning():
- # This will raise a deprecation warning; suppress it since we already warned above.
- rv = Hub(GLOBAL_HUB)
- _local.set(rv)
- return rv
-
- @property
- def main(cls) -> "Hub":
- """Returns the main instance of the hub."""
- warnings.warn(SentryHubDeprecationWarning(), stacklevel=2)
- return GLOBAL_HUB
-
-
-class Hub(with_metaclass(HubMeta)): # type: ignore
- """
- .. deprecated:: 2.0.0
- The Hub is deprecated. Its functionality will be merged into :py:class:`sentry_sdk.scope.Scope`.
-
- The hub wraps the concurrency management of the SDK. Each thread has
- its own hub but the hub might transfer with the flow of execution if
- context vars are available.
-
- If the hub is used with a with statement it's temporarily activated.
- """
-
- _stack: "List[Tuple[Optional[Client], Scope]]" = None # type: ignore[assignment]
- _scope: "Optional[Scope]" = None
-
- # Mypy doesn't pick up on the metaclass.
-
- if TYPE_CHECKING:
- current: "Hub" = None # type: ignore[assignment]
- main: "Optional[Hub]" = None
-
- def __init__(
- self,
- client_or_hub: "Optional[Union[Hub, Client]]" = None,
- scope: "Optional[Any]" = None,
- ) -> None:
- warnings.warn(SentryHubDeprecationWarning(), stacklevel=2)
-
- current_scope = None
-
- if isinstance(client_or_hub, Hub):
- client = get_client()
- if scope is None:
- # hub cloning is going on, we use a fork of the current/isolation scope for context manager
- scope = get_isolation_scope().fork()
- current_scope = get_current_scope().fork()
- else:
- client = client_or_hub
- get_global_scope().set_client(client)
-
- if scope is None: # so there is no Hub cloning going on
- # just the current isolation scope is used for context manager
- scope = get_isolation_scope()
- current_scope = get_current_scope()
-
- if current_scope is None:
- # just the current current scope is used for context manager
- current_scope = get_current_scope()
-
- self._stack = [(client, scope)] # type: ignore
- self._last_event_id: "Optional[str]" = None
- self._old_hubs: "List[Hub]" = []
-
- self._old_current_scopes: "List[Scope]" = []
- self._old_isolation_scopes: "List[Scope]" = []
- self._current_scope: "Scope" = current_scope
- self._scope: "Scope" = scope
-
- def __enter__(self) -> "Hub":
- self._old_hubs.append(Hub.current)
- _local.set(self)
-
- current_scope = get_current_scope()
- self._old_current_scopes.append(current_scope)
- scope._current_scope.set(self._current_scope)
-
- isolation_scope = get_isolation_scope()
- self._old_isolation_scopes.append(isolation_scope)
- scope._isolation_scope.set(self._scope)
-
- return self
-
- def __exit__(
- self,
- exc_type: "Optional[type]",
- exc_value: "Optional[BaseException]",
- tb: "Optional[Any]",
- ) -> None:
- old = self._old_hubs.pop()
- _local.set(old)
-
- old_current_scope = self._old_current_scopes.pop()
- scope._current_scope.set(old_current_scope)
-
- old_isolation_scope = self._old_isolation_scopes.pop()
- scope._isolation_scope.set(old_isolation_scope)
-
- def run(
- self,
- callback: "Callable[[], T]",
- ) -> "T":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
-
- Runs a callback in the context of the hub. Alternatively the
- with statement can be used on the hub directly.
- """
- with self:
- return callback()
-
- def get_integration(
- self,
- name_or_class: "Union[str, Type[Integration]]",
- ) -> "Any":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.client._Client.get_integration` instead.
-
- Returns the integration for this hub by name or class. If there
- is no client bound or the client does not have that integration
- then `None` is returned.
-
- If the return value is not `None` the hub is guaranteed to have a
- client attached.
- """
- return get_client().get_integration(name_or_class)
-
- @property
- def client(self) -> "Optional[BaseClient]":
- """
- .. deprecated:: 2.0.0
- This property is deprecated and will be removed in a future release.
- Please use :py:func:`sentry_sdk.api.get_client` instead.
-
- Returns the current client on the hub.
- """
- client = get_client()
-
- if not client.is_active():
- return None
-
- return client
-
- @property
- def scope(self) -> "Scope":
- """
- .. deprecated:: 2.0.0
- This property is deprecated and will be removed in a future release.
- Returns the current scope on the hub.
- """
- return get_isolation_scope()
-
- def last_event_id(self) -> "Optional[str]":
- """
- Returns the last event ID.
-
- .. deprecated:: 1.40.5
- This function is deprecated and will be removed in a future release. The functions `capture_event`, `capture_message`, and `capture_exception` return the event ID directly.
- """
- logger.warning(
- "Deprecated: last_event_id is deprecated. This will be removed in the future. The functions `capture_event`, `capture_message`, and `capture_exception` return the event ID directly."
- )
- return self._last_event_id
-
- def bind_client(
- self,
- new: "Optional[BaseClient]",
- ) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.set_client` instead.
-
- Binds a new client to the hub.
- """
- get_global_scope().set_client(new)
-
- def capture_event(
- self,
- event: "Event",
- hint: "Optional[Hint]" = None,
- scope: "Optional[Scope]" = None,
- **scope_kwargs: "Any",
- ) -> "Optional[str]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.capture_event` instead.
-
- Captures an event.
-
- Alias of :py:meth:`sentry_sdk.Scope.capture_event`.
-
- :param event: A ready-made event that can be directly sent to Sentry.
-
- :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.
-
- :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
- The `scope` and `scope_kwargs` parameters are mutually exclusive.
-
- :param scope_kwargs: Optional data to apply to event.
- For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`.
- The `scope` and `scope_kwargs` parameters are mutually exclusive.
- """
- last_event_id = get_current_scope().capture_event(
- event, hint, scope=scope, **scope_kwargs
- )
-
- is_transaction = event.get("type") == "transaction"
- if last_event_id is not None and not is_transaction:
- self._last_event_id = last_event_id
-
- return last_event_id
-
- def capture_message(
- self,
- message: str,
- level: "Optional[LogLevelStr]" = None,
- scope: "Optional[Scope]" = None,
- **scope_kwargs: "Any",
- ) -> "Optional[str]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.capture_message` instead.
-
- Captures a message.
-
- Alias of :py:meth:`sentry_sdk.Scope.capture_message`.
-
- :param message: The string to send as the message to Sentry.
-
- :param level: If no level is provided, the default level is `info`.
-
- :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
- The `scope` and `scope_kwargs` parameters are mutually exclusive.
-
- :param scope_kwargs: Optional data to apply to event.
- For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`.
- The `scope` and `scope_kwargs` parameters are mutually exclusive.
-
- :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`).
- """
- last_event_id = get_current_scope().capture_message(
- message, level=level, scope=scope, **scope_kwargs
- )
-
- if last_event_id is not None:
- self._last_event_id = last_event_id
-
- return last_event_id
-
- def capture_exception(
- self,
- error: "Optional[Union[BaseException, ExcInfo]]" = None,
- scope: "Optional[Scope]" = None,
- **scope_kwargs: "Any",
- ) -> "Optional[str]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.capture_exception` instead.
-
- Captures an exception.
-
- Alias of :py:meth:`sentry_sdk.Scope.capture_exception`.
-
- :param error: An exception to capture. If `None`, `sys.exc_info()` will be used.
-
- :param scope: An optional :py:class:`sentry_sdk.Scope` to apply to events.
- The `scope` and `scope_kwargs` parameters are mutually exclusive.
-
- :param scope_kwargs: Optional data to apply to event.
- For supported `**scope_kwargs` see :py:meth:`sentry_sdk.Scope.update_from_kwargs`.
- The `scope` and `scope_kwargs` parameters are mutually exclusive.
-
- :returns: An `event_id` if the SDK decided to send the event (see :py:meth:`sentry_sdk.client._Client.capture_event`).
- """
- last_event_id = get_current_scope().capture_exception(
- error, scope=scope, **scope_kwargs
- )
-
- if last_event_id is not None:
- self._last_event_id = last_event_id
-
- return last_event_id
-
- def add_breadcrumb(
- self,
- crumb: "Optional[Breadcrumb]" = None,
- hint: "Optional[BreadcrumbHint]" = None,
- **kwargs: "Any",
- ) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.add_breadcrumb` instead.
-
- Adds a breadcrumb.
-
- :param crumb: Dictionary with the data as the sentry v7/v8 protocol expects.
-
- :param hint: An optional value that can be used by `before_breadcrumb`
- to customize the breadcrumbs that are emitted.
- """
- get_isolation_scope().add_breadcrumb(crumb, hint, **kwargs)
-
- def start_span(
- self, instrumenter: str = INSTRUMENTER.SENTRY, **kwargs: "Any"
- ) -> "Span":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.start_span` instead.
-
- Start a span whose parent is the currently active span or transaction, if any.
-
- The return value is a :py:class:`sentry_sdk.tracing.Span` instance,
- typically used as a context manager to start and stop timing in a `with`
- block.
-
- Only spans contained in a transaction are sent to Sentry. Most
- integrations start a transaction at the appropriate time, for example
- for every incoming HTTP request. Use
- :py:meth:`sentry_sdk.start_transaction` to start a new transaction when
- one is not already in progress.
-
- For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
- """
- scope = get_current_scope()
- return scope.start_span(instrumenter=instrumenter, **kwargs)
-
- def start_transaction(
- self,
- transaction: "Optional[Transaction]" = None,
- instrumenter: str = INSTRUMENTER.SENTRY,
- custom_sampling_context: "Optional[SamplingContext]" = None,
- **kwargs: "Unpack[TransactionKwargs]",
- ) -> "Union[Transaction, NoOpSpan]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.start_transaction` instead.
-
- Start and return a transaction.
-
- Start an existing transaction if given, otherwise create and start a new
- transaction with kwargs.
-
- This is the entry point to manual tracing instrumentation.
-
- A tree structure can be built by adding child spans to the transaction,
- and child spans to other spans. To start a new child span within the
- transaction or any span, call the respective `.start_child()` method.
-
- Every child span must be finished before the transaction is finished,
- otherwise the unfinished spans are discarded.
-
- When used as context managers, spans and transactions are automatically
- finished at the end of the `with` block. If not using context managers,
- call the `.finish()` method.
-
- When the transaction is finished, it will be sent to Sentry with all its
- finished child spans.
-
- For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Transaction`.
- """
- scope = get_current_scope()
-
- # For backwards compatibility, we allow passing the scope as the hub.
- # We need a major release to make this nice. (if someone searches the code: deprecated)
- # Type checking disabled for this line because deprecated keys are not allowed in the type signature.
- kwargs["hub"] = scope # type: ignore
-
- return scope.start_transaction(
- transaction, instrumenter, custom_sampling_context, **kwargs
- )
-
- def continue_trace(
- self,
- environ_or_headers: "Dict[str, Any]",
- op: "Optional[str]" = None,
- name: "Optional[str]" = None,
- source: "Optional[str]" = None,
- ) -> "Transaction":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.continue_trace` instead.
-
- Sets the propagation context from environment or headers and returns a transaction.
- """
- return get_isolation_scope().continue_trace(
- environ_or_headers=environ_or_headers, op=op, name=name, source=source
- )
-
- @overload
- def push_scope(
- self,
- callback: "Optional[None]" = None,
- ) -> "ContextManager[Scope]":
- pass
-
- @overload
- def push_scope( # noqa: F811
- self,
- callback: "Callable[[Scope], None]",
- ) -> None:
- pass
-
- def push_scope( # noqa
- self,
- callback: "Optional[Callable[[Scope], None]]" = None,
- continue_trace: bool = True,
- ) -> "Optional[ContextManager[Scope]]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
-
- Pushes a new layer on the scope stack.
-
- :param callback: If provided, this method pushes a scope, calls
- `callback`, and pops the scope again.
-
- :returns: If no `callback` is provided, a context manager that should
- be used to pop the scope again.
- """
- if callback is not None:
- with self.push_scope() as scope:
- callback(scope)
- return None
-
- return _ScopeManager(self)
-
- def pop_scope_unsafe(self) -> "Tuple[Optional[Client], Scope]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
-
- Pops a scope layer from the stack.
-
- Try to use the context manager :py:meth:`push_scope` instead.
- """
- rv = self._stack.pop()
- assert self._stack, "stack must have at least one layer"
- return rv
-
- @overload
- def configure_scope(
- self,
- callback: "Optional[None]" = None,
- ) -> "ContextManager[Scope]":
- pass
-
- @overload
- def configure_scope( # noqa: F811
- self,
- callback: "Callable[[Scope], None]",
- ) -> None:
- pass
-
- def configure_scope( # noqa
- self,
- callback: "Optional[Callable[[Scope], None]]" = None,
- continue_trace: bool = True,
- ) -> "Optional[ContextManager[Scope]]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
-
- Reconfigures the scope.
-
- :param callback: If provided, call the callback with the current scope.
-
- :returns: If no callback is provided, returns a context manager that returns the scope.
- """
- scope = get_isolation_scope()
-
- if continue_trace:
- scope.generate_propagation_context()
-
- if callback is not None:
- # TODO: used to return None when client is None. Check if this changes behavior.
- callback(scope)
-
- return None
-
- @contextmanager
- def inner() -> "Generator[Scope, None, None]":
- yield scope
-
- return inner()
-
- def start_session(
- self,
- session_mode: str = "application",
- ) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.start_session` instead.
-
- Starts a new session.
- """
- get_isolation_scope().start_session(
- session_mode=session_mode,
- )
-
- def end_session(self) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.end_session` instead.
-
- Ends the current session if there is one.
- """
- get_isolation_scope().end_session()
-
- def stop_auto_session_tracking(self) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.stop_auto_session_tracking` instead.
-
- Stops automatic session tracking.
-
- This temporarily session tracking for the current scope when called.
- To resume session tracking call `resume_auto_session_tracking`.
- """
- get_isolation_scope().stop_auto_session_tracking()
-
- def resume_auto_session_tracking(self) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.resume_auto_session_tracking` instead.
-
- Resumes automatic session tracking for the current scope if
- disabled earlier. This requires that generally automatic session
- tracking is enabled.
- """
- get_isolation_scope().resume_auto_session_tracking()
-
- def flush(
- self,
- timeout: "Optional[float]" = None,
- callback: "Optional[Callable[[int, float], None]]" = None,
- ) -> None:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.client._Client.flush` instead.
-
- Alias for :py:meth:`sentry_sdk.client._Client.flush`
- """
- return get_client().flush(timeout=timeout, callback=callback)
-
- def get_traceparent(self) -> "Optional[str]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.get_traceparent` instead.
-
- Returns the traceparent either from the active span or from the scope.
- """
- current_scope = get_current_scope()
- traceparent = current_scope.get_traceparent()
-
- if traceparent is None:
- isolation_scope = get_isolation_scope()
- traceparent = isolation_scope.get_traceparent()
-
- return traceparent
-
- def get_baggage(self) -> "Optional[str]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.get_baggage` instead.
-
- Returns Baggage either from the active span or from the scope.
- """
- current_scope = get_current_scope()
- baggage = current_scope.get_baggage()
-
- if baggage is None:
- isolation_scope = get_isolation_scope()
- baggage = isolation_scope.get_baggage()
-
- if baggage is not None:
- return baggage.serialize()
-
- return None
-
- def iter_trace_propagation_headers(
- self, span: "Optional[Span]" = None
- ) -> "Generator[Tuple[str, str], None, None]":
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.iter_trace_propagation_headers` instead.
-
- Return HTTP headers which allow propagation of trace data. Data taken
- from the span representing the request, if available, or the current
- span on the scope if not.
- """
- return get_current_scope().iter_trace_propagation_headers(
- span=span,
- )
-
- def trace_propagation_meta(self, span: "Optional[Span]" = None) -> str:
- """
- .. deprecated:: 2.0.0
- This function is deprecated and will be removed in a future release.
- Please use :py:meth:`sentry_sdk.Scope.trace_propagation_meta` instead.
-
- Return meta tags which should be injected into HTML templates
- to allow propagation of trace information.
- """
- if span is not None:
- logger.warning(
- "The parameter `span` in trace_propagation_meta() is deprecated and will be removed in the future."
- )
-
- return get_current_scope().trace_propagation_meta(
- span=span,
- )
-
-
-with _suppress_hub_deprecation_warning():
- # Suppress deprecation warning for the Hub here, since we still always
- # import this module.
- GLOBAL_HUB = Hub()
-_local.set(GLOBAL_HUB)
-
-
-# Circular imports
-from sentry_sdk import scope
diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py
index 677d34a81e..7aa496cec2 100644
--- a/sentry_sdk/integrations/__init__.py
+++ b/sentry_sdk/integrations/__init__.py
@@ -1,3 +1,4 @@
+import sys
from abc import ABC, abstractmethod
from threading import Lock
from typing import TYPE_CHECKING
@@ -64,6 +65,11 @@ def iter_default_integrations(
"sentry_sdk.integrations.threading.ThreadingIntegration",
]
+if sys.version_info >= (3, 8):
+ _DEFAULT_INTEGRATIONS.append(
+ "sentry_sdk.integrations.unraisablehook.UnraisablehookIntegration"
+ )
+
_AUTO_ENABLING_INTEGRATIONS = [
"sentry_sdk.integrations.aiohttp.AioHttpIntegration",
"sentry_sdk.integrations.anthropic.AnthropicIntegration",
@@ -104,7 +110,6 @@ def iter_default_integrations(
"sentry_sdk.integrations.sqlalchemy.SqlalchemyIntegration",
"sentry_sdk.integrations.starlette.StarletteIntegration",
"sentry_sdk.integrations.starlite.StarliteIntegration",
- "sentry_sdk.integrations.strawberry.StrawberryIntegration",
"sentry_sdk.integrations.tornado.TornadoIntegration",
]
diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py
index 50db021a62..a538363b8c 100644
--- a/sentry_sdk/integrations/_wsgi_common.py
+++ b/sentry_sdk/integrations/_wsgi_common.py
@@ -5,7 +5,7 @@
from sentry_sdk._types import SENSITIVE_DATA_SUBSTITUTE
from sentry_sdk.data_collection import _apply_key_value_collection_filtering
from sentry_sdk.scope import should_send_default_pii
-from sentry_sdk.utils import AnnotatedValue, has_data_collection_enabled, logger
+from sentry_sdk.utils import AnnotatedValue, has_data_collection_enabled
try:
from django.http.request import RawPostDataException
@@ -20,7 +20,7 @@
if TYPE_CHECKING:
from typing import Any, Dict, Mapping, MutableMapping, Optional, Union
- from sentry_sdk._types import Event, HttpStatusCodeRange
+ from sentry_sdk._types import Event
SENSITIVE_ENV_KEYS = (
@@ -247,36 +247,3 @@ def _filter_headers(
)
for k, v in headers.items()
}
-
-
-def _in_http_status_code_range(
- code: object, code_ranges: "list[HttpStatusCodeRange]"
-) -> bool:
- for target in code_ranges:
- if isinstance(target, int):
- if code == target:
- return True
- continue
-
- try:
- if code in target:
- return True
- except TypeError:
- logger.warning(
- "failed_request_status_codes has to be a list of integers or containers"
- )
-
- return False
-
-
-class HttpCodeRangeContainer:
- """
- Wrapper to make it possible to use list[HttpStatusCodeRange] as a Container[int].
- Used for backwards compatibility with the old `failed_request_status_codes` option.
- """
-
- def __init__(self, code_ranges: "list[HttpStatusCodeRange]") -> None:
- self._code_ranges = code_ranges
-
- def __contains__(self, item: object) -> bool:
- return _in_http_status_code_range(item, self._code_ranges)
diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py
index d594c3504d..fa240e35d6 100644
--- a/sentry_sdk/integrations/asgi.py
+++ b/sentry_sdk/integrations/asgi.py
@@ -206,15 +206,6 @@ async def _run_app(
return await self.app(scope, receive, send)
except Exception as exc:
- suppress_chained_exceptions = (
- sentry_sdk.get_client()
- .options.get("_experiments", {})
- .get("suppress_asgi_chained_exceptions", True)
- )
- if suppress_chained_exceptions:
- self._capture_lifespan_exception(exc)
- raise exc from None
-
exc_info = sys.exc_info()
with capture_internal_exceptions():
self._capture_lifespan_exception(exc)
@@ -373,15 +364,6 @@ async def _sentry_wrapped_send(
)
except Exception as exc:
- suppress_chained_exceptions = (
- sentry_sdk.get_client()
- .options.get("_experiments", {})
- .get("suppress_asgi_chained_exceptions", True)
- )
- if suppress_chained_exceptions:
- self._capture_request_exception(exc)
- raise exc from None
-
exc_info = sys.exc_info()
with capture_internal_exceptions():
self._capture_request_exception(exc)
diff --git a/sentry_sdk/integrations/asyncio.py b/sentry_sdk/integrations/asyncio.py
index 4fc600d36f..c7e25a6a76 100644
--- a/sentry_sdk/integrations/asyncio.py
+++ b/sentry_sdk/integrations/asyncio.py
@@ -173,8 +173,8 @@ async def _task_with_sentry_span_creation() -> "Any":
with span_ctx if span_ctx else nullcontext():
try:
result = await coro
- except StopAsyncIteration as e:
- raise e from None
+ except StopAsyncIteration:
+ raise
except Exception:
reraise(*_capture_exception())
diff --git a/sentry_sdk/integrations/django/asgi.py b/sentry_sdk/integrations/django/asgi.py
index df287e6436..f3b1fd0898 100644
--- a/sentry_sdk/integrations/django/asgi.py
+++ b/sentry_sdk/integrations/django/asgi.py
@@ -188,10 +188,6 @@ async def sentry_wrapped_callback(
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
- sentry_scope = sentry_sdk.get_isolation_scope()
- if sentry_scope.profile is not None:
- sentry_scope.profile.update_active_thread_id()
-
integration = client.get_integration(DjangoIntegration)
if not integration or not integration.middleware_spans:
return await callback(request, *args, **kwargs)
diff --git a/sentry_sdk/integrations/django/views.py b/sentry_sdk/integrations/django/views.py
index b24e4df45b..eacd3b74fe 100644
--- a/sentry_sdk/integrations/django/views.py
+++ b/sentry_sdk/integrations/django/views.py
@@ -99,12 +99,6 @@ def sentry_wrapped_callback(request: "Any", *args: "Any", **kwargs: "Any") -> "A
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
- sentry_scope = sentry_sdk.get_isolation_scope()
- # set the active thread id to the handler thread for sync views
- # this isn't necessary for async views since that runs on main
- if sentry_scope.profile is not None:
- sentry_scope.profile.update_active_thread_id()
-
integration = client.get_integration(DjangoIntegration)
if not integration or not integration.middleware_spans:
return callback(request, *args, **kwargs)
diff --git a/sentry_sdk/integrations/fastapi.py b/sentry_sdk/integrations/fastapi.py
index 0b048e11ee..14ee66eecf 100644
--- a/sentry_sdk/integrations/fastapi.py
+++ b/sentry_sdk/integrations/fastapi.py
@@ -181,10 +181,6 @@ def _sentry_call(*args: "Any", **kwargs: "Any") -> "Any":
elif current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
- sentry_scope = sentry_sdk.get_isolation_scope()
- if sentry_scope.profile is not None:
- sentry_scope.profile.update_active_thread_id()
-
return old_call(*args, **kwargs)
_sentry_call._sentry_is_patched = True # type: ignore[attr-defined]
diff --git a/sentry_sdk/integrations/opentelemetry/__init__.py b/sentry_sdk/integrations/opentelemetry/__init__.py
deleted file mode 100644
index d76b8058ee..0000000000
--- a/sentry_sdk/integrations/opentelemetry/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
-from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
-
-__all__ = [
- "SentryPropagator",
- "SentrySpanProcessor",
-]
diff --git a/sentry_sdk/integrations/opentelemetry/consts.py b/sentry_sdk/integrations/opentelemetry/consts.py
deleted file mode 100644
index d6733036ea..0000000000
--- a/sentry_sdk/integrations/opentelemetry/consts.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from sentry_sdk.integrations import DidNotEnable
-
-try:
- from opentelemetry.context import create_key
-except ImportError:
- raise DidNotEnable("opentelemetry not installed")
-
-SENTRY_TRACE_KEY = create_key("sentry-trace")
-SENTRY_BAGGAGE_KEY = create_key("sentry-baggage")
diff --git a/sentry_sdk/integrations/opentelemetry/integration.py b/sentry_sdk/integrations/opentelemetry/integration.py
deleted file mode 100644
index 472e8181f9..0000000000
--- a/sentry_sdk/integrations/opentelemetry/integration.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
-IMPORTANT: The contents of this file are part of a proof of concept and as such
-are experimental and not suitable for production use. They may be changed or
-removed at any time without prior notice.
-"""
-
-import warnings
-from typing import TYPE_CHECKING
-
-from sentry_sdk.integrations import DidNotEnable, Integration
-from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
-from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
-from sentry_sdk.tracing_utils import has_span_streaming_enabled
-from sentry_sdk.utils import logger
-
-if TYPE_CHECKING:
- from typing import Any, Dict, Optional
-
-try:
- from opentelemetry import trace
- from opentelemetry.propagate import set_global_textmap
- from opentelemetry.sdk.trace import TracerProvider
-except ImportError:
- raise DidNotEnable("opentelemetry not installed")
-
-try:
- from opentelemetry.instrumentation.django import ( # type: ignore[import-not-found]
- DjangoInstrumentor,
- )
-except ImportError:
- DjangoInstrumentor = None
-
-
-CONFIGURABLE_INSTRUMENTATIONS = {
- DjangoInstrumentor: {"is_sql_commentor_enabled": True},
-}
-
-
-class OpenTelemetryIntegration(Integration):
- identifier = "opentelemetry"
-
- @staticmethod
- def setup_once() -> None:
- pass
-
- def setup_once_with_options(
- self, options: "Optional[Dict[str, Any]]" = None
- ) -> None:
- if has_span_streaming_enabled(options):
- logger.warning(
- "[OTel] OpenTelemetryIntegration is not compatible with span streaming "
- "(trace_lifecycle='stream') and will be disabled."
- )
- return
-
- warnings.warn(
- "OpenTelemetryIntegration is deprecated. "
- "Please use OTLPIntegration instead: "
- "https://docs.sentry.io/platforms/python/integrations/otlp/",
- DeprecationWarning,
- stacklevel=2,
- )
-
- _setup_sentry_tracing()
- # _setup_instrumentors()
-
- logger.debug("[OTel] Finished setting up OpenTelemetry integration")
-
-
-def _setup_sentry_tracing() -> None:
- provider = TracerProvider()
- provider.add_span_processor(SentrySpanProcessor())
- trace.set_tracer_provider(provider)
- set_global_textmap(SentryPropagator())
-
-
-def _setup_instrumentors() -> None:
- for instrumentor, kwargs in CONFIGURABLE_INSTRUMENTATIONS.items():
- if instrumentor is None:
- continue
- instrumentor().instrument(**kwargs)
diff --git a/sentry_sdk/integrations/opentelemetry/propagator.py b/sentry_sdk/integrations/opentelemetry/propagator.py
deleted file mode 100644
index 5b5f13861d..0000000000
--- a/sentry_sdk/integrations/opentelemetry/propagator.py
+++ /dev/null
@@ -1,128 +0,0 @@
-from sentry_sdk.integrations import DidNotEnable
-from sentry_sdk.integrations.opentelemetry.consts import (
- SENTRY_BAGGAGE_KEY,
- SENTRY_TRACE_KEY,
-)
-from sentry_sdk.integrations.opentelemetry.span_processor import (
- SentrySpanProcessor,
-)
-from sentry_sdk.tracing import (
- BAGGAGE_HEADER_NAME,
- SENTRY_TRACE_HEADER_NAME,
-)
-from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
-
-try:
- from opentelemetry import trace
- from opentelemetry.context import (
- Context,
- get_current,
- set_value,
- )
- from opentelemetry.propagators.textmap import (
- CarrierT,
- Getter,
- Setter,
- TextMapPropagator,
- default_getter,
- default_setter,
- )
- from opentelemetry.trace import (
- NonRecordingSpan,
- SpanContext,
- TraceFlags,
- )
-except ImportError:
- raise DidNotEnable("opentelemetry not installed")
-
-from typing import TYPE_CHECKING
-
-if TYPE_CHECKING:
- from typing import Optional, Set
-
-
-class SentryPropagator(TextMapPropagator):
- """
- Propagates tracing headers for Sentry's tracing system in a way OTel understands.
- """
-
- def extract(
- self,
- carrier: "CarrierT",
- context: "Optional[Context]" = None,
- getter: "Getter[CarrierT]" = default_getter,
- ) -> "Context":
- if context is None:
- context = get_current()
-
- sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME)
- if not sentry_trace:
- return context
-
- sentrytrace = extract_sentrytrace_data(sentry_trace[0])
- if not sentrytrace:
- return context
-
- context = set_value(SENTRY_TRACE_KEY, sentrytrace, context)
-
- trace_id, span_id = sentrytrace["trace_id"], sentrytrace["parent_span_id"]
-
- span_context = SpanContext(
- trace_id=int(trace_id, 16), # type: ignore
- span_id=int(span_id, 16), # type: ignore
- # we simulate a sampled trace on the otel side and leave the sampling to sentry
- trace_flags=TraceFlags(TraceFlags.SAMPLED),
- is_remote=True,
- )
-
- baggage_header = getter.get(carrier, BAGGAGE_HEADER_NAME)
-
- if baggage_header:
- baggage = Baggage.from_incoming_header(baggage_header[0])
- else:
- # If there's an incoming sentry-trace but no incoming baggage header,
- # for instance in traces coming from older SDKs,
- # baggage will be empty and frozen and won't be populated as head SDK.
- baggage = Baggage(sentry_items={})
-
- baggage.freeze()
- context = set_value(SENTRY_BAGGAGE_KEY, baggage, context)
-
- span = NonRecordingSpan(span_context)
- modified_context = trace.set_span_in_context(span, context)
- return modified_context
-
- def inject(
- self,
- carrier: "CarrierT",
- context: "Optional[Context]" = None,
- setter: "Setter[CarrierT]" = default_setter,
- ) -> None:
- if context is None:
- context = get_current()
-
- current_span = trace.get_current_span(context)
- current_span_context = current_span.get_span_context()
-
- if not current_span_context.is_valid:
- return
-
- span_id = trace.format_span_id(current_span_context.span_id)
-
- span_map = SentrySpanProcessor.otel_span_map
- sentry_span = span_map.get(span_id, None)
- if not sentry_span:
- return
-
- setter.set(carrier, SENTRY_TRACE_HEADER_NAME, sentry_span.to_traceparent())
-
- if sentry_span.containing_transaction:
- baggage = sentry_span.containing_transaction.get_baggage()
- if baggage:
- baggage_data = baggage.serialize()
- if baggage_data:
- setter.set(carrier, BAGGAGE_HEADER_NAME, baggage_data)
-
- @property
- def fields(self) -> "Set[str]":
- return {SENTRY_TRACE_HEADER_NAME, BAGGAGE_HEADER_NAME}
diff --git a/sentry_sdk/integrations/opentelemetry/span_processor.py b/sentry_sdk/integrations/opentelemetry/span_processor.py
deleted file mode 100644
index 1efd2ac455..0000000000
--- a/sentry_sdk/integrations/opentelemetry/span_processor.py
+++ /dev/null
@@ -1,407 +0,0 @@
-from datetime import datetime, timezone
-from time import time
-from typing import TYPE_CHECKING, cast
-
-from urllib3.util import parse_url as urlparse
-
-from sentry_sdk import get_client, start_transaction
-from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS
-from sentry_sdk.integrations import DidNotEnable
-from sentry_sdk.integrations.opentelemetry.consts import (
- SENTRY_BAGGAGE_KEY,
- SENTRY_TRACE_KEY,
-)
-from sentry_sdk.scope import add_global_event_processor
-from sentry_sdk.tracing import Span as SentrySpan
-from sentry_sdk.tracing import Transaction
-
-try:
- from opentelemetry.context import get_value
- from opentelemetry.sdk.trace import ReadableSpan as OTelSpan
- from opentelemetry.sdk.trace import SpanProcessor
- from opentelemetry.semconv.trace import SpanAttributes
- from opentelemetry.trace import (
- SpanKind,
- format_span_id,
- format_trace_id,
- get_current_span,
- )
- from opentelemetry.trace.span import (
- INVALID_SPAN_ID,
- INVALID_TRACE_ID,
- )
-except ImportError:
- raise DidNotEnable("opentelemetry not installed")
-
-if TYPE_CHECKING:
- from typing import Any, Optional, Union
-
- from opentelemetry import context as context_api
- from opentelemetry.trace import SpanContext
-
- from sentry_sdk._types import Event, Hint
-
-OPEN_TELEMETRY_CONTEXT = "otel"
-SPAN_MAX_TIME_OPEN_MINUTES = 10
-SPAN_ORIGIN = "auto.otel"
-
-
-def link_trace_context_to_error_event(
- event: "Event", otel_span_map: "dict[str, Union[Transaction, SentrySpan]]"
-) -> "Event":
- client = get_client()
-
- if client.options["instrumenter"] != INSTRUMENTER.OTEL:
- return event
-
- if hasattr(event, "type") and event["type"] == "transaction":
- return event
-
- otel_span = get_current_span()
- if not otel_span:
- return event
-
- ctx = otel_span.get_span_context()
-
- if ctx.trace_id == INVALID_TRACE_ID or ctx.span_id == INVALID_SPAN_ID:
- return event
-
- sentry_span = otel_span_map.get(format_span_id(ctx.span_id), None)
- if not sentry_span:
- return event
-
- contexts = event.setdefault("contexts", {})
- contexts.setdefault("trace", {}).update(sentry_span.get_trace_context())
-
- return event
-
-
-class SentrySpanProcessor(SpanProcessor):
- """
- Converts OTel spans into Sentry spans so they can be sent to the Sentry backend.
- """
-
- # The mapping from otel span ids to sentry spans
- otel_span_map: "dict[str, Union[Transaction, SentrySpan]]" = {}
-
- # The currently open spans. Elements will be discarded after SPAN_MAX_TIME_OPEN_MINUTES
- open_spans: "dict[int, set[str]]" = {}
-
- initialized: "bool" = False
-
- def __new__(cls) -> "SentrySpanProcessor":
- if not hasattr(cls, "instance"):
- cls.instance = super().__new__(cls)
-
- # "instance" class attribute is guaranteed to be set above (mypy believes instance is an instance-only attribute).
- return cls.instance # type: ignore[misc]
-
- def __init__(self) -> None:
- if self.initialized:
- return
-
- @add_global_event_processor
- def global_event_processor(event: "Event", hint: "Hint") -> "Event":
- return link_trace_context_to_error_event(event, self.otel_span_map)
-
- self.initialized = True
-
- def _prune_old_spans(self: "SentrySpanProcessor") -> None:
- """
- Prune spans that have been open for too long.
- """
- current_time_minutes = int(time() / 60)
- for span_start_minutes in list(
- self.open_spans.keys()
- ): # making a list because we change the dict
- # prune empty open spans buckets
- if self.open_spans[span_start_minutes] == set():
- self.open_spans.pop(span_start_minutes)
-
- # prune old buckets
- elif current_time_minutes - span_start_minutes > SPAN_MAX_TIME_OPEN_MINUTES:
- for span_id in self.open_spans.pop(span_start_minutes):
- self.otel_span_map.pop(span_id, None)
-
- def on_start(
- self,
- otel_span: "OTelSpan",
- parent_context: "Optional[context_api.Context]" = None,
- ) -> None:
- client = get_client()
-
- if not client.parsed_dsn:
- return
-
- if client.options["instrumenter"] != INSTRUMENTER.OTEL:
- return
-
- span_context = otel_span.get_span_context()
- if span_context is None or not span_context.is_valid:
- return
-
- if self._is_sentry_span(otel_span):
- return
-
- trace_data = self._get_trace_data(
- span_context, otel_span.parent, parent_context
- )
-
- parent_span_id = trace_data["parent_span_id"]
- sentry_parent_span = (
- self.otel_span_map.get(parent_span_id) if parent_span_id else None
- )
-
- start_timestamp = None
- if otel_span.start_time is not None:
- start_timestamp = datetime.fromtimestamp(
- otel_span.start_time / 1e9, timezone.utc
- ) # OTel spans have nanosecond precision
-
- sentry_span = None
- if sentry_parent_span:
- sentry_span = sentry_parent_span.start_child(
- span_id=trace_data["span_id"],
- name=otel_span.name,
- start_timestamp=start_timestamp,
- instrumenter=INSTRUMENTER.OTEL,
- origin=SPAN_ORIGIN,
- )
- else:
- sentry_span = start_transaction(
- name=otel_span.name,
- span_id=trace_data["span_id"],
- parent_span_id=parent_span_id,
- trace_id=trace_data["trace_id"],
- baggage=trace_data["baggage"],
- start_timestamp=start_timestamp,
- instrumenter=INSTRUMENTER.OTEL,
- origin=SPAN_ORIGIN,
- )
-
- self.otel_span_map[trace_data["span_id"]] = sentry_span
-
- if otel_span.start_time is not None:
- span_start_in_minutes = int(
- otel_span.start_time / 1e9 / 60
- ) # OTel spans have nanosecond precision
- self.open_spans.setdefault(span_start_in_minutes, set()).add(
- trace_data["span_id"]
- )
-
- self._prune_old_spans()
-
- def on_end(self, otel_span: "OTelSpan") -> None:
- client = get_client()
-
- if client.options["instrumenter"] != INSTRUMENTER.OTEL:
- return
-
- span_context = otel_span.get_span_context()
- if span_context is None or not span_context.is_valid:
- return
-
- span_id = format_span_id(span_context.span_id)
- sentry_span = self.otel_span_map.pop(span_id, None)
- if not sentry_span:
- return
-
- sentry_span.op = otel_span.name
-
- self._update_span_with_otel_status(sentry_span, otel_span)
-
- if isinstance(sentry_span, Transaction):
- sentry_span.name = otel_span.name
- sentry_span.set_context(
- OPEN_TELEMETRY_CONTEXT, self._get_otel_context(otel_span)
- )
- self._update_transaction_with_otel_data(sentry_span, otel_span)
-
- else:
- self._update_span_with_otel_data(sentry_span, otel_span)
-
- end_timestamp = None
- if otel_span.end_time is not None:
- end_timestamp = datetime.fromtimestamp(
- otel_span.end_time / 1e9, timezone.utc
- ) # OTel spans have nanosecond precision
-
- sentry_span.finish(end_timestamp=end_timestamp)
-
- if otel_span.start_time is not None:
- span_start_in_minutes = int(
- otel_span.start_time / 1e9 / 60
- ) # OTel spans have nanosecond precision
- self.open_spans.setdefault(span_start_in_minutes, set()).discard(span_id)
-
- self._prune_old_spans()
-
- def _is_sentry_span(self, otel_span: "OTelSpan") -> bool:
- """
- Break infinite loop:
- HTTP requests to Sentry are caught by OTel and send again to Sentry.
- """
- otel_span_url = None
- if otel_span.attributes is not None:
- otel_span_url = otel_span.attributes.get(SpanAttributes.HTTP_URL)
- otel_span_url = cast("Optional[str]", otel_span_url)
-
- parsed_dsn = get_client().parsed_dsn
- dsn_url = parsed_dsn.netloc if parsed_dsn else None
-
- if otel_span_url and dsn_url and dsn_url in otel_span_url:
- return True
-
- return False
-
- def _get_otel_context(self, otel_span: "OTelSpan") -> "dict[str, Any]":
- """
- Returns the OTel context for Sentry.
- See: https://develop.sentry.dev/sdk/performance/opentelemetry/#step-5-add-opentelemetry-context
- """
- ctx = {}
-
- if otel_span.attributes:
- ctx["attributes"] = dict(otel_span.attributes)
-
- if otel_span.resource.attributes:
- ctx["resource"] = dict(otel_span.resource.attributes)
-
- return ctx
-
- def _get_trace_data(
- self,
- span_context: "SpanContext",
- parent_span_context: "Optional[SpanContext]",
- parent_context: "Optional[context_api.Context]",
- ) -> "dict[str, Any]":
- """
- Extracts tracing information from one OTel span's context and its parent OTel context.
- """
- trace_data: "dict[str, Any]" = {}
-
- span_id = format_span_id(span_context.span_id)
- trace_data["span_id"] = span_id
-
- trace_id = format_trace_id(span_context.trace_id)
- trace_data["trace_id"] = trace_id
-
- parent_span_id = (
- format_span_id(parent_span_context.span_id) if parent_span_context else None
- )
- trace_data["parent_span_id"] = parent_span_id
-
- sentry_trace_data = get_value(SENTRY_TRACE_KEY, parent_context)
- sentry_trace_data = cast("dict[str, Union[str, bool, None]]", sentry_trace_data)
- trace_data["parent_sampled"] = (
- sentry_trace_data["parent_sampled"] if sentry_trace_data else None
- )
-
- baggage = get_value(SENTRY_BAGGAGE_KEY, parent_context)
- trace_data["baggage"] = baggage
-
- return trace_data
-
- def _update_span_with_otel_status(
- self, sentry_span: "SentrySpan", otel_span: "OTelSpan"
- ) -> None:
- """
- Set the Sentry span status from the OTel span
- """
- if otel_span.status.is_unset:
- return
-
- if otel_span.status.is_ok:
- sentry_span.set_status(SPANSTATUS.OK)
- return
-
- sentry_span.set_status(SPANSTATUS.INTERNAL_ERROR)
-
- def _update_span_with_otel_data(
- self, sentry_span: "SentrySpan", otel_span: "OTelSpan"
- ) -> None:
- """
- Convert OTel span data and update the Sentry span with it.
- This should eventually happen on the server when ingesting the spans.
- """
- sentry_span.set_data("otel.kind", otel_span.kind)
-
- op = otel_span.name
- description = otel_span.name
-
- if otel_span.attributes is not None:
- for key, val in otel_span.attributes.items():
- sentry_span.set_data(key, val)
-
- http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD)
- http_method = cast("Optional[str]", http_method)
-
- db_query = otel_span.attributes.get(SpanAttributes.DB_SYSTEM)
-
- if http_method:
- op = "http"
-
- if otel_span.kind == SpanKind.SERVER:
- op += ".server"
- elif otel_span.kind == SpanKind.CLIENT:
- op += ".client"
-
- description = http_method
-
- peer_name = otel_span.attributes.get(SpanAttributes.NET_PEER_NAME, None)
- if peer_name:
- description += " {}".format(peer_name)
-
- target = otel_span.attributes.get(SpanAttributes.HTTP_TARGET, None)
- if target:
- description += " {}".format(target)
-
- if not peer_name and not target:
- url = otel_span.attributes.get(SpanAttributes.HTTP_URL, None)
- url = cast("Optional[str]", url)
- if url:
- parsed_url = urlparse(url)
- url = "{}://{}{}".format(
- parsed_url.scheme, parsed_url.netloc, parsed_url.path
- )
- description += " {}".format(url)
-
- status_code = otel_span.attributes.get(
- SpanAttributes.HTTP_STATUS_CODE, None
- )
- status_code = cast("Optional[int]", status_code)
- if status_code:
- sentry_span.set_http_status(status_code)
-
- elif db_query:
- op = "db"
- statement = otel_span.attributes.get(SpanAttributes.DB_STATEMENT, None)
- statement = cast("Optional[str]", statement)
- if statement:
- description = statement
-
- sentry_span.op = op
- sentry_span.description = description
-
- def _update_transaction_with_otel_data(
- self, sentry_span: "SentrySpan", otel_span: "OTelSpan"
- ) -> None:
- if otel_span.attributes is None:
- return
-
- http_method = otel_span.attributes.get(SpanAttributes.HTTP_METHOD)
-
- if http_method:
- status_code = otel_span.attributes.get(SpanAttributes.HTTP_STATUS_CODE)
- status_code = cast("Optional[int]", status_code)
- if status_code:
- sentry_span.set_http_status(status_code)
-
- op = "http"
-
- if otel_span.kind == SpanKind.SERVER:
- op += ".server"
- elif otel_span.kind == SpanKind.CLIENT:
- op += ".client"
-
- sentry_span.op = op
diff --git a/sentry_sdk/integrations/otlp.py b/sentry_sdk/integrations/otlp.py
index b91f2cfd21..5b26752700 100644
--- a/sentry_sdk/integrations/otlp.py
+++ b/sentry_sdk/integrations/otlp.py
@@ -6,7 +6,7 @@
BAGGAGE_HEADER_NAME,
SENTRY_TRACE_HEADER_NAME,
)
-from sentry_sdk.tracing_utils import Baggage
+from sentry_sdk.tracing_utils import Baggage, extract_sentrytrace_data
from sentry_sdk.utils import (
Dsn,
capture_internal_exceptions,
@@ -15,16 +15,22 @@
)
try:
+ from opentelemetry import trace
from opentelemetry.context import (
Context,
+ create_key,
get_current,
get_value,
+ set_value,
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.textmap import (
CarrierT,
+ Getter,
Setter,
+ TextMapPropagator,
+ default_getter,
default_setter,
)
from opentelemetry.sdk.trace import Span, TracerProvider
@@ -32,23 +38,27 @@
from opentelemetry.trace import (
INVALID_SPAN_ID,
INVALID_TRACE_ID,
+ NonRecordingSpan,
SpanContext,
+ TraceFlags,
format_span_id,
format_trace_id,
get_current_span,
get_tracer_provider,
set_tracer_provider,
)
-
- from sentry_sdk.integrations.opentelemetry.consts import SENTRY_BAGGAGE_KEY
- from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
except ImportError:
raise DidNotEnable("opentelemetry-distro[otlp] is not installed")
+
from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from typing import Any, Dict, Optional, Tuple
+ from typing import Any, Dict, Optional, Set, Tuple
+
+
+SENTRY_BAGGAGE_KEY = create_key("sentry-baggage")
+SENTRY_TRACE_KEY = create_key("sentry-trace")
def otel_propagation_context() -> "Optional[Tuple[str, str]]":
@@ -121,11 +131,8 @@ def _sentry_patched_record_exception(
_sentry_patched_exception = True
-class SentryOTLPPropagator(SentryPropagator):
+class SentryOTLPPropagator(TextMapPropagator):
"""
- We need to override the inject of the older propagator since that
- is SpanProcessor based.
-
!!! Note regarding baggage:
We cannot meaningfully populate a new baggage as a head SDK
when we are using OTLP since we don't have any sort of transaction semantic to
@@ -134,6 +141,52 @@ class SentryOTLPPropagator(SentryPropagator):
For incoming baggage, we just pass it on as is so that case is correctly handled.
"""
+ def extract(
+ self,
+ carrier: "CarrierT",
+ context: "Optional[Context]" = None,
+ getter: "Getter[CarrierT]" = default_getter,
+ ) -> "Context":
+ if context is None:
+ context = get_current()
+
+ sentry_trace = getter.get(carrier, SENTRY_TRACE_HEADER_NAME)
+ if not sentry_trace:
+ return context
+
+ sentrytrace = extract_sentrytrace_data(sentry_trace[0])
+ if not sentrytrace:
+ return context
+
+ context = set_value(SENTRY_TRACE_KEY, sentrytrace, context)
+
+ trace_id, span_id = sentrytrace["trace_id"], sentrytrace["parent_span_id"]
+
+ span_context = SpanContext(
+ trace_id=int(trace_id, 16), # type: ignore
+ span_id=int(span_id, 16), # type: ignore
+ # we simulate a sampled trace on the otel side and leave the sampling to sentry
+ trace_flags=TraceFlags(TraceFlags.SAMPLED),
+ is_remote=True,
+ )
+
+ baggage_header = getter.get(carrier, BAGGAGE_HEADER_NAME)
+
+ if baggage_header:
+ baggage = Baggage.from_incoming_header(baggage_header[0])
+ else:
+ # If there's an incoming sentry-trace but no incoming baggage header,
+ # for instance in traces coming from older SDKs,
+ # baggage will be empty and frozen and won't be populated as head SDK.
+ baggage = Baggage(sentry_items={})
+
+ baggage.freeze()
+ context = set_value(SENTRY_BAGGAGE_KEY, baggage, context)
+
+ span = NonRecordingSpan(span_context)
+ modified_context = trace.set_span_in_context(span, context)
+ return modified_context
+
def inject(
self,
carrier: "CarrierT",
@@ -162,6 +215,10 @@ def inject(
if baggage_data:
setter.set(carrier, BAGGAGE_HEADER_NAME, baggage_data)
+ @property
+ def fields(self) -> "Set[str]":
+ return {SENTRY_TRACE_HEADER_NAME, BAGGAGE_HEADER_NAME}
+
def _to_traceparent(span_context: "SpanContext") -> str:
"""
diff --git a/sentry_sdk/integrations/quart.py b/sentry_sdk/integrations/quart.py
index 444ef93fa5..6ad19b19cc 100644
--- a/sentry_sdk/integrations/quart.py
+++ b/sentry_sdk/integrations/quart.py
@@ -133,10 +133,6 @@ def _sentry_func(*args: "Any", **kwargs: "Any") -> "Any":
if current_scope.transaction is not None:
current_scope.transaction.update_active_thread()
- sentry_scope = sentry_sdk.get_isolation_scope()
- if sentry_scope.profile is not None:
- sentry_scope.profile.update_active_thread_id()
-
return old_func(*args, **kwargs)
return old_decorator(_sentry_func)
diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py
index 2d839d1c61..9f1ece76c8 100644
--- a/sentry_sdk/integrations/sanic.py
+++ b/sentry_sdk/integrations/sanic.py
@@ -241,7 +241,7 @@ async def _context_exit(
response_status = None if response is None else response.status
# This capture_internal_exceptions block has been intentionally nested here, so that in case an exception
- # happens while trying to end the transaction, we still attempt to exit the hub.
+ # happens while trying to end the transaction, we still attempt to exit the scope.
with capture_internal_exceptions():
span = request.ctx._sentry_root_span
if isinstance(span, StreamedSpan):
diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py
index 7579c70c11..054cea6c12 100644
--- a/sentry_sdk/integrations/starlette.py
+++ b/sentry_sdk/integrations/starlette.py
@@ -1,7 +1,6 @@
import functools
import json
import sys
-import warnings
from collections.abc import Set
from copy import deepcopy
from json import JSONDecodeError
@@ -19,7 +18,6 @@
from sentry_sdk.integrations._asgi_common import _RootPathInPath
from sentry_sdk.integrations._wsgi_common import (
DEFAULT_HTTP_METHODS_TO_CAPTURE,
- HttpCodeRangeContainer,
_is_json_content_type,
request_body_within_bounds,
)
@@ -51,10 +49,9 @@
Dict,
Optional,
Tuple,
- Union,
)
- from sentry_sdk._types import Event, HttpStatusCodeRange
+ from sentry_sdk._types import Event
try:
import starlette
from starlette import __version__ as STARLETTE_VERSION
@@ -113,7 +110,7 @@ class StarletteIntegration(Integration):
def __init__(
self,
transaction_style: str = "url",
- failed_request_status_codes: "Union[Set[int], list[HttpStatusCodeRange], None]" = _DEFAULT_FAILED_REQUEST_STATUS_CODES,
+ failed_request_status_codes: "Set[int]" = _DEFAULT_FAILED_REQUEST_STATUS_CODES,
middleware_spans: bool = False,
http_methods_to_capture: "tuple[str, ...]" = DEFAULT_HTTP_METHODS_TO_CAPTURE,
):
@@ -126,24 +123,7 @@ def __init__(
self.middleware_spans = middleware_spans
self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture))
- if isinstance(failed_request_status_codes, Set):
- self.failed_request_status_codes: "Container[int]" = (
- failed_request_status_codes
- )
- else:
- warnings.warn(
- "Passing a list or None for failed_request_status_codes is deprecated. "
- "Please pass a set of int instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if failed_request_status_codes is None:
- self.failed_request_status_codes = _DEFAULT_FAILED_REQUEST_STATUS_CODES
- else:
- self.failed_request_status_codes = HttpCodeRangeContainer(
- failed_request_status_codes
- )
+ self.failed_request_status_codes: "Container[int]" = failed_request_status_codes
@staticmethod
def setup_once() -> None:
@@ -626,8 +606,6 @@ def _sentry_sync_func(*args: "Any", **kwargs: "Any") -> "Any":
current_scope.transaction.update_active_thread()
sentry_scope = sentry_sdk.get_isolation_scope()
- if sentry_scope.profile is not None:
- sentry_scope.profile.update_active_thread_id()
request = args[0]
diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py
index f3ba046332..606caa852e 100644
--- a/sentry_sdk/integrations/threading.py
+++ b/sentry_sdk/integrations/threading.py
@@ -11,7 +11,6 @@
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
- logger,
reraise,
)
@@ -27,23 +26,9 @@
class ThreadingIntegration(Integration):
identifier = "threading"
- def __init__(
- self, propagate_hub: "Optional[bool]" = None, propagate_scope: bool = True
- ) -> None:
- if propagate_hub is not None:
- logger.warning(
- "Deprecated: propagate_hub is deprecated. This will be removed in the future."
- )
-
- # Note: propagate_hub did not have any effect on propagation of scope data
- # scope data was always propagated no matter what the value of propagate_hub was
- # This is why the default for propagate_scope is True
-
+ def __init__(self, propagate_scope: bool = True) -> None:
self.propagate_scope = propagate_scope
- if propagate_hub is not None:
- self.propagate_scope = propagate_hub
-
@staticmethod
def setup_once() -> None:
old_start = Thread.start
diff --git a/sentry_sdk/profiler/__init__.py b/sentry_sdk/profiler/__init__.py
index d562405295..99d8cd2d61 100644
--- a/sentry_sdk/profiler/__init__.py
+++ b/sentry_sdk/profiler/__init__.py
@@ -4,17 +4,6 @@
stop_profile_session,
stop_profiler,
)
-from sentry_sdk.profiler.transaction_profiler import (
- MAX_PROFILE_DURATION_NS,
- PROFILE_MINIMUM_SAMPLES,
- GeventScheduler,
- Profile,
- Scheduler,
- ThreadScheduler,
- has_profiling_enabled,
- setup_profiler,
- teardown_profiler,
-)
from sentry_sdk.profiler.utils import (
DEFAULT_SAMPLING_FREQUENCY,
MAX_STACK_DEPTH,
@@ -29,17 +18,6 @@
"start_profiler",
"stop_profile_session", # TODO: Deprecate this in favor of `stop_profiler`
"stop_profiler",
- # DEPRECATED: The following was re-exported for backwards compatibility. It
- # will be removed from sentry_sdk.profiler in a future release.
- "MAX_PROFILE_DURATION_NS",
- "PROFILE_MINIMUM_SAMPLES",
- "Profile",
- "Scheduler",
- "ThreadScheduler",
- "GeventScheduler",
- "has_profiling_enabled",
- "setup_profiler",
- "teardown_profiler",
"DEFAULT_SAMPLING_FREQUENCY",
"MAX_STACK_DEPTH",
"get_frame_name",
diff --git a/sentry_sdk/profiler/transaction_profiler.py b/sentry_sdk/profiler/transaction_profiler.py
deleted file mode 100644
index fe65774c0b..0000000000
--- a/sentry_sdk/profiler/transaction_profiler.py
+++ /dev/null
@@ -1,802 +0,0 @@
-"""
-This file is originally based on code from https://github.com/nylas/nylas-perftools,
-which is published under the following license:
-
-The MIT License (MIT)
-
-Copyright (c) 2014 Nylas
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-"""
-
-import atexit
-import os
-import platform
-import random
-import sys
-import threading
-import time
-import uuid
-import warnings
-from abc import ABC, abstractmethod
-from collections import deque
-from typing import TYPE_CHECKING
-
-import sentry_sdk
-from sentry_sdk._lru_cache import LRUCache
-from sentry_sdk.profiler.utils import (
- DEFAULT_SAMPLING_FREQUENCY,
- extract_stack,
-)
-from sentry_sdk.utils import (
- capture_internal_exception,
- capture_internal_exceptions,
- get_current_thread_meta,
- is_gevent,
- is_valid_sample_rate,
- logger,
- nanosecond_time,
- set_in_app_in_frames,
-)
-
-if TYPE_CHECKING:
- from typing import Any, Callable, Deque, Dict, List, Optional, Set, Type
-
- from typing_extensions import TypedDict
-
- from sentry_sdk._types import Event, ProfilerMode, SamplingContext
- from sentry_sdk.profiler.utils import (
- ExtractedSample,
- FrameId,
- ProcessedFrame,
- ProcessedStack,
- ProcessedThreadMetadata,
- StackId,
- ThreadId,
- )
-
- ProcessedSample = TypedDict(
- "ProcessedSample",
- {
- "elapsed_since_start_ns": str,
- "thread_id": ThreadId,
- "stack_id": int,
- },
- )
-
- ProcessedProfile = TypedDict(
- "ProcessedProfile",
- {
- "frames": List[ProcessedFrame],
- "stacks": List[ProcessedStack],
- "samples": List[ProcessedSample],
- "thread_metadata": Dict[ThreadId, ProcessedThreadMetadata],
- },
- )
-
-
-try:
- from gevent.monkey import get_original
- from gevent.threadpool import ThreadPool as _ThreadPool
-
- ThreadPool: "Optional[Type[_ThreadPool]]" = _ThreadPool
- thread_sleep = get_original("time", "sleep")
-except ImportError:
- thread_sleep = time.sleep
-
- ThreadPool = None
-
-
-_scheduler: "Optional[Scheduler]" = None
-
-
-# The minimum number of unique samples that must exist in a profile to be
-# considered valid.
-PROFILE_MINIMUM_SAMPLES = 2
-
-
-def has_profiling_enabled(options: "Dict[str, Any]") -> bool:
- profiles_sampler = options["profiles_sampler"]
- if profiles_sampler is not None:
- return True
-
- profiles_sample_rate = options["profiles_sample_rate"]
- if profiles_sample_rate is not None and profiles_sample_rate > 0:
- return True
-
- profiles_sample_rate = options["_experiments"].get("profiles_sample_rate")
- if profiles_sample_rate is not None:
- logger.warning(
- "_experiments['profiles_sample_rate'] is deprecated. "
- "Please use the non-experimental profiles_sample_rate option "
- "directly."
- )
- if profiles_sample_rate > 0:
- return True
-
- return False
-
-
-def setup_profiler(options: "Dict[str, Any]") -> bool:
- global _scheduler
-
- if _scheduler is not None:
- logger.debug("[Profiling] Profiler is already setup")
- return False
-
- frequency = DEFAULT_SAMPLING_FREQUENCY
-
- if is_gevent():
- # If gevent has patched the threading modules then we cannot rely on
- # them to spawn a native thread for sampling.
- # Instead we default to the GeventScheduler which is capable of
- # spawning native threads within gevent.
- default_profiler_mode = GeventScheduler.mode
- else:
- default_profiler_mode = ThreadScheduler.mode
-
- if options.get("profiler_mode") is not None:
- profiler_mode = options["profiler_mode"]
- else:
- profiler_mode = options.get("_experiments", {}).get("profiler_mode")
- if profiler_mode is not None:
- logger.warning(
- "_experiments['profiler_mode'] is deprecated. Please use the "
- "non-experimental profiler_mode option directly."
- )
- profiler_mode = profiler_mode or default_profiler_mode
-
- if (
- profiler_mode == ThreadScheduler.mode
- # for legacy reasons, we'll keep supporting sleep mode for this scheduler
- or profiler_mode == "sleep"
- ):
- _scheduler = ThreadScheduler(frequency=frequency)
- elif profiler_mode == GeventScheduler.mode:
- _scheduler = GeventScheduler(frequency=frequency)
- else:
- raise ValueError("Unknown profiler mode: {}".format(profiler_mode))
-
- logger.debug(
- "[Profiling] Setting up profiler in {mode} mode".format(mode=_scheduler.mode)
- )
- _scheduler.setup()
-
- atexit.register(teardown_profiler)
-
- return True
-
-
-def teardown_profiler() -> None:
- global _scheduler
-
- if _scheduler is not None:
- _scheduler.teardown()
-
- _scheduler = None
-
-
-MAX_PROFILE_DURATION_NS = int(3e10) # 30 seconds
-
-
-class Profile:
- def __init__(
- self,
- sampled: "Optional[bool]",
- start_ns: int,
- hub: "Optional[sentry_sdk.Hub]" = None,
- scheduler: "Optional[Scheduler]" = None,
- ) -> None:
- self.scheduler = _scheduler if scheduler is None else scheduler
-
- self.event_id: str = uuid.uuid4().hex
-
- self.sampled: "Optional[bool]" = sampled
-
- # Various framework integrations are capable of overwriting the active thread id.
- # If it is set to `None` at the end of the profile, we fall back to the default.
- self._default_active_thread_id: int = get_current_thread_meta()[0] or 0
- self.active_thread_id: "Optional[int]" = None
-
- try:
- self.start_ns: int = start_ns
- except AttributeError:
- self.start_ns = 0
-
- self.stop_ns: int = 0
- self.active: bool = False
-
- self.indexed_frames: "Dict[FrameId, int]" = {}
- self.indexed_stacks: "Dict[StackId, int]" = {}
- self.frames: "List[ProcessedFrame]" = []
- self.stacks: "List[ProcessedStack]" = []
- self.samples: "List[ProcessedSample]" = []
-
- self.unique_samples = 0
-
- # Backwards compatibility with the old hub property
- self._hub: "Optional[sentry_sdk.Hub]" = None
- if hub is not None:
- self._hub = hub
- warnings.warn(
- "The `hub` parameter is deprecated. Please do not use it.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- def update_active_thread_id(self) -> None:
- self.active_thread_id = get_current_thread_meta()[0]
- logger.debug(
- "[Profiling] updating active thread id to {tid}".format(
- tid=self.active_thread_id
- )
- )
-
- def _set_initial_sampling_decision(
- self, sampling_context: "SamplingContext"
- ) -> None:
- """
- Sets the profile's sampling decision according to the following
- precedence rules:
-
- 1. If the transaction to be profiled is not sampled, that decision
- will be used, regardless of anything else.
-
- 2. Use `profiles_sample_rate` to decide.
- """
-
- # The corresponding transaction was not sampled,
- # so don't generate a profile for it.
- if not self.sampled:
- logger.debug(
- "[Profiling] Discarding profile because transaction is discarded."
- )
- self.sampled = False
- return
-
- # The profiler hasn't been properly initialized.
- if self.scheduler is None:
- logger.debug(
- "[Profiling] Discarding profile because profiler was not started."
- )
- self.sampled = False
- return
-
- client = sentry_sdk.get_client()
- if not client.is_active():
- self.sampled = False
- return
-
- options = client.options
-
- if callable(options.get("profiles_sampler")):
- sample_rate = options["profiles_sampler"](sampling_context)
- elif options["profiles_sample_rate"] is not None:
- sample_rate = options["profiles_sample_rate"]
- else:
- sample_rate = options["_experiments"].get("profiles_sample_rate")
-
- # The profiles_sample_rate option was not set, so profiling
- # was never enabled.
- if sample_rate is None:
- logger.debug(
- "[Profiling] Discarding profile because profiling was not enabled."
- )
- self.sampled = False
- return
-
- if not is_valid_sample_rate(sample_rate, source="Profiling"):
- logger.warning(
- "[Profiling] Discarding profile because of invalid sample rate."
- )
- self.sampled = False
- return
-
- # Now we roll the dice. random.random is inclusive of 0, but not of 1,
- # so strict < is safe here. In case sample_rate is a boolean, cast it
- # to a float (True becomes 1.0 and False becomes 0.0)
- self.sampled = random.random() < float(sample_rate)
-
- if self.sampled:
- logger.debug("[Profiling] Initializing profile")
- else:
- logger.debug(
- "[Profiling] Discarding profile because it's not included in the random sample (sample rate = {sample_rate})".format(
- sample_rate=float(sample_rate)
- )
- )
-
- def start(self) -> None:
- if not self.sampled or self.active:
- return
-
- assert self.scheduler, "No scheduler specified"
- logger.debug("[Profiling] Starting profile")
- self.active = True
- if not self.start_ns:
- self.start_ns = nanosecond_time()
- self.scheduler.start_profiling(self)
-
- def stop(self) -> None:
- if not self.sampled or not self.active:
- return
-
- assert self.scheduler, "No scheduler specified"
- logger.debug("[Profiling] Stopping profile")
- self.active = False
- self.stop_ns = nanosecond_time()
-
- def __enter__(self) -> "Profile":
- scope = sentry_sdk.get_isolation_scope()
- old_profile = scope.profile
- scope.profile = self
-
- self._context_manager_state = (scope, old_profile)
-
- self.start()
-
- return self
-
- def __exit__(
- self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
- ) -> None:
- with capture_internal_exceptions():
- self.stop()
-
- scope, old_profile = self._context_manager_state
- del self._context_manager_state
-
- scope.profile = old_profile
-
- def write(self, ts: int, sample: "ExtractedSample") -> None:
- if not self.active:
- return
-
- if ts < self.start_ns:
- return
-
- offset = ts - self.start_ns
- if offset > MAX_PROFILE_DURATION_NS:
- self.stop()
- return
-
- self.unique_samples += 1
-
- elapsed_since_start_ns = str(offset)
-
- for tid, (stack_id, frame_ids, frames) in sample:
- try:
- # Check if the stack is indexed first, this lets us skip
- # indexing frames if it's not necessary
- if stack_id not in self.indexed_stacks:
- for i, frame_id in enumerate(frame_ids):
- if frame_id not in self.indexed_frames:
- self.indexed_frames[frame_id] = len(self.indexed_frames)
- self.frames.append(frames[i])
-
- self.indexed_stacks[stack_id] = len(self.indexed_stacks)
- self.stacks.append(
- [self.indexed_frames[frame_id] for frame_id in frame_ids]
- )
-
- self.samples.append(
- {
- "elapsed_since_start_ns": elapsed_since_start_ns,
- "thread_id": tid,
- "stack_id": self.indexed_stacks[stack_id],
- }
- )
- except AttributeError:
- # For some reason, the frame we get doesn't have certain attributes.
- # When this happens, we abandon the current sample as it's bad.
- capture_internal_exception(sys.exc_info())
-
- def process(self) -> "ProcessedProfile":
- # This collects the thread metadata at the end of a profile. Doing it
- # this way means that any threads that terminate before the profile ends
- # will not have any metadata associated with it.
- thread_metadata: "Dict[str, ProcessedThreadMetadata]" = {
- str(thread.ident): {
- "name": str(thread.name),
- }
- for thread in threading.enumerate()
- }
-
- return {
- "frames": self.frames,
- "stacks": self.stacks,
- "samples": self.samples,
- "thread_metadata": thread_metadata,
- }
-
- def to_json(
- self, event_opt: "Event", options: "Dict[str, Any]"
- ) -> "Dict[str, Any]":
- profile = self.process()
-
- set_in_app_in_frames(
- profile["frames"],
- options["in_app_exclude"],
- options["in_app_include"],
- options["project_root"],
- )
-
- return {
- "environment": event_opt.get("environment"),
- "event_id": self.event_id,
- "platform": "python",
- "profile": profile,
- "release": event_opt.get("release", ""),
- "timestamp": event_opt["start_timestamp"],
- "version": "1",
- "device": {
- "architecture": platform.machine(),
- },
- "os": {
- "name": platform.system(),
- "version": platform.release(),
- },
- "runtime": {
- "name": platform.python_implementation(),
- "version": platform.python_version(),
- },
- "transactions": [
- {
- "id": event_opt["event_id"],
- "name": event_opt["transaction"],
- # we start the transaction before the profile and this is
- # the transaction start time relative to the profile, so we
- # hardcode it to 0 until we can start the profile before
- "relative_start_ns": "0",
- # use the duration of the profile instead of the transaction
- # because we end the transaction after the profile
- "relative_end_ns": str(self.stop_ns - self.start_ns),
- "trace_id": event_opt["contexts"]["trace"]["trace_id"],
- "active_thread_id": str(
- self._default_active_thread_id
- if self.active_thread_id is None
- else self.active_thread_id
- ),
- }
- ],
- }
-
- def valid(self) -> bool:
- client = sentry_sdk.get_client()
- if not client.is_active():
- return False
-
- if not has_profiling_enabled(client.options):
- return False
-
- if self.sampled is None or not self.sampled:
- if client.transport:
- client.transport.record_lost_event(
- "sample_rate", data_category="profile"
- )
- return False
-
- if self.unique_samples < PROFILE_MINIMUM_SAMPLES:
- if client.transport:
- client.transport.record_lost_event(
- "insufficient_data", data_category="profile"
- )
- logger.debug("[Profiling] Discarding profile because insufficient samples.")
- return False
-
- return True
-
- @property
- def hub(self) -> "Optional[sentry_sdk.Hub]":
- warnings.warn(
- "The `hub` attribute is deprecated. Please do not access it.",
- DeprecationWarning,
- stacklevel=2,
- )
- return self._hub
-
- @hub.setter
- def hub(self, value: "Optional[sentry_sdk.Hub]") -> None:
- warnings.warn(
- "The `hub` attribute is deprecated. Please do not set it.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._hub = value
-
-
-class Scheduler(ABC):
- mode: "ProfilerMode" = "unknown"
-
- def __init__(self, frequency: int) -> None:
- self.interval = 1.0 / frequency
-
- self.sampler = self.make_sampler()
-
- # cap the number of new profiles at any time so it does not grow infinitely
- self.new_profiles: "Deque[Profile]" = deque(maxlen=128)
- self.active_profiles: "Set[Profile]" = set()
-
- def __enter__(self) -> "Scheduler":
- self.setup()
- return self
-
- def __exit__(
- self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
- ) -> None:
- self.teardown()
-
- @abstractmethod
- def setup(self) -> None:
- pass
-
- @abstractmethod
- def teardown(self) -> None:
- pass
-
- def ensure_running(self) -> None:
- """
- Ensure the scheduler is running. By default, this method is a no-op.
- The method should be overridden by any implementation for which it is
- relevant.
- """
- return None
-
- def start_profiling(self, profile: "Profile") -> None:
- self.ensure_running()
- self.new_profiles.append(profile)
-
- def make_sampler(self) -> "Callable[..., None]":
- cwd = os.getcwd()
-
- cache = LRUCache(max_size=256)
-
- def _sample_stack(*args: "Any", **kwargs: "Any") -> None:
- """
- Take a sample of the stack on all the threads in the process.
- This should be called at a regular interval to collect samples.
- """
- # no profiles taking place, so we can stop early
- if not self.new_profiles and not self.active_profiles:
- # make sure to clear the cache if we're not profiling so we dont
- # keep a reference to the last stack of frames around
- return
-
- # This is the number of profiles we want to pop off.
- # It's possible another thread adds a new profile to
- # the list and we spend longer than we want inside
- # the loop below.
- #
- # Also make sure to set this value before extracting
- # frames so we do not write to any new profiles that
- # were started after this point.
- new_profiles = len(self.new_profiles)
-
- now = nanosecond_time()
-
- try:
- sample = [
- (str(tid), extract_stack(frame, cache, cwd))
- for tid, frame in sys._current_frames().items()
- ]
- except AttributeError:
- # For some reason, the frame we get doesn't have certain attributes.
- # When this happens, we abandon the current sample as it's bad.
- capture_internal_exception(sys.exc_info())
- return
-
- # Move the new profiles into the active_profiles set.
- #
- # We cannot directly add the to active_profiles set
- # in `start_profiling` because it is called from other
- # threads which can cause a RuntimeError when it the
- # set sizes changes during iteration without a lock.
- #
- # We also want to avoid using a lock here so threads
- # that are starting profiles are not blocked until it
- # can acquire the lock.
- for _ in range(new_profiles):
- self.active_profiles.add(self.new_profiles.popleft())
-
- inactive_profiles = []
-
- for profile in self.active_profiles:
- if profile.active:
- profile.write(now, sample)
- else:
- # If a profile is marked inactive, we buffer it
- # to `inactive_profiles` so it can be removed.
- # We cannot remove it here as it would result
- # in a RuntimeError.
- inactive_profiles.append(profile)
-
- for profile in inactive_profiles:
- self.active_profiles.remove(profile)
-
- return _sample_stack
-
-
-class ThreadScheduler(Scheduler):
- """
- This scheduler is based on running a daemon thread that will call
- the sampler at a regular interval.
- """
-
- mode: "ProfilerMode" = "thread"
- name = "sentry.profiler.ThreadScheduler"
-
- def __init__(self, frequency: int) -> None:
- super().__init__(frequency=frequency)
-
- # used to signal to the thread that it should stop
- self.running = False
- self.thread: "Optional[threading.Thread]" = None
- self.pid: "Optional[int]" = None
- self.lock = threading.Lock()
-
- def setup(self) -> None:
- pass
-
- def teardown(self) -> None:
- if self.running:
- self.running = False
- if self.thread is not None:
- self.thread.join()
-
- def ensure_running(self) -> None:
- """
- Check that the profiler has an active thread to run in, and start one if
- that's not the case.
-
- Note that this might fail (e.g. in Python 3.12 it's not possible to
- spawn new threads at interpreter shutdown). In that case self.running
- will be False after running this function.
- """
- pid = os.getpid()
-
- # is running on the right process
- if self.running and self.pid == pid:
- return
-
- with self.lock:
- # another thread may have tried to acquire the lock
- # at the same time so it may start another thread
- # make sure to check again before proceeding
- if self.running and self.pid == pid:
- return
-
- self.pid = pid
- self.running = True
-
- # make sure the thread is a daemon here otherwise this
- # can keep the application running after other threads
- # have exited
- self.thread = threading.Thread(name=self.name, target=self.run, daemon=True)
- try:
- self.thread.start()
- except RuntimeError:
- # Unfortunately at this point the interpreter is in a state that no
- # longer allows us to spawn a thread and we have to bail.
- self.running = False
- self.thread = None
- return
-
- def run(self) -> None:
- last = time.perf_counter()
-
- while self.running:
- self.sampler()
-
- # some time may have elapsed since the last time
- # we sampled, so we need to account for that and
- # not sleep for too long
- elapsed = time.perf_counter() - last
- if elapsed < self.interval:
- thread_sleep(self.interval - elapsed)
-
- # after sleeping, make sure to take the current
- # timestamp so we can use it next iteration
- last = time.perf_counter()
-
-
-class GeventScheduler(Scheduler):
- """
- This scheduler is based on the thread scheduler but adapted to work with
- gevent. When using gevent, it may monkey patch the threading modules
- (`threading` and `_thread`). This results in the use of greenlets instead
- of native threads.
-
- This is an issue because the sampler CANNOT run in a greenlet because
- 1. Other greenlets doing sync work will prevent the sampler from running
- 2. The greenlet runs in the same thread as other greenlets so when taking
- a sample, other greenlets will have been evicted from the thread. This
- results in a sample containing only the sampler's code.
- """
-
- mode: "ProfilerMode" = "gevent"
- name = "sentry.profiler.GeventScheduler"
-
- def __init__(self, frequency: int) -> None:
- if ThreadPool is None:
- raise ValueError("Profiler mode: {} is not available".format(self.mode))
-
- super().__init__(frequency=frequency)
-
- # used to signal to the thread that it should stop
- self.running = False
- self.thread: "Optional[_ThreadPool]" = None
- self.pid: "Optional[int]" = None
-
- # This intentionally uses the gevent patched threading.Lock.
- # The lock will be required when first trying to start profiles
- # as we need to spawn the profiler thread from the greenlets.
- self.lock = threading.Lock()
-
- def setup(self) -> None:
- pass
-
- def teardown(self) -> None:
- if self.running:
- self.running = False
- if self.thread is not None:
- self.thread.join()
-
- def ensure_running(self) -> None:
- pid = os.getpid()
-
- # is running on the right process
- if self.running and self.pid == pid:
- return
-
- with self.lock:
- # another thread may have tried to acquire the lock
- # at the same time so it may start another thread
- # make sure to check again before proceeding
- if self.running and self.pid == pid:
- return
-
- self.pid = pid
- self.running = True
-
- self.thread = ThreadPool(1) # type: ignore[misc]
- try:
- self.thread.spawn(self.run)
- except RuntimeError:
- # Unfortunately at this point the interpreter is in a state that no
- # longer allows us to spawn a thread and we have to bail.
- self.running = False
- self.thread = None
- return
-
- def run(self) -> None:
- last = time.perf_counter()
-
- while self.running:
- self.sampler()
-
- # some time may have elapsed since the last time
- # we sampled, so we need to account for that and
- # not sleep for too long
- elapsed = time.perf_counter() - last
- if elapsed < self.interval:
- thread_sleep(self.interval - elapsed)
-
- # after sleeping, make sure to take the current
- # timestamp so we can use it next iteration
- last = time.perf_counter()
diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py
index 09e8876d6d..4ac12e9310 100644
--- a/sentry_sdk/scope.py
+++ b/sentry_sdk/scope.py
@@ -17,7 +17,6 @@
from sentry_sdk.consts import (
DEFAULT_MAX_BREADCRUMBS,
FALSE_VALUES,
- INSTRUMENTER,
SPANDATA,
)
from sentry_sdk.feature_flags import DEFAULT_FLAG_CAPACITY, FlagBuffer
@@ -26,7 +25,6 @@
try_autostart_continuous_profiler,
try_profile_lifecycle_trace_start,
)
-from sentry_sdk.profiler.transaction_profiler import Profile
from sentry_sdk.session import Session
from sentry_sdk.traces import (
_DEFAULT_PARENT_SPAN,
@@ -139,7 +137,7 @@ class ScopeType(Enum):
class _ScopeManager:
- def __init__(self, hub: "Optional[Any]" = None) -> None:
+ def __init__(self) -> None:
self._old_scopes: "List[Scope]" = []
def __enter__(self) -> "Scope":
@@ -235,7 +233,6 @@ class Scope:
"_session",
"_attachments",
"_force_auto_session_tracking",
- "_profile",
"_propagation_context",
"client",
"_type",
@@ -302,8 +299,6 @@ def __copy__(self) -> "Scope":
rv._force_auto_session_tracking = self._force_auto_session_tracking
rv._attachments = self._attachments.copy()
- rv._profile = self._profile
-
rv._last_event_id = self._last_event_id
rv._flags = deepcopy(self._flags)
@@ -688,13 +683,6 @@ def iter_trace_propagation_headers(
If no span is given, the trace data is taken from the scope.
"""
client = self.get_client()
- if not client.options.get("propagate_traces"):
- warnings.warn(
- "The `propagate_traces` parameter is deprecated. Please use `trace_propagation_targets` instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- return
span = kwargs.pop("span", None)
if not span:
@@ -758,8 +746,6 @@ def clear(self) -> None:
self._session: "Optional[Session]" = None
self._force_auto_session_tracking: "Optional[bool]" = None
- self._profile: "Optional[Profile]" = None
-
self._propagation_context = None
# self._last_event_id is only applicable to isolation scopes
@@ -932,14 +918,6 @@ def streamed_span(self, span: "Optional[StreamedSpan]") -> None:
span._attributes["sentry.segment.name.source"]
)
- @property
- def profile(self) -> "Optional[Profile]":
- return self._profile
-
- @profile.setter
- def profile(self, profile: "Optional[Profile]") -> None:
- self._profile = profile
-
def set_tag(self, key: str, value: "Any") -> None:
"""
Sets a tag for a key to a specific value.
@@ -1107,7 +1085,6 @@ def add_breadcrumb(
def start_transaction(
self,
transaction: "Optional[Transaction]" = None,
- instrumenter: str = INSTRUMENTER.SENTRY,
custom_sampling_context: "Optional[SamplingContext]" = None,
**kwargs: "Unpack[TransactionKwargs]",
) -> "Union[Transaction, NoOpSpan]":
@@ -1135,8 +1112,6 @@ def start_transaction(
:param transaction: The transaction to start. If omitted, we create and
start a new transaction.
- :param instrumenter: This parameter is meant for internal use only. It
- will be removed in the next major version.
:param custom_sampling_context: The transaction's custom sampling context.
:param kwargs: Optional keyword arguments to be passed to the Transaction
constructor. See :py:class:`sentry_sdk.tracing.Transaction` for
@@ -1153,11 +1128,6 @@ def start_transaction(
kwargs.setdefault("scope", self)
- configuration_instrumenter = client.options["instrumenter"]
-
- if instrumenter != configuration_instrumenter:
- return NoOpSpan()
-
try_autostart_continuous_profiler()
custom_sampling_context = custom_sampling_context or {}
@@ -1193,13 +1163,6 @@ def start_transaction(
)
if transaction.sampled:
- profile = Profile(
- transaction.sampled, transaction._start_timestamp_monotonic_ns
- )
- profile._set_initial_sampling_decision(sampling_context=sampling_context)
-
- transaction._profile = profile
-
transaction._continuous_profile = try_profile_lifecycle_trace_start()
# Typically, the profiler is set when the transaction is created. But when
@@ -1215,9 +1178,7 @@ def start_transaction(
return transaction
- def start_span(
- self, instrumenter: str = INSTRUMENTER.SENTRY, **kwargs: "Any"
- ) -> "Span":
+ def start_span(self, **kwargs: "Any") -> "Span":
"""
Start a span whose parent is the currently active span or transaction, if any.
@@ -1232,10 +1193,6 @@ def start_span(
one is not already in progress.
For supported `**kwargs` see :py:class:`sentry_sdk.tracing.Span`.
-
- The instrumenter parameter is deprecated for user code, and it will
- be removed in the next major version. Going forward, it should only
- be used by the SDK itself.
"""
client = sentry_sdk.get_client()
if has_span_streaming_enabled(client.options):
@@ -1258,11 +1215,6 @@ def start_span(
client = self.get_client()
- configuration_instrumenter = client.options["instrumenter"]
-
- if instrumenter != configuration_instrumenter:
- return NoOpSpan()
-
# get current span or transaction
span = self.span or self.get_isolation_scope().span
if isinstance(span, StreamedSpan):
@@ -1964,8 +1916,6 @@ def update_from_scope(self, scope: "Scope") -> None:
self._span = scope._span
if scope._attachments:
self._attachments.extend(scope._attachments)
- if scope._profile:
- self._profile = scope._profile
if scope._propagation_context:
self._propagation_context = scope._propagation_context
if scope._session:
diff --git a/sentry_sdk/sessions.py b/sentry_sdk/sessions.py
index aabf874fcd..ea4a9a8757 100644
--- a/sentry_sdk/sessions.py
+++ b/sentry_sdk/sessions.py
@@ -1,5 +1,4 @@
import os
-import warnings
from contextlib import contextmanager
from threading import Event, Lock, Thread
from typing import TYPE_CHECKING
@@ -10,76 +9,7 @@
from sentry_sdk.utils import format_timestamp
if TYPE_CHECKING:
- from typing import Any, Callable, Dict, Generator, List, Optional, Union
-
-
-def is_auto_session_tracking_enabled(
- hub: "Optional[sentry_sdk.Hub]" = None,
-) -> "Union[Any, bool, None]":
- """DEPRECATED: Utility function to find out if session tracking is enabled."""
-
- # Internal callers should use private _is_auto_session_tracking_enabled, instead.
- warnings.warn(
- "This function is deprecated and will be removed in the next major release. "
- "There is no public API replacement.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if hub is None:
- hub = sentry_sdk.Hub.current
-
- should_track = hub.scope._force_auto_session_tracking
-
- if should_track is None:
- client_options = hub.client.options if hub.client else {}
- should_track = client_options.get("auto_session_tracking", False)
-
- return should_track
-
-
-@contextmanager
-def auto_session_tracking(
- hub: "Optional[sentry_sdk.Hub]" = None, session_mode: str = "application"
-) -> "Generator[None, None, None]":
- """DEPRECATED: Use track_session instead
- Starts and stops a session automatically around a block.
- """
- warnings.warn(
- "This function is deprecated and will be removed in the next major release. "
- "Use track_session instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- if hub is None:
- hub = sentry_sdk.Hub.current
- with warnings.catch_warnings():
- warnings.simplefilter("ignore", DeprecationWarning)
- should_track = is_auto_session_tracking_enabled(hub)
- if should_track:
- hub.start_session(session_mode=session_mode)
- try:
- yield
- finally:
- if should_track:
- hub.end_session()
-
-
-def is_auto_session_tracking_enabled_scope(scope: "sentry_sdk.Scope") -> bool:
- """
- DEPRECATED: Utility function to find out if session tracking is enabled.
- """
-
- warnings.warn(
- "This function is deprecated and will be removed in the next major release. "
- "There is no public API replacement.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- # Internal callers should use private _is_auto_session_tracking_enabled, instead.
- return _is_auto_session_tracking_enabled(scope)
+ from typing import Any, Callable, Dict, Generator, List, Optional
def _is_auto_session_tracking_enabled(scope: "sentry_sdk.Scope") -> bool:
@@ -95,24 +25,6 @@ def _is_auto_session_tracking_enabled(scope: "sentry_sdk.Scope") -> bool:
return should_track
-@contextmanager
-def auto_session_tracking_scope(
- scope: "sentry_sdk.Scope", session_mode: str = "application"
-) -> "Generator[None, None, None]":
- """DEPRECATED: This function is a deprecated alias for track_session.
- Starts and stops a session automatically around a block.
- """
-
- warnings.warn(
- "This function is a deprecated alias for track_session and will be removed in the next major release.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- with track_session(scope, session_mode=session_mode):
- yield
-
-
@contextmanager
def track_session(
scope: "sentry_sdk.Scope", session_mode: str = "application"
diff --git a/sentry_sdk/spotlight.py b/sentry_sdk/spotlight.py
index 2dcc86bc47..41877346bc 100644
--- a/sentry_sdk/spotlight.py
+++ b/sentry_sdk/spotlight.py
@@ -3,20 +3,15 @@
import os
import sys
import time
-import urllib.error
-import urllib.parse
-import urllib.request
-from itertools import chain, product
from typing import TYPE_CHECKING
import urllib3
if TYPE_CHECKING:
- from typing import Any, Callable, Dict, Optional, Self
+ from typing import Any, Dict, Optional
from sentry_sdk.envelope import Envelope
from sentry_sdk.utils import (
- capture_internal_exceptions,
env_to_bool,
)
from sentry_sdk.utils import (
@@ -27,7 +22,6 @@
DEFAULT_SPOTLIGHT_URL = "http://localhost:8969/stream"
-DJANGO_SPOTLIGHT_MIDDLEWARE_PATH = "sentry_sdk.spotlight.SpotlightMiddleware"
class SpotlightClient:
@@ -90,145 +84,6 @@ def capture_envelope(self, envelope: "Envelope") -> None:
)
-try:
- from django.conf import settings
- from django.http import HttpRequest, HttpResponse, HttpResponseServerError
- from django.utils.deprecation import MiddlewareMixin
-
- SPOTLIGHT_JS_ENTRY_PATH = "/assets/main.js"
- SPOTLIGHT_JS_SNIPPET_PATTERN = (
- "\n"
- '\n'
- )
- SPOTLIGHT_ERROR_PAGE_SNIPPET = (
- '\n'
- '\n'
- )
- CHARSET_PREFIX = "charset="
- BODY_TAG_NAME = "body"
- BODY_CLOSE_TAG_POSSIBILITIES = tuple(
- "{}>".format("".join(chars))
- for chars in product(*zip(BODY_TAG_NAME.upper(), BODY_TAG_NAME.lower()))
- )
-
- class SpotlightMiddleware(MiddlewareMixin): # type: ignore[misc]
- _spotlight_script: "Optional[str]" = None
- _spotlight_url: "Optional[str]" = None
-
- def __init__(self: "Self", get_response: "Callable[..., HttpResponse]") -> None:
- super().__init__(get_response)
-
- import sentry_sdk.api
-
- self.sentry_sdk = sentry_sdk.api
-
- spotlight_client = self.sentry_sdk.get_client().spotlight
- if spotlight_client is None:
- sentry_logger.warning(
- "Cannot find Spotlight client from SpotlightMiddleware, disabling the middleware."
- )
- return None
- # Spotlight URL has a trailing `/stream` part at the end so split it off
- self._spotlight_url = urllib.parse.urljoin(spotlight_client.url, "../")
-
- @property
- def spotlight_script(self: "Self") -> "Optional[str]":
- if self._spotlight_url is not None and self._spotlight_script is None:
- try:
- spotlight_js_url = urllib.parse.urljoin(
- self._spotlight_url, SPOTLIGHT_JS_ENTRY_PATH
- )
- req = urllib.request.Request(
- spotlight_js_url,
- method="HEAD",
- )
- urllib.request.urlopen(req)
- self._spotlight_script = SPOTLIGHT_JS_SNIPPET_PATTERN.format(
- spotlight_url=self._spotlight_url,
- spotlight_js_url=spotlight_js_url,
- )
- except urllib.error.URLError as err:
- sentry_logger.debug(
- "Cannot get Spotlight JS to inject at %s. SpotlightMiddleware will not be very useful.",
- spotlight_js_url,
- exc_info=err,
- )
-
- return self._spotlight_script
-
- def process_response(
- self: "Self", _request: "HttpRequest", response: "HttpResponse"
- ) -> "Optional[HttpResponse]":
- content_type_header = tuple(
- p.strip()
- for p in response.headers.get("Content-Type", "").lower().split(";")
- )
- content_type = content_type_header[0]
- if len(content_type_header) > 1 and content_type_header[1].startswith(
- CHARSET_PREFIX
- ):
- encoding = content_type_header[1][len(CHARSET_PREFIX) :]
- else:
- encoding = "utf-8"
-
- if (
- self.spotlight_script is not None
- and not response.streaming
- and content_type == "text/html"
- ):
- content_length = len(response.content)
- injection = self.spotlight_script.encode(encoding)
- injection_site = next(
- (
- idx
- for idx in (
- response.content.rfind(body_variant.encode(encoding))
- for body_variant in BODY_CLOSE_TAG_POSSIBILITIES
- )
- if idx > -1
- ),
- content_length,
- )
-
- # This approach works even when we don't have a `` tag
- response.content = (
- response.content[:injection_site]
- + injection
- + response.content[injection_site:]
- )
-
- if response.has_header("Content-Length"):
- response.headers["Content-Length"] = content_length + len(injection)
-
- return response
-
- def process_exception(
- self: "Self", _request: "HttpRequest", exception: Exception
- ) -> "Optional[HttpResponseServerError]":
- if not settings.DEBUG or not self._spotlight_url:
- return None
-
- try:
- spotlight = (
- urllib.request.urlopen(self._spotlight_url).read().decode("utf-8")
- )
- except urllib.error.URLError:
- return None
- else:
- event_id = self.sentry_sdk.capture_exception(exception)
- return HttpResponseServerError(
- spotlight.replace(
- "",
- SPOTLIGHT_ERROR_PAGE_SNIPPET.format(
- spotlight_url=self._spotlight_url, event_id=event_id
- ),
- )
- )
-
-except ImportError:
- settings = None
-
-
def _resolve_spotlight_url(
spotlight_config: "Any", sentry_logger: "Any"
) -> "Optional[str]":
@@ -307,20 +162,6 @@ def setup_spotlight(options: "Dict[str, Any]") -> "Optional[SpotlightClient]":
# Update options with resolved URL for consistency
options["spotlight"] = url
- with capture_internal_exceptions():
- if (
- settings is not None
- and settings.DEBUG
- and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_ON_ERROR", "1"))
- and env_to_bool(os.environ.get("SENTRY_SPOTLIGHT_MIDDLEWARE", "1"))
- ):
- middleware = settings.MIDDLEWARE
- if DJANGO_SPOTLIGHT_MIDDLEWARE_PATH not in middleware:
- settings.MIDDLEWARE = type(middleware)(
- chain(middleware, (DJANGO_SPOTLIGHT_MIDDLEWARE_PATH,))
- )
- logger.info("Enabled Spotlight integration for Django")
-
client = SpotlightClient(url)
logger.info("Enabled Spotlight using sidecar at %s", url)
diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py
index 5c088b936b..eef2e35929 100644
--- a/sentry_sdk/tracing.py
+++ b/sentry_sdk/tracing.py
@@ -5,7 +5,7 @@
from typing import TYPE_CHECKING, cast
import sentry_sdk
-from sentry_sdk.consts import INSTRUMENTER, SPANDATA, SPANSTATUS, SPANTEMPLATE
+from sentry_sdk.consts import SPANDATA, SPANSTATUS, SPANTEMPLATE
from sentry_sdk.profiler.continuous_profiler import get_profiler_id
from sentry_sdk.utils import (
capture_internal_exceptions,
@@ -38,12 +38,9 @@
from sentry_sdk._types import (
Event,
- MeasurementUnit,
- MeasurementValue,
SamplingContext,
)
from sentry_sdk.profiler.continuous_profiler import ContinuousProfile
- from sentry_sdk.profiler.transaction_profiler import Profile
class SpanKwargs(TypedDict, total=False):
trace_id: str
@@ -76,9 +73,6 @@ class SpanKwargs(TypedDict, total=False):
description: str
"""A description of what operation is being performed within the span. This argument is DEPRECATED. Please use the `name` parameter, instead."""
- hub: "Optional[sentry_sdk.Hub]"
- """The hub to use for this span. This argument is DEPRECATED. Please use the `scope` parameter, instead."""
-
status: str
"""The span's status. Possible values are listed at https://develop.sentry.dev/sdk/event-payloads/span/"""
@@ -240,7 +234,6 @@ class Span:
.. deprecated:: 2.15.0
Please use the `name` parameter, instead.
:param name: A string describing what operation is being performed within the span.
- :param hub: The hub to use for this span.
.. deprecated:: 2.0.0
Please use the `scope` parameter, instead.
@@ -260,7 +253,6 @@ class Span:
"sampled",
"op",
"description",
- "_measurements",
"start_timestamp",
"_start_timestamp_monotonic_ns",
"status",
@@ -268,7 +260,6 @@ class Span:
"_tags",
"_data",
"_span_recorder",
- "hub",
"_context_manager_state",
"_containing_transaction",
"scope",
@@ -287,7 +278,6 @@ def __init__(
sampled: "Optional[bool]" = None,
op: "Optional[str]" = None,
description: "Optional[str]" = None,
- hub: "Optional[sentry_sdk.Hub]" = None, # deprecated
status: "Optional[str]" = None,
containing_transaction: "Optional[Transaction]" = None,
start_timestamp: "Optional[Union[datetime, float]]" = None,
@@ -303,25 +293,14 @@ def __init__(
self.op = op
self.description = name or description
self.status = status
- self.hub = hub # backwards compatibility
self.scope = scope
self.origin = origin
- self._measurements: "Dict[str, MeasurementValue]" = {}
self._tags: "MutableMapping[str, str]" = {}
self._data: "Dict[str, Any]" = {}
self._containing_transaction = containing_transaction
self._flags: "Dict[str, bool]" = {}
self._flags_capacity = 10
- if hub is not None:
- warnings.warn(
- "The `hub` parameter is deprecated. Please use `scope` instead.",
- DeprecationWarning,
- stacklevel=2,
- )
-
- self.scope = self.scope or hub.scope
-
if start_timestamp is None:
start_timestamp = datetime.now(timezone.utc)
elif isinstance(start_timestamp, float):
@@ -429,19 +408,13 @@ def containing_transaction(self) -> "Optional[Transaction]":
# referencing themselves)
return self._containing_transaction
- def start_child(
- self, instrumenter: str = INSTRUMENTER.SENTRY, **kwargs: "Any"
- ) -> "Span":
+ def start_child(self, **kwargs: "Any") -> "Span":
"""
Start a sub-span from the current span or transaction.
Takes the same arguments as the initializer of :py:class:`Span`. The
trace id, sampling decision, transaction pointer, and span recorder are
inherited from the current span/transaction.
-
- The instrumenter parameter is deprecated for user code, and it will
- be removed in the next major version. Going forward, it should only
- be used by the SDK itself.
"""
if kwargs.get("description") is not None:
warnings.warn(
@@ -450,11 +423,6 @@ def start_child(
stacklevel=2,
)
- configuration_instrumenter = sentry_sdk.get_client().options["instrumenter"]
-
- if instrumenter != configuration_instrumenter:
- return NoOpSpan()
-
kwargs.setdefault("sampled", self.sampled)
child = Span(
@@ -614,21 +582,6 @@ def set_flag(self, flag: str, result: bool) -> None:
def set_status(self, value: str) -> None:
self.status = value
- def set_measurement(
- self, name: str, value: float, unit: "MeasurementUnit" = ""
- ) -> None:
- """
- .. deprecated:: 2.28.0
- This function is deprecated and will be removed in the next major release.
- """
-
- warnings.warn(
- "`set_measurement()` is deprecated and will be removed in the next major version. Please use `set_data()` instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._measurements[name] = {"value": value, "unit": unit}
-
def set_thread(
self, thread_id: "Optional[int]", thread_name: "Optional[str]"
) -> None:
@@ -724,9 +677,6 @@ def to_json(self) -> "Dict[str, Any]":
# TODO-neel remove redundant tag in major
self._tags["status"] = self.status
- if len(self._measurements) > 0:
- rv["measurements"] = self._measurements
-
tags = self._tags
if tags:
rv["tags"] = tags
@@ -815,9 +765,7 @@ class Transaction(Span):
"parent_sampled",
# used to create baggage value for head SDKs in dynamic sampling
"sample_rate",
- "_measurements",
"_contexts",
- "_profile",
"_continuous_profile",
"_baggage",
"_sample_rand",
@@ -837,9 +785,7 @@ def __init__( # type: ignore[misc]
self.source = source
self.sample_rate: "Optional[float]" = None
self.parent_sampled = parent_sampled
- self._measurements: "Dict[str, MeasurementValue]" = {}
self._contexts: "Dict[str, Any]" = {}
- self._profile: "Optional[Profile]" = None
self._continuous_profile: "Optional[ContinuousProfile]" = None
self._baggage = baggage
@@ -888,17 +834,11 @@ def __enter__(self) -> "Transaction":
super().__enter__()
- if self._profile is not None:
- self._profile.__enter__()
-
return self
def __exit__(
self, ty: "Optional[Any]", value: "Optional[Any]", tb: "Optional[Any]"
) -> None:
- if self._profile is not None:
- self._profile.__exit__(ty, value, tb)
-
if self._continuous_profile is not None:
self._continuous_profile.stop()
@@ -915,38 +855,6 @@ def containing_transaction(self) -> "Transaction":
# reference.
return self
- def _get_scope_from_finish_args(
- self,
- scope_arg: "Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]]",
- hub_arg: "Optional[Union[sentry_sdk.Scope, sentry_sdk.Hub]]",
- ) -> "Optional[sentry_sdk.Scope]":
- """
- Logic to get the scope from the arguments passed to finish. This
- function exists for backwards compatibility with the old finish.
-
- TODO: Remove this function in the next major version.
- """
- scope_or_hub = scope_arg
- if hub_arg is not None:
- warnings.warn(
- "The `hub` parameter is deprecated. Please use the `scope` parameter, instead.",
- DeprecationWarning,
- stacklevel=3,
- )
-
- scope_or_hub = hub_arg
-
- if isinstance(scope_or_hub, sentry_sdk.Hub):
- warnings.warn(
- "Passing a Hub to finish is deprecated. Please pass a Scope, instead.",
- DeprecationWarning,
- stacklevel=3,
- )
-
- return scope_or_hub.scope
-
- return scope_or_hub
-
def _get_log_representation(self) -> str:
return "{op}transaction <{name}>".format(
op=("<" + self.op + "> " if self.op else ""), name=self.name
@@ -956,8 +864,6 @@ def finish(
self,
scope: "Optional[sentry_sdk.Scope]" = None,
end_timestamp: "Optional[Union[float, datetime]]" = None,
- *,
- hub: "Optional[sentry_sdk.Hub]" = None,
) -> "Optional[str]":
"""Finishes the transaction and sends it to Sentry.
All finished spans in the transaction will also be sent to Sentry.
@@ -966,9 +872,6 @@ def finish(
If not provided, the current Scope will be used.
:param end_timestamp: Optional timestamp that should
be used as timestamp instead of the current time.
- :param hub: The hub to use for this transaction.
- This argument is DEPRECATED. Please use the `scope`
- parameter, instead.
:return: The event ID if the transaction was sent to Sentry,
otherwise None.
@@ -977,10 +880,6 @@ def finish(
# This transaction is already finished, ignore.
return None
- # For backwards compatibility, we must handle the case where `scope`
- # or `hub` could both either be a `Scope` or a `Hub`.
- scope = self._get_scope_from_finish_args(scope, hub)
-
scope = scope or self.scope or sentry_sdk.get_current_scope()
client = sentry_sdk.get_client()
@@ -1105,29 +1004,8 @@ def finish(
if has_gen_ai_span:
event["_has_gen_ai_span"] = True
- if self._profile is not None and self._profile.valid():
- event["profile"] = self._profile
- self._profile = None
-
- event["measurements"] = self._measurements
-
return scope.capture_event(event)
- def set_measurement(
- self, name: str, value: float, unit: "MeasurementUnit" = ""
- ) -> None:
- """
- .. deprecated:: 2.28.0
- This function is deprecated and will be removed in the next major release.
- """
-
- warnings.warn(
- "`set_measurement()` is deprecated and will be removed in the next major version. Please use `set_data()` instead.",
- DeprecationWarning,
- stacklevel=2,
- )
- self._measurements[name] = {"value": value, "unit": unit}
-
def set_context(self, key: str, value: "dict[str, Any]") -> None:
"""Sets a context. Transactions can have multiple contexts
and they should follow the format described in the "Contexts Interface"
@@ -1297,9 +1175,7 @@ def __repr__(self) -> str:
def containing_transaction(self) -> "Optional[Transaction]":
return None
- def start_child(
- self, instrumenter: str = INSTRUMENTER.SENTRY, **kwargs: "Any"
- ) -> "NoOpSpan":
+ def start_child(self, **kwargs: "Any") -> "NoOpSpan":
return NoOpSpan()
def to_traceparent(self) -> str:
@@ -1345,17 +1221,7 @@ def finish(
self,
scope: "Optional[sentry_sdk.Scope]" = None,
end_timestamp: "Optional[Union[float, datetime]]" = None,
- *,
- hub: "Optional[sentry_sdk.Hub]" = None,
) -> "Optional[str]":
- """
- The `hub` parameter is deprecated. Please use the `scope` parameter, instead.
- """
- pass
-
- def set_measurement(
- self, name: str, value: float, unit: "MeasurementUnit" = ""
- ) -> None:
pass
def set_context(self, key: str, value: "dict[str, Any]") -> None:
diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py
index d75d94efd6..105edb0ec7 100644
--- a/sentry_sdk/tracing_utils.py
+++ b/sentry_sdk/tracing_utils.py
@@ -95,17 +95,14 @@ def __iter__(self) -> "Generator[str, None, None]":
def has_tracing_enabled(options: "Optional[Dict[str, Any]]") -> bool:
"""
Returns True if either traces_sample_rate or traces_sampler is
- defined and enable_tracing is set and not false.
+ defined.
"""
if options is None:
return False
return bool(
- options.get("enable_tracing") is not False
- and (
- options.get("traces_sample_rate") is not None
- or options.get("traces_sampler") is not None
- )
+ options.get("traces_sample_rate") is not None
+ or options.get("traces_sampler") is not None
)
diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py
index 2b71fee429..d98b8597fa 100644
--- a/sentry_sdk/transport.py
+++ b/sentry_sdk/transport.py
@@ -42,7 +42,6 @@
import certifi
import urllib3
-import sentry_sdk
from sentry_sdk.consts import EndpointType
from sentry_sdk.envelope import Envelope, Item, PayloadRef
from sentry_sdk.utils import (
@@ -234,9 +233,6 @@ def __init__(self: "Self", options: "Dict[str, Any]") -> None:
self._pool = self._make_pool()
- # Backwards compatibility for deprecated `self.hub_class` attribute
- self._hub_cls = sentry_sdk.Hub
-
experiments = options.get("_experiments", {})
compression_level = experiments.get(
"transport_compression_level",
@@ -715,27 +711,6 @@ def flush(
self._worker.submit(lambda: self._flush_client_reports(force=True))
self._worker.flush(timeout, callback)
- @staticmethod
- def _warn_hub_cls() -> None:
- """Convenience method to warn users about the deprecation of the `hub_cls` attribute."""
- warnings.warn(
- "The `hub_cls` attribute is deprecated and will be removed in a future release.",
- DeprecationWarning,
- stacklevel=3,
- )
-
- @property
- def hub_cls(self: "Self") -> "type[sentry_sdk.Hub]":
- """DEPRECATED: This attribute is deprecated and will be removed in a future release."""
- HttpTransport._warn_hub_cls()
- return self._hub_cls
-
- @hub_cls.setter
- def hub_cls(self: "Self", value: "type[sentry_sdk.Hub]") -> None:
- """DEPRECATED: This attribute is deprecated and will be removed in a future release."""
- HttpTransport._warn_hub_cls()
- self._hub_cls = value
-
class HttpTransport(BaseHttpTransport):
if TYPE_CHECKING:
diff --git a/setup.py b/setup.py
index 35d95ff636..79a4c56431 100644
--- a/setup.py
+++ b/setup.py
@@ -44,57 +44,16 @@ def get_file_text(file_name):
"certifi",
],
extras_require={
- "aiohttp": ["aiohttp>=3.5"],
- "anthropic": ["anthropic>=0.16"],
- "arq": ["arq>=0.23"],
- "asyncpg": ["asyncpg>=0.23"],
- "beam": ["apache-beam>=2.12"],
- "bottle": ["bottle>=0.12.13"],
- "celery": ["celery>=3"],
- "celery-redbeat": ["celery-redbeat>=2"],
- "chalice": ["chalice>=1.16.0"],
- "clickhouse-driver": ["clickhouse-driver>=0.2.0"],
- "django": ["django>=1.8"],
- "falcon": ["falcon>=1.4"],
- "fastapi": ["fastapi>=0.79.0"],
- "flask": ["flask>=0.11", "blinker>=1.1", "markupsafe"],
+ # Only add an extra if our integration needs something additional to
+ # work with the framework (a dependency or an extra). For instance,
+ # our Flask integration has a hard dependency on blinker.
+ "asyncio": ["httpcore[asyncio]==1.*"],
+ "flask": ["flask", "blinker>=1.1", "markupsafe"],
"grpcio": ["grpcio>=1.21.1", "protobuf>=3.8.0"],
"http2": ["httpcore[http2]==1.*"],
- "asyncio": ["httpcore[asyncio]==1.*"],
- "httpx": ["httpx>=0.16.0"],
- "huey": ["huey>=2"],
- "huggingface_hub": ["huggingface_hub>=0.22"],
- "langchain": ["langchain>=0.0.210"],
- "langgraph": ["langgraph>=0.6.6"],
- "launchdarkly": ["launchdarkly-server-sdk>=9.8.0"],
- "litellm": ["litellm>=1.77.5,!=1.82.7,!=1.82.8"],
- "litestar": ["litestar>=2.0.0"],
- "loguru": ["loguru>=0.5"],
- "mcp": ["mcp>=1.15.0"],
- "openai": ["openai>=1.0.0", "tiktoken>=0.3.0"],
- "openfeature": ["openfeature-sdk>=0.7.1"],
- "opentelemetry": ["opentelemetry-distro>=0.35b0"],
- "opentelemetry-experimental": ["opentelemetry-distro"],
"opentelemetry-otlp": ["opentelemetry-distro[otlp]>=0.35b0"],
"pure-eval": ["pure_eval", "executing", "asttokens"],
- "pydantic_ai": ["pydantic-ai>=1.0.0"],
- "pymongo": ["pymongo>=3.1"],
- "pyspark": ["pyspark>=2.4.4"],
- "quart": ["quart>=0.16.1", "blinker>=1.1"],
- "rq": ["rq>=0.6"],
- "sanic": ["sanic>=0.8"],
- "sqlalchemy": ["sqlalchemy>=1.2"],
- "starlette": ["starlette>=0.19.1"],
- "starlite": ["starlite>=1.48"],
- "statsig": ["statsig>=0.55.3"],
- "tornado": ["tornado>=6"],
- "unleash": ["UnleashClient>=6.0.1"],
- "google-genai": ["google-genai>=1.29.0"],
- },
- entry_points={
- "opentelemetry_propagator": [
- "sentry=sentry_sdk.integrations.opentelemetry:SentryPropagator"
- ]
+ "quart": ["quart", "blinker>=1.1"],
},
classifiers=[
"Development Status :: 5 - Production/Stable",
diff --git a/tests/conftest.py b/tests/conftest.py
index 6b406d6a06..04debe9b8c 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -61,7 +61,6 @@
_installed_integrations,
_processed_integrations,
)
-from sentry_sdk.profiler import teardown_profiler
from sentry_sdk.profiler.continuous_profiler import teardown_continuous_profiler
from sentry_sdk.transport import Transport
from sentry_sdk.utils import package_version, reraise
@@ -272,12 +271,7 @@ def reset_integrations():
but this also means some other stuff will be monkeypatched twice.
"""
global _DEFAULT_INTEGRATIONS, _processed_integrations
- try:
- _DEFAULT_INTEGRATIONS.remove(
- "sentry_sdk.integrations.opentelemetry.integration.OpenTelemetryIntegration"
- )
- except ValueError:
- pass
+
_processed_integrations.clear()
_installed_integrations.clear()
@@ -776,13 +770,11 @@ def __ne__(self, test_obj):
@pytest.fixture
def teardown_profiling():
# Make sure that a previous test didn't leave the profiler running
- teardown_profiler()
teardown_continuous_profiler()
yield
- # Make sure that to shut down the profiler after the test
- teardown_profiler()
+ # Make sure to shut down the profiler after the test
teardown_continuous_profiler()
diff --git a/tests/integrations/celery/test_update_celery_task_headers.py b/tests/integrations/celery/test_update_celery_task_headers.py
index 18f2f96bbf..4418653885 100644
--- a/tests/integrations/celery/test_update_celery_task_headers.py
+++ b/tests/integrations/celery/test_update_celery_task_headers.py
@@ -189,39 +189,3 @@ def test_celery_trace_propagation_traces_sample_rate(
else:
assert "sentry-monitor-start-timestamp-s" not in outgoing_headers
assert "sentry-monitor-start-timestamp-s" not in outgoing_headers["headers"]
-
-
-@pytest.mark.parametrize(
- "enable_tracing,monitor_beat_tasks",
- list(itertools.product([None, True, False], [True, False])),
-)
-def test_celery_trace_propagation_enable_tracing(
- sentry_init, enable_tracing, monitor_beat_tasks
-):
- """
- The celery integration does not check the traces_sample_rate.
- By default traces_sample_rate is None which means "do not propagate traces".
- But the celery integration does not check this value.
- The Celery integration has its own mechanism to propagate traces:
- https://docs.sentry.io/platforms/python/integrations/celery/#distributed-traces
- """
- sentry_init(enable_tracing=enable_tracing)
-
- headers = {}
- span = None
-
- scope = sentry_sdk.get_isolation_scope()
-
- outgoing_headers = _update_celery_task_headers(headers, span, monitor_beat_tasks)
-
- assert outgoing_headers["sentry-trace"] == scope.get_traceparent()
- assert outgoing_headers["headers"]["sentry-trace"] == scope.get_traceparent()
- assert outgoing_headers["baggage"] == scope.get_baggage().serialize()
- assert outgoing_headers["headers"]["baggage"] == scope.get_baggage().serialize()
-
- if monitor_beat_tasks:
- assert "sentry-monitor-start-timestamp-s" in outgoing_headers
- assert "sentry-monitor-start-timestamp-s" in outgoing_headers["headers"]
- else:
- assert "sentry-monitor-start-timestamp-s" not in outgoing_headers
- assert "sentry-monitor-start-timestamp-s" not in outgoing_headers["headers"]
diff --git a/tests/integrations/conftest.py b/tests/integrations/conftest.py
index 70a4b498d7..d97ba3265f 100644
--- a/tests/integrations/conftest.py
+++ b/tests/integrations/conftest.py
@@ -7,28 +7,16 @@
def capture_exceptions(monkeypatch):
def inner():
errors = set()
- old_capture_event_hub = sentry_sdk.Hub.capture_event
- old_capture_event_scope = sentry_sdk.Scope.capture_event
+ old_capture_event = sentry_sdk.Scope.capture_event
- def capture_event_hub(self, event, hint=None, scope=None):
- """
- Can be removed when we remove push_scope and the Hub from the SDK.
- """
+ def capture_event(self, event, hint=None, scope=None):
if hint:
if "exc_info" in hint:
error = hint["exc_info"][1]
errors.add(error)
- return old_capture_event_hub(self, event, hint=hint, scope=scope)
+ return old_capture_event(self, event, hint=hint, scope=scope)
- def capture_event_scope(self, event, hint=None, scope=None):
- if hint:
- if "exc_info" in hint:
- error = hint["exc_info"][1]
- errors.add(error)
- return old_capture_event_scope(self, event, hint=hint, scope=scope)
-
- monkeypatch.setattr(sentry_sdk.Hub, "capture_event", capture_event_hub)
- monkeypatch.setattr(sentry_sdk.Scope, "capture_event", capture_event_scope)
+ monkeypatch.setattr(sentry_sdk.Scope, "capture_event", capture_event)
return errors
diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py
index 9abf52b120..7690f01ffb 100644
--- a/tests/integrations/django/asgi/test_asgi.py
+++ b/tests/integrations/django/asgi/test_asgi.py
@@ -1,10 +1,8 @@
import asyncio
import base64
import inspect
-import json
import os
import sys
-from unittest import mock
import django
import pytest
@@ -197,85 +195,6 @@ async def test_async_views(
}
-@pytest.mark.parametrize("application", APPS)
-@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
-@pytest.mark.parametrize("middleware_spans", [False, True])
-@pytest.mark.asyncio
-@pytest.mark.skipif(
- django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
-)
-@pytest.mark.parametrize("span_streaming", [True, False])
-async def test_active_thread_id(
- sentry_init,
- capture_envelopes,
- capture_items,
- teardown_profiling,
- endpoint,
- application,
- middleware_spans,
- span_streaming,
-):
- sentry_init(
- integrations=[DjangoIntegration(middleware_spans=middleware_spans)],
- traces_sample_rate=1.0,
- profiles_sample_rate=1.0,
- trace_lifecycle="stream" if span_streaming else "static",
- )
-
- comm = HttpCommunicator(application, "GET", endpoint)
-
- if span_streaming:
- with mock.patch(
- "sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0
- ):
- items = capture_items("span")
- response = await comm.get_response()
- await comm.wait()
-
- assert response["status"] == 200, response["body"]
-
- data = json.loads(response["body"])
-
- sentry_sdk.flush()
- spans = [item.payload for item in items]
-
- for span in spans:
- if span["is_segment"] is False:
- continue
- assert str(data["active"]) == span["attributes"]["thread.id"]
- else:
- with mock.patch(
- "sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0
- ):
- envelopes = capture_envelopes()
- response = await comm.get_response()
- await comm.wait()
-
- assert response["status"] == 200, response["body"]
-
- assert len(envelopes) == 1
-
- profiles = [item for item in envelopes[0].items if item.type == "profile"]
- assert len(profiles) == 1
-
- data = json.loads(response["body"])
-
- for item in profiles:
- transactions = item.payload.json["transactions"]
- assert len(transactions) == 1
- assert str(data["active"]) == transactions[0]["active_thread_id"]
-
- transactions = [
- item for item in envelopes[0].items if item.type == "transaction"
- ]
- assert len(transactions) == 1
-
- for item in transactions:
- transaction = item.payload.json
- trace_context = transaction["contexts"]["trace"]
- assert str(data["active"]) == trace_context["data"]["thread.id"]
-
-
@pytest.mark.asyncio
@pytest.mark.skipif(
django.VERSION < (3, 1), reason="async views have been introduced in Django 3.1"
diff --git a/tests/integrations/django/test_basic.py b/tests/integrations/django/test_basic.py
index 8f6c32cdb0..bf4fabf5ec 100644
--- a/tests/integrations/django/test_basic.py
+++ b/tests/integrations/django/test_basic.py
@@ -2386,61 +2386,6 @@ def test_transaction_http_method_custom(
assert event2["request"]["method"] == "HEAD"
-def test_ensures_spotlight_middleware_when_spotlight_is_enabled(sentry_init, settings):
- """
- Test that ensures if Spotlight is enabled, relevant SpotlightMiddleware
- is added to middleware list in settings.
- """
- settings.DEBUG = True
- original_middleware = frozenset(settings.MIDDLEWARE)
-
- sentry_init(integrations=[DjangoIntegration()], spotlight=True)
-
- added = frozenset(settings.MIDDLEWARE) ^ original_middleware
-
- assert "sentry_sdk.spotlight.SpotlightMiddleware" in added
-
-
-def test_ensures_no_spotlight_middleware_when_env_killswitch_is_false(
- monkeypatch, sentry_init, settings
-):
- """
- Test that ensures if Spotlight is enabled, but is set to a falsy value
- the relevant SpotlightMiddleware is NOT added to middleware list in settings.
- """
- settings.DEBUG = True
- monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "no")
-
- original_middleware = frozenset(settings.MIDDLEWARE)
-
- sentry_init(integrations=[DjangoIntegration()], spotlight=True)
-
- added = frozenset(settings.MIDDLEWARE) ^ original_middleware
-
- assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added
-
-
-def test_ensures_no_spotlight_middleware_when_no_spotlight(
- monkeypatch, sentry_init, settings
-):
- """
- Test that ensures if Spotlight is not enabled
- the relevant SpotlightMiddleware is NOT added to middleware list in settings.
- """
- settings.DEBUG = True
-
- # We should NOT have the middleware even if the env var is truthy if Spotlight is off
- monkeypatch.setenv("SENTRY_SPOTLIGHT_ON_ERROR", "1")
-
- original_middleware = frozenset(settings.MIDDLEWARE)
-
- sentry_init(integrations=[DjangoIntegration()], spotlight=False)
-
- added = frozenset(settings.MIDDLEWARE) ^ original_middleware
-
- assert "sentry_sdk.spotlight.SpotlightMiddleware" not in added
-
-
def test_get_frame_name_when_in_lazy_object():
allowed_to_init = False
diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py
index 913396f356..8f334509cd 100644
--- a/tests/integrations/fastapi/test_fastapi.py
+++ b/tests/integrations/fastapi/test_fastapi.py
@@ -4,7 +4,6 @@
import os
import threading
import warnings
-from unittest import mock
import fastapi
import pytest
@@ -57,7 +56,6 @@
)
from tests.integrations.conftest import parametrize_test_configurable_status_codes
-from tests.integrations.starlette import test_starlette
def fastapi_app_factory():
@@ -458,44 +456,6 @@ def test_legacy_setup(
assert event["transaction"] == "/message/{message_id}"
-@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
-@mock.patch("sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0)
-def test_active_thread_id(sentry_init, capture_envelopes, teardown_profiling, endpoint):
- sentry_init(
- traces_sample_rate=1.0,
- profiles_sample_rate=1.0,
- )
- app = fastapi_app_factory()
- asgi_app = SentryAsgiMiddleware(app)
-
- envelopes = capture_envelopes()
-
- client = TestClient(asgi_app)
- response = client.get(endpoint)
- assert response.status_code == 200
-
- data = json.loads(response.content)
-
- envelopes = [envelope for envelope in envelopes]
- assert len(envelopes) == 1
-
- profiles = [item for item in envelopes[0].items if item.type == "profile"]
- assert len(profiles) == 1
-
- for item in profiles:
- transactions = item.payload.json["transactions"]
- assert len(transactions) == 1
- assert str(data["active"]) == transactions[0]["active_thread_id"]
-
- transactions = [item for item in envelopes[0].items if item.type == "transaction"]
- assert len(transactions) == 1
-
- for item in transactions:
- transaction = item.payload.json
- trace_context = transaction["contexts"]["trace"]
- assert str(data["active"]) == trace_context["data"]["thread.id"]
-
-
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint):
sentry_init(
@@ -916,48 +876,6 @@ def test_transaction_name_in_middleware(
)
-@test_starlette.parametrize_test_configurable_status_codes_deprecated
-def test_configurable_status_codes_deprecated(
- sentry_init,
- capture_events,
- failed_request_status_codes,
- status_code,
- expected_error,
-):
- with pytest.warns(DeprecationWarning):
- starlette_integration = StarletteIntegration(
- failed_request_status_codes=failed_request_status_codes
- )
-
- with pytest.warns(DeprecationWarning):
- fast_api_integration = FastApiIntegration(
- failed_request_status_codes=failed_request_status_codes
- )
-
- sentry_init(
- integrations=[
- starlette_integration,
- fast_api_integration,
- ]
- )
-
- events = capture_events()
-
- app = FastAPI()
-
- @app.get("/error")
- async def _error():
- raise HTTPException(status_code)
-
- client = TestClient(app)
- client.get("/error")
-
- if expected_error:
- assert len(events) == 1
- else:
- assert not events
-
-
@pytest.mark.skipif(
FASTAPI_VERSION < (0, 80),
reason="Requires FastAPI >= 0.80, because earlier versions do not support HTTP 'HEAD' requests",
diff --git a/tests/integrations/opentelemetry/__init__.py b/tests/integrations/opentelemetry/__init__.py
deleted file mode 100644
index 75763c2fee..0000000000
--- a/tests/integrations/opentelemetry/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import pytest
-
-pytest.importorskip("opentelemetry")
diff --git a/tests/integrations/opentelemetry/test_entry_points.py b/tests/integrations/opentelemetry/test_entry_points.py
deleted file mode 100644
index 77d9898278..0000000000
--- a/tests/integrations/opentelemetry/test_entry_points.py
+++ /dev/null
@@ -1,26 +0,0 @@
-try:
- from importlib.metadata import entry_points
-except ImportError:
- from importlib_metadata import entry_points
-
-from sentry_sdk.integrations.opentelemetry import SentryPropagator
-
-
-def test_propagator_registered_as_entry_point():
- all_eps = entry_points()
-
- if isinstance(all_eps, dict):
- # Python 3.7-3.8 stdlib returns a dict keyed by group
- matches = [
- ep
- for ep in all_eps.get("opentelemetry_propagator", [])
- if ep.name == "sentry"
- ]
- else:
- matches = list(all_eps.select(group="opentelemetry_propagator", name="sentry"))
-
- assert len(matches) >= 1
- assert matches[0].value == "sentry_sdk.integrations.opentelemetry:SentryPropagator"
-
- loaded = matches[0].load()
- assert loaded is SentryPropagator
diff --git a/tests/integrations/opentelemetry/test_experimental.py b/tests/integrations/opentelemetry/test_experimental.py
deleted file mode 100644
index 905ab0d5b4..0000000000
--- a/tests/integrations/opentelemetry/test_experimental.py
+++ /dev/null
@@ -1,57 +0,0 @@
-from unittest.mock import MagicMock, patch
-
-import pytest
-
-from sentry_sdk.integrations.opentelemetry.integration import OpenTelemetryIntegration
-
-
-@pytest.mark.forked
-def test_integration_enabled_if_option_is_on(sentry_init, reset_integrations):
- mocked_setup_sentry_tracing = MagicMock()
-
- with patch(
- "sentry_sdk.integrations.opentelemetry.integration._setup_sentry_tracing",
- mocked_setup_sentry_tracing,
- ):
- with pytest.warns(DeprecationWarning):
- sentry_init(_experiments={"otel_powered_performance": True})
- mocked_setup_sentry_tracing.assert_called_once()
-
-
-@pytest.mark.forked
-def test_integration_not_enabled_if_option_is_off(sentry_init, reset_integrations):
- mocked_setup_sentry_tracing = MagicMock()
-
- with patch(
- "sentry_sdk.integrations.opentelemetry.integration._setup_sentry_tracing",
- mocked_setup_sentry_tracing,
- ):
- sentry_init(_experiments={"otel_powered_performance": False})
- mocked_setup_sentry_tracing.assert_not_called()
-
-
-@pytest.mark.forked
-def test_integration_not_enabled_if_option_is_missing(sentry_init, reset_integrations):
- mocked_setup_sentry_tracing = MagicMock()
-
- with patch(
- "sentry_sdk.integrations.opentelemetry.integration._setup_sentry_tracing",
- mocked_setup_sentry_tracing,
- ):
- sentry_init()
- mocked_setup_sentry_tracing.assert_not_called()
-
-
-@pytest.mark.forked
-def test_integration_disabled_with_span_streaming(sentry_init, reset_integrations):
- mocked_setup_sentry_tracing = MagicMock()
-
- with patch(
- "sentry_sdk.integrations.opentelemetry.integration._setup_sentry_tracing",
- mocked_setup_sentry_tracing,
- ):
- sentry_init(
- integrations=[OpenTelemetryIntegration()],
- trace_lifecycle="stream",
- )
- mocked_setup_sentry_tracing.assert_not_called()
diff --git a/tests/integrations/opentelemetry/test_propagator.py b/tests/integrations/opentelemetry/test_propagator.py
deleted file mode 100644
index 1c3e301c3d..0000000000
--- a/tests/integrations/opentelemetry/test_propagator.py
+++ /dev/null
@@ -1,322 +0,0 @@
-from unittest import mock
-from unittest.mock import MagicMock
-
-import pytest
-from opentelemetry import trace
-from opentelemetry.context import get_current
-from opentelemetry.trace import (
- SpanContext,
- TraceFlags,
- set_span_in_context,
-)
-from opentelemetry.trace.propagation import get_current_span
-
-from sentry_sdk.integrations.opentelemetry.consts import (
- SENTRY_BAGGAGE_KEY,
- SENTRY_TRACE_KEY,
-)
-from sentry_sdk.integrations.opentelemetry.integration import (
- OpenTelemetryIntegration,
-)
-from sentry_sdk.integrations.opentelemetry.propagator import SentryPropagator
-from sentry_sdk.integrations.opentelemetry.span_processor import SentrySpanProcessor
-from sentry_sdk.scope import global_event_processors
-from sentry_sdk.tracing_utils import Baggage
-
-
-@pytest.mark.forked
-def test_extract_no_context_no_sentry_trace_header():
- """
- No context and NO Sentry trace data in getter.
- Extract should return empty context.
- """
- carrier = None
- context = None
- getter = MagicMock()
- getter.get.return_value = None
-
- modified_context = SentryPropagator().extract(carrier, context, getter)
-
- assert modified_context == {}
-
-
-@pytest.mark.forked
-def test_extract_context_no_sentry_trace_header():
- """
- Context but NO Sentry trace data in getter.
- Extract should return context as is.
- """
- carrier = None
- context = {"some": "value"}
- getter = MagicMock()
- getter.get.return_value = None
-
- modified_context = SentryPropagator().extract(carrier, context, getter)
-
- assert modified_context == context
-
-
-@pytest.mark.forked
-def test_extract_empty_context_sentry_trace_header_no_baggage():
- """
- Empty context but Sentry trace data but NO Baggage in getter.
- Extract should return context that has empty baggage in it and also a NoopSpan with span_id and trace_id.
- """
- carrier = None
- context = {}
- getter = MagicMock()
- getter.get.side_effect = [
- ["1234567890abcdef1234567890abcdef-1234567890abcdef-1"],
- None,
- ]
-
- modified_context = SentryPropagator().extract(carrier, context, getter)
-
- assert len(modified_context.keys()) == 3
-
- assert modified_context[SENTRY_TRACE_KEY] == {
- "trace_id": "1234567890abcdef1234567890abcdef",
- "parent_span_id": "1234567890abcdef",
- "parent_sampled": True,
- }
- assert modified_context[SENTRY_BAGGAGE_KEY].serialize() == ""
-
- span_context = get_current_span(modified_context).get_span_context()
- assert span_context.span_id == int("1234567890abcdef", 16)
- assert span_context.trace_id == int("1234567890abcdef1234567890abcdef", 16)
-
-
-@pytest.mark.forked
-def test_extract_context_sentry_trace_header_baggage():
- """
- Empty context but Sentry trace data and Baggage in getter.
- Extract should return context that has baggage in it and also a NoopSpan with span_id and trace_id.
- """
- baggage_header = (
- "other-vendor-value-1=foo;bar;baz, sentry-trace_id=771a43a4192642f0b136d5159a501700, "
- "sentry-public_key=49d0f7386ad645858ae85020e393bef3, sentry-sample_rate=0.01337, "
- "sentry-user_id=Am%C3%A9lie, other-vendor-value-2=foo;bar;"
- )
-
- carrier = None
- context = {"some": "value"}
- getter = MagicMock()
- getter.get.side_effect = [
- ["1234567890abcdef1234567890abcdef-1234567890abcdef-1"],
- [baggage_header],
- ]
-
- modified_context = SentryPropagator().extract(carrier, context, getter)
-
- assert len(modified_context.keys()) == 4
-
- assert modified_context[SENTRY_TRACE_KEY] == {
- "trace_id": "1234567890abcdef1234567890abcdef",
- "parent_span_id": "1234567890abcdef",
- "parent_sampled": True,
- }
-
- assert modified_context[SENTRY_BAGGAGE_KEY].serialize() == (
- "sentry-trace_id=771a43a4192642f0b136d5159a501700,"
- "sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
- "sentry-sample_rate=0.01337,sentry-user_id=Am%C3%A9lie"
- )
-
- span_context = get_current_span(modified_context).get_span_context()
- assert span_context.span_id == int("1234567890abcdef", 16)
- assert span_context.trace_id == int("1234567890abcdef1234567890abcdef", 16)
-
-
-@pytest.mark.forked
-def test_inject_empty_otel_span_map():
- """
- Empty otel_span_map.
- So there is no sentry_span to be found in inject()
- and the function is returned early and no setters are called.
- """
- carrier = None
- context = get_current()
- setter = MagicMock()
- setter.set = MagicMock()
-
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- trace_flags=TraceFlags(TraceFlags.SAMPLED),
- is_remote=True,
- )
- span = MagicMock()
- span.get_span_context.return_value = span_context
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.propagator.trace.get_current_span",
- return_value=span,
- ):
- full_context = set_span_in_context(span, context)
- SentryPropagator().inject(carrier, full_context, setter)
-
- setter.set.assert_not_called()
-
-
-@pytest.mark.forked
-def test_inject_sentry_span_no_baggage():
- """
- Inject a sentry span with no baggage.
- """
- carrier = None
- context = get_current()
- setter = MagicMock()
- setter.set = MagicMock()
-
- trace_id = "1234567890abcdef1234567890abcdef"
- span_id = "1234567890abcdef"
-
- span_context = SpanContext(
- trace_id=int(trace_id, 16),
- span_id=int(span_id, 16),
- trace_flags=TraceFlags(TraceFlags.SAMPLED),
- is_remote=True,
- )
- span = MagicMock()
- span.get_span_context.return_value = span_context
-
- sentry_span = MagicMock()
- sentry_span.to_traceparent = mock.Mock(
- return_value="1234567890abcdef1234567890abcdef-1234567890abcdef-1"
- )
- sentry_span.containing_transaction.get_baggage = mock.Mock(return_value=None)
-
- span_processor = SentrySpanProcessor()
- span_processor.otel_span_map[span_id] = sentry_span
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.propagator.trace.get_current_span",
- return_value=span,
- ):
- full_context = set_span_in_context(span, context)
- SentryPropagator().inject(carrier, full_context, setter)
-
- setter.set.assert_called_once_with(
- carrier,
- "sentry-trace",
- "1234567890abcdef1234567890abcdef-1234567890abcdef-1",
- )
-
-
-def test_inject_sentry_span_empty_baggage():
- """
- Inject a sentry span with no baggage.
- """
- carrier = None
- context = get_current()
- setter = MagicMock()
- setter.set = MagicMock()
-
- trace_id = "1234567890abcdef1234567890abcdef"
- span_id = "1234567890abcdef"
-
- span_context = SpanContext(
- trace_id=int(trace_id, 16),
- span_id=int(span_id, 16),
- trace_flags=TraceFlags(TraceFlags.SAMPLED),
- is_remote=True,
- )
- span = MagicMock()
- span.get_span_context.return_value = span_context
-
- sentry_span = MagicMock()
- sentry_span.to_traceparent = mock.Mock(
- return_value="1234567890abcdef1234567890abcdef-1234567890abcdef-1"
- )
- sentry_span.containing_transaction.get_baggage = mock.Mock(return_value=Baggage({}))
-
- span_processor = SentrySpanProcessor()
- span_processor.otel_span_map[span_id] = sentry_span
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.propagator.trace.get_current_span",
- return_value=span,
- ):
- full_context = set_span_in_context(span, context)
- SentryPropagator().inject(carrier, full_context, setter)
-
- setter.set.assert_called_once_with(
- carrier,
- "sentry-trace",
- "1234567890abcdef1234567890abcdef-1234567890abcdef-1",
- )
-
-
-def test_inject_sentry_span_baggage():
- """
- Inject a sentry span with baggage.
- """
- carrier = None
- context = get_current()
- setter = MagicMock()
- setter.set = MagicMock()
-
- trace_id = "1234567890abcdef1234567890abcdef"
- span_id = "1234567890abcdef"
-
- span_context = SpanContext(
- trace_id=int(trace_id, 16),
- span_id=int(span_id, 16),
- trace_flags=TraceFlags(TraceFlags.SAMPLED),
- is_remote=True,
- )
- span = MagicMock()
- span.get_span_context.return_value = span_context
-
- sentry_span = MagicMock()
- sentry_span.to_traceparent = mock.Mock(
- return_value="1234567890abcdef1234567890abcdef-1234567890abcdef-1"
- )
- sentry_items = {
- "sentry-trace_id": "771a43a4192642f0b136d5159a501700",
- "sentry-public_key": "49d0f7386ad645858ae85020e393bef3",
- "sentry-sample_rate": 0.01337,
- "sentry-user_id": "Amélie",
- }
- baggage = Baggage(sentry_items=sentry_items)
- sentry_span.containing_transaction.get_baggage = MagicMock(return_value=baggage)
-
- span_processor = SentrySpanProcessor()
- span_processor.otel_span_map[span_id] = sentry_span
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.propagator.trace.get_current_span",
- return_value=span,
- ):
- full_context = set_span_in_context(span, context)
- SentryPropagator().inject(carrier, full_context, setter)
-
- setter.set.assert_any_call(
- carrier,
- "sentry-trace",
- "1234567890abcdef1234567890abcdef-1234567890abcdef-1",
- )
-
- setter.set.assert_any_call(
- carrier,
- "baggage",
- baggage.serialize(),
- )
-
-
-def test_inject_no_memory_leak():
-
- OpenTelemetryIntegration.setup_once()
-
- tracer = trace.get_tracer(__name__)
- propagator = SentryPropagator()
-
- cnt_before = len(global_event_processors)
-
- with tracer.start_as_current_span("bar") as new_span:
- context = set_span_in_context(new_span)
- carrier = "any_carrier"
- propagator.inject(carrier, context)
-
- cnt_after = len(global_event_processors)
- assert cnt_after == cnt_before
diff --git a/tests/integrations/opentelemetry/test_span_processor.py b/tests/integrations/opentelemetry/test_span_processor.py
deleted file mode 100644
index abecee7301..0000000000
--- a/tests/integrations/opentelemetry/test_span_processor.py
+++ /dev/null
@@ -1,635 +0,0 @@
-import time
-from datetime import datetime, timezone
-from unittest import mock
-from unittest.mock import MagicMock
-
-import pytest
-from opentelemetry.trace import SpanContext, SpanKind, Status, StatusCode
-
-import sentry_sdk
-from sentry_sdk.integrations.opentelemetry.span_processor import (
- SentrySpanProcessor,
- link_trace_context_to_error_event,
-)
-from sentry_sdk.scope import global_event_processors
-from sentry_sdk.tracing import Span, Transaction
-from sentry_sdk.tracing_utils import extract_sentrytrace_data
-from sentry_sdk.utils import Dsn
-
-
-def test_is_sentry_span():
- otel_span = MagicMock()
-
- span_processor = SentrySpanProcessor()
- assert not span_processor._is_sentry_span(otel_span)
-
- client = MagicMock()
- client.options = {"instrumenter": "otel"}
- client.parsed_dsn = Dsn("https://1234567890abcdef@o123456.ingest.sentry.io/123456")
- sentry_sdk.get_global_scope().set_client(client)
-
- assert not span_processor._is_sentry_span(otel_span)
-
- otel_span.attributes = {
- "http.url": "https://example.com",
- }
- assert not span_processor._is_sentry_span(otel_span)
-
- otel_span.attributes = {
- "http.url": "https://o123456.ingest.sentry.io/api/123/envelope",
- }
- assert span_processor._is_sentry_span(otel_span)
-
-
-def test_get_otel_context():
- otel_span = MagicMock()
- otel_span.attributes = {"foo": "bar"}
- otel_span.resource = MagicMock()
- otel_span.resource.attributes = {"baz": "qux"}
-
- span_processor = SentrySpanProcessor()
- otel_context = span_processor._get_otel_context(otel_span)
-
- assert otel_context == {
- "attributes": {"foo": "bar"},
- "resource": {"baz": "qux"},
- }
-
-
-def test_get_trace_data_with_span_and_trace():
- otel_span = MagicMock()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = None
-
- parent_context = {}
-
- span_processor = SentrySpanProcessor()
- sentry_trace_data = span_processor._get_trace_data(
- otel_span.get_span_context(), otel_span.parent, parent_context
- )
- assert sentry_trace_data["trace_id"] == "1234567890abcdef1234567890abcdef"
- assert sentry_trace_data["span_id"] == "1234567890abcdef"
- assert sentry_trace_data["parent_span_id"] is None
- assert sentry_trace_data["parent_sampled"] is None
- assert sentry_trace_data["baggage"] is None
-
-
-def test_get_trace_data_with_span_and_trace_and_parent():
- otel_span = MagicMock()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- parent_context = {}
-
- span_processor = SentrySpanProcessor()
- sentry_trace_data = span_processor._get_trace_data(
- otel_span.get_span_context(), otel_span.parent, parent_context
- )
- assert sentry_trace_data["trace_id"] == "1234567890abcdef1234567890abcdef"
- assert sentry_trace_data["span_id"] == "1234567890abcdef"
- assert sentry_trace_data["parent_span_id"] == "abcdef1234567890"
- assert sentry_trace_data["parent_sampled"] is None
- assert sentry_trace_data["baggage"] is None
-
-
-def test_get_trace_data_with_sentry_trace():
- otel_span = MagicMock()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- parent_context = {}
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.span_processor.get_value",
- side_effect=[
- extract_sentrytrace_data(
- "1234567890abcdef1234567890abcdef-1234567890abcdef-1"
- ),
- None,
- ],
- ):
- span_processor = SentrySpanProcessor()
- sentry_trace_data = span_processor._get_trace_data(
- otel_span.get_span_context(), otel_span.parent, parent_context
- )
- assert sentry_trace_data["trace_id"] == "1234567890abcdef1234567890abcdef"
- assert sentry_trace_data["span_id"] == "1234567890abcdef"
- assert sentry_trace_data["parent_span_id"] == "abcdef1234567890"
- assert sentry_trace_data["parent_sampled"] is True
- assert sentry_trace_data["baggage"] is None
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.span_processor.get_value",
- side_effect=[
- extract_sentrytrace_data(
- "1234567890abcdef1234567890abcdef-1234567890abcdef-0"
- ),
- None,
- ],
- ):
- span_processor = SentrySpanProcessor()
- sentry_trace_data = span_processor._get_trace_data(
- otel_span.get_span_context(), otel_span.parent, parent_context
- )
- assert sentry_trace_data["trace_id"] == "1234567890abcdef1234567890abcdef"
- assert sentry_trace_data["span_id"] == "1234567890abcdef"
- assert sentry_trace_data["parent_span_id"] == "abcdef1234567890"
- assert sentry_trace_data["parent_sampled"] is False
- assert sentry_trace_data["baggage"] is None
-
-
-def test_get_trace_data_with_sentry_trace_and_baggage():
- otel_span = MagicMock()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- parent_context = {}
-
- baggage = (
- "sentry-trace_id=771a43a4192642f0b136d5159a501700,"
- "sentry-public_key=49d0f7386ad645858ae85020e393bef3,"
- "sentry-sample_rate=0.01337,sentry-user_id=Am%C3%A9lie"
- )
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.span_processor.get_value",
- side_effect=[
- extract_sentrytrace_data(
- "1234567890abcdef1234567890abcdef-1234567890abcdef-1"
- ),
- baggage,
- ],
- ):
- span_processor = SentrySpanProcessor()
- sentry_trace_data = span_processor._get_trace_data(
- otel_span.get_span_context(), otel_span.parent, parent_context
- )
- assert sentry_trace_data["trace_id"] == "1234567890abcdef1234567890abcdef"
- assert sentry_trace_data["span_id"] == "1234567890abcdef"
- assert sentry_trace_data["parent_span_id"] == "abcdef1234567890"
- assert sentry_trace_data["parent_sampled"]
- assert sentry_trace_data["baggage"] == baggage
-
-
-def test_update_span_with_otel_data_http_method():
- sentry_span = Span()
-
- otel_span = MagicMock()
- otel_span.name = "Test OTel Span"
- otel_span.kind = SpanKind.CLIENT
- otel_span.attributes = {
- "http.method": "GET",
- "http.status_code": 429,
- "http.status_text": "xxx",
- "http.user_agent": "curl/7.64.1",
- "net.peer.name": "example.com",
- "http.target": "/",
- }
-
- span_processor = SentrySpanProcessor()
- span_processor._update_span_with_otel_data(sentry_span, otel_span)
-
- assert sentry_span.op == "http.client"
- assert sentry_span.description == "GET example.com /"
- assert sentry_span.status == "resource_exhausted"
-
- assert sentry_span._data["http.method"] == "GET"
- assert sentry_span._data["http.response.status_code"] == 429
- assert sentry_span._data["http.status_text"] == "xxx"
- assert sentry_span._data["http.user_agent"] == "curl/7.64.1"
- assert sentry_span._data["net.peer.name"] == "example.com"
- assert sentry_span._data["http.target"] == "/"
-
-
-@pytest.mark.parametrize(
- "otel_status, expected_status",
- [
- pytest.param(Status(StatusCode.UNSET), None, id="unset"),
- pytest.param(Status(StatusCode.OK), "ok", id="ok"),
- pytest.param(Status(StatusCode.ERROR), "internal_error", id="error"),
- ],
-)
-def test_update_span_with_otel_status(otel_status, expected_status):
- sentry_span = Span()
-
- otel_span = MagicMock()
- otel_span.name = "Test OTel Span"
- otel_span.kind = SpanKind.INTERNAL
- otel_span.status = otel_status
-
- span_processor = SentrySpanProcessor()
- span_processor._update_span_with_otel_status(sentry_span, otel_span)
-
- assert sentry_span.get_trace_context().get("status") == expected_status
-
-
-def test_update_span_with_otel_data_http_method2():
- sentry_span = Span()
-
- otel_span = MagicMock()
- otel_span.name = "Test OTel Span"
- otel_span.kind = SpanKind.SERVER
- otel_span.attributes = {
- "http.method": "GET",
- "http.status_code": 429,
- "http.status_text": "xxx",
- "http.user_agent": "curl/7.64.1",
- "http.url": "https://example.com/status/403?password=123&username=test@example.com&author=User123&auth=1234567890abcdef",
- }
-
- span_processor = SentrySpanProcessor()
- span_processor._update_span_with_otel_data(sentry_span, otel_span)
-
- assert sentry_span.op == "http.server"
- assert sentry_span.description == "GET https://example.com/status/403"
- assert sentry_span.status == "resource_exhausted"
-
- assert sentry_span._data["http.method"] == "GET"
- assert sentry_span._data["http.response.status_code"] == 429
- assert sentry_span._data["http.status_text"] == "xxx"
- assert sentry_span._data["http.user_agent"] == "curl/7.64.1"
- assert (
- sentry_span._data["http.url"]
- == "https://example.com/status/403?password=123&username=test@example.com&author=User123&auth=1234567890abcdef"
- )
-
-
-def test_update_span_with_otel_data_db_query():
- sentry_span = Span()
-
- otel_span = MagicMock()
- otel_span.name = "Test OTel Span"
- otel_span.attributes = {
- "db.system": "postgresql",
- "db.statement": "SELECT * FROM table where pwd = '123456'",
- }
-
- span_processor = SentrySpanProcessor()
- span_processor._update_span_with_otel_data(sentry_span, otel_span)
-
- assert sentry_span.op == "db"
- assert sentry_span.description == "SELECT * FROM table where pwd = '123456'"
-
- assert sentry_span._data["db.system"] == "postgresql"
- assert (
- sentry_span._data["db.statement"] == "SELECT * FROM table where pwd = '123456'"
- )
-
-
-def test_on_start_transaction():
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.start_time = time.time_ns()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- parent_context = {}
-
- fake_start_transaction = MagicMock()
-
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel"}
- fake_client.dsn = "https://1234567890abcdef@o123456.ingest.sentry.io/123456"
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.span_processor.start_transaction",
- fake_start_transaction,
- ):
- span_processor = SentrySpanProcessor()
- span_processor.on_start(otel_span, parent_context)
-
- fake_start_transaction.assert_called_once_with(
- name="Sample OTel Span",
- span_id="1234567890abcdef",
- parent_span_id="abcdef1234567890",
- trace_id="1234567890abcdef1234567890abcdef",
- baggage=None,
- start_timestamp=datetime.fromtimestamp(
- otel_span.start_time / 1e9, timezone.utc
- ),
- instrumenter="otel",
- origin="auto.otel",
- )
-
- assert len(span_processor.otel_span_map.keys()) == 1
- assert list(span_processor.otel_span_map.keys())[0] == "1234567890abcdef"
-
-
-def test_on_start_child():
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.start_time = time.time_ns()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- parent_context = {}
-
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel"}
- fake_client.dsn = "https://1234567890abcdef@o123456.ingest.sentry.io/123456"
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- fake_span = MagicMock()
-
- span_processor = SentrySpanProcessor()
- span_processor.otel_span_map["abcdef1234567890"] = fake_span
- span_processor.on_start(otel_span, parent_context)
-
- fake_span.start_child.assert_called_once_with(
- span_id="1234567890abcdef",
- name="Sample OTel Span",
- start_timestamp=datetime.fromtimestamp(
- otel_span.start_time / 1e9, timezone.utc
- ),
- instrumenter="otel",
- origin="auto.otel",
- )
-
- assert len(span_processor.otel_span_map.keys()) == 2
- assert "abcdef1234567890" in span_processor.otel_span_map.keys()
- assert "1234567890abcdef" in span_processor.otel_span_map.keys()
-
-
-def test_on_end_no_sentry_span():
- """
- If on_end is called on a span that is not in the otel_span_map, it should be a no-op.
- """
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.end_time = time.time_ns()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
-
- span_processor = SentrySpanProcessor()
- span_processor.otel_span_map = {}
- span_processor._get_otel_context = MagicMock()
- span_processor._update_span_with_otel_data = MagicMock()
-
- span_processor.on_end(otel_span)
-
- span_processor._get_otel_context.assert_not_called()
- span_processor._update_span_with_otel_data.assert_not_called()
-
-
-def test_on_end_sentry_transaction():
- """
- Test on_end for a sentry Transaction.
- """
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.end_time = time.time_ns()
- otel_span.status = Status(StatusCode.OK)
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
-
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel"}
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- fake_sentry_span = MagicMock(spec=Transaction)
- fake_sentry_span.set_context = MagicMock()
- fake_sentry_span.finish = MagicMock()
-
- span_processor = SentrySpanProcessor()
- span_processor._get_otel_context = MagicMock()
- span_processor._update_span_with_otel_data = MagicMock()
- span_processor.otel_span_map["1234567890abcdef"] = fake_sentry_span
-
- span_processor.on_end(otel_span)
-
- fake_sentry_span.set_context.assert_called_once()
- span_processor._update_span_with_otel_data.assert_not_called()
- fake_sentry_span.set_status.assert_called_once_with("ok")
- fake_sentry_span.finish.assert_called_once()
-
-
-def test_on_end_sentry_span():
- """
- Test on_end for a sentry Span.
- """
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.end_time = time.time_ns()
- otel_span.status = Status(StatusCode.OK)
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
-
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel"}
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- fake_sentry_span = MagicMock(spec=Span)
- fake_sentry_span.set_context = MagicMock()
- fake_sentry_span.finish = MagicMock()
-
- span_processor = SentrySpanProcessor()
- span_processor._get_otel_context = MagicMock()
- span_processor._update_span_with_otel_data = MagicMock()
- span_processor.otel_span_map["1234567890abcdef"] = fake_sentry_span
-
- span_processor.on_end(otel_span)
-
- fake_sentry_span.set_context.assert_not_called()
- span_processor._update_span_with_otel_data.assert_called_once_with(
- fake_sentry_span, otel_span
- )
- fake_sentry_span.set_status.assert_called_once_with("ok")
- fake_sentry_span.finish.assert_called_once()
-
-
-def test_link_trace_context_to_error_event():
- """
- Test that the trace context is added to the error event.
- """
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel"}
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- span_id = "1234567890abcdef"
- trace_id = "1234567890abcdef1234567890abcdef"
-
- fake_trace_context = {
- "bla": "blub",
- "foo": "bar",
- "baz": 123,
- }
-
- sentry_span = MagicMock()
- sentry_span.get_trace_context = MagicMock(return_value=fake_trace_context)
-
- otel_span_map = {
- span_id: sentry_span,
- }
-
- span_context = SpanContext(
- trace_id=int(trace_id, 16),
- span_id=int(span_id, 16),
- is_remote=True,
- )
- otel_span = MagicMock()
- otel_span.get_span_context = MagicMock(return_value=span_context)
-
- fake_event = {"event_id": "1234567890abcdef1234567890abcdef"}
-
- with mock.patch(
- "sentry_sdk.integrations.opentelemetry.span_processor.get_current_span",
- return_value=otel_span,
- ):
- event = link_trace_context_to_error_event(fake_event, otel_span_map)
-
- assert event
- assert event == fake_event # the event is changed in place inside the function
- assert "contexts" in event
- assert "trace" in event["contexts"]
- assert event["contexts"]["trace"] == fake_trace_context
-
-
-def test_pruning_old_spans_on_start():
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.start_time = time.time_ns()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- parent_context = {}
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel", "debug": False}
- fake_client.dsn = "https://1234567890abcdef@o123456.ingest.sentry.io/123456"
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- span_processor = SentrySpanProcessor()
-
- span_processor.otel_span_map = {
- "111111111abcdef": MagicMock(), # should stay
- "2222222222abcdef": MagicMock(), # should go
- "3333333333abcdef": MagicMock(), # should go
- }
- current_time_minutes = int(time.time() / 60)
- span_processor.open_spans = {
- current_time_minutes - 3: {"111111111abcdef"}, # should stay
- current_time_minutes - 11: {
- "2222222222abcdef",
- "3333333333abcdef",
- }, # should go
- }
-
- span_processor.on_start(otel_span, parent_context)
- assert sorted(list(span_processor.otel_span_map.keys())) == [
- "111111111abcdef",
- "1234567890abcdef",
- ]
- assert sorted(list(span_processor.open_spans.values())) == [
- {"111111111abcdef"},
- {"1234567890abcdef"},
- ]
-
-
-def test_pruning_old_spans_on_end():
- otel_span = MagicMock()
- otel_span.name = "Sample OTel Span"
- otel_span.start_time = time.time_ns()
- span_context = SpanContext(
- trace_id=int("1234567890abcdef1234567890abcdef", 16),
- span_id=int("1234567890abcdef", 16),
- is_remote=True,
- )
- otel_span.get_span_context.return_value = span_context
- otel_span.parent = MagicMock()
- otel_span.parent.span_id = int("abcdef1234567890", 16)
-
- fake_client = MagicMock()
- fake_client.options = {"instrumenter": "otel"}
- sentry_sdk.get_global_scope().set_client(fake_client)
-
- fake_sentry_span = MagicMock(spec=Span)
- fake_sentry_span.set_context = MagicMock()
- fake_sentry_span.finish = MagicMock()
-
- span_processor = SentrySpanProcessor()
- span_processor._get_otel_context = MagicMock()
- span_processor._update_span_with_otel_data = MagicMock()
-
- span_processor.otel_span_map = {
- "111111111abcdef": MagicMock(), # should stay
- "2222222222abcdef": MagicMock(), # should go
- "3333333333abcdef": MagicMock(), # should go
- "1234567890abcdef": fake_sentry_span, # should go (because it is closed)
- }
- current_time_minutes = int(time.time() / 60)
- span_processor.open_spans = {
- current_time_minutes: {"1234567890abcdef"}, # should go (because it is closed)
- current_time_minutes - 3: {"111111111abcdef"}, # should stay
- current_time_minutes - 11: {
- "2222222222abcdef",
- "3333333333abcdef",
- }, # should go
- }
-
- span_processor.on_end(otel_span)
- assert sorted(list(span_processor.otel_span_map.keys())) == ["111111111abcdef"]
- assert sorted(list(span_processor.open_spans.values())) == [{"111111111abcdef"}]
-
-
-def test_no_memory_leak():
- span_processor_1 = SentrySpanProcessor()
- cnt_before = len(global_event_processors)
-
- span_processor_2 = SentrySpanProcessor()
-
- cnt_after = len(global_event_processors)
- assert span_processor_1 is span_processor_2
- assert cnt_before == cnt_after
diff --git a/tests/integrations/otlp/test_otlp.py b/tests/integrations/otlp/test_otlp.py
index 25e2a5f7fe..504507e274 100644
--- a/tests/integrations/otlp/test_otlp.py
+++ b/tests/integrations/otlp/test_otlp.py
@@ -16,7 +16,10 @@
)
from opentelemetry.util._once import Once
-from sentry_sdk.integrations.otlp import OTLPIntegration, SentryOTLPPropagator
+from sentry_sdk.integrations.otlp import (
+ OTLPIntegration,
+ SentryOTLPPropagator,
+)
from sentry_sdk.scope import get_external_propagation_context
original_propagator = get_global_textmap()
diff --git a/tests/integrations/quart/test_quart.py b/tests/integrations/quart/test_quart.py
index e084d6ac1b..730e65b33f 100644
--- a/tests/integrations/quart/test_quart.py
+++ b/tests/integrations/quart/test_quart.py
@@ -1,8 +1,6 @@
import importlib
-import json
import sys
import threading
-from unittest import mock
import pytest
@@ -589,82 +587,6 @@ async def dispatch_request(self):
assert event["transaction"] == "hello_class"
-@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
-@pytest.mark.asyncio
-async def test_active_thread_id(
- sentry_init, capture_envelopes, teardown_profiling, endpoint
-):
- with mock.patch(
- "sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0
- ):
- sentry_init(
- traces_sample_rate=1.0,
- profiles_sample_rate=1.0,
- )
- app = quart_app_factory()
-
- envelopes = capture_envelopes()
-
- async with app.test_client() as client:
- response = await client.get(endpoint)
- assert response.status_code == 200
-
- data = json.loads(await response.get_data(as_text=True))
-
- envelopes = [envelope for envelope in envelopes]
- assert len(envelopes) == 1
-
- profiles = [item for item in envelopes[0].items if item.type == "profile"]
- assert len(profiles) == 1, envelopes[0].items
-
- for item in profiles:
- transactions = item.payload.json["transactions"]
- assert len(transactions) == 1
- assert str(data["active"]) == transactions[0]["active_thread_id"]
-
- transactions = [
- item for item in envelopes[0].items if item.type == "transaction"
- ]
- assert len(transactions) == 1
-
- for item in transactions:
- transaction = item.payload.json
- trace_context = transaction["contexts"]["trace"]
- assert str(data["active"]) == trace_context["data"]["thread.id"]
-
-
-@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
-@pytest.mark.asyncio
-async def test_active_thread_id_span_streaming(
- sentry_init, capture_items, teardown_profiling, endpoint
-):
- with mock.patch(
- "sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0
- ):
- sentry_init(
- traces_sample_rate=1.0,
- profiles_sample_rate=1.0,
- trace_lifecycle="stream",
- )
- app = quart_app_factory()
-
- items = capture_items("span")
-
- async with app.test_client() as client:
- response = await client.get(endpoint)
- assert response.status_code == 200
-
- data = json.loads(await response.get_data(as_text=True))
-
- sentry_sdk.flush()
-
- spans = [item.payload for item in items]
- assert len(spans) == 1
-
- segment = spans[0]
- assert str(data["active"]) == segment["attributes"]["thread.id"]
-
-
@pytest.mark.asyncio
async def test_span_origin(sentry_init, capture_events):
sentry_init(
diff --git a/tests/integrations/starlette/test_starlette.py b/tests/integrations/starlette/test_starlette.py
index e8198ce7b8..c110884c71 100644
--- a/tests/integrations/starlette/test_starlette.py
+++ b/tests/integrations/starlette/test_starlette.py
@@ -7,7 +7,6 @@
import re
import threading
import warnings
-from unittest import mock
import pytest
import starlette
@@ -1436,44 +1435,6 @@ def test_legacy_setup(
assert event["transaction"] == "/message/{message_id}"
-@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
-@mock.patch("sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0)
-def test_active_thread_id(sentry_init, capture_envelopes, teardown_profiling, endpoint):
- sentry_init(
- traces_sample_rate=1.0,
- profiles_sample_rate=1.0,
- )
- app = starlette_app_factory()
- asgi_app = SentryAsgiMiddleware(app)
-
- envelopes = capture_envelopes()
-
- client = TestClient(asgi_app)
- response = client.get(endpoint)
- assert response.status_code == 200
-
- data = json.loads(response.content)
-
- envelopes = [envelope for envelope in envelopes]
- assert len(envelopes) == 1
-
- profiles = [item for item in envelopes[0].items if item.type == "profile"]
- assert len(profiles) == 1
-
- for item in profiles:
- transactions = item.payload.json["transactions"]
- assert len(transactions) == 1
- assert str(data["active"]) == transactions[0]["active_thread_id"]
-
- transactions = [item for item in envelopes[0].items if item.type == "transaction"]
- assert len(transactions) == 1
-
- for item in transactions:
- transaction = item.payload.json
- trace_context = transaction["contexts"]["trace"]
- assert str(data["active"]) == trace_context["data"]["thread.id"]
-
-
@pytest.mark.parametrize("endpoint", ["/sync/thread_ids", "/async/thread_ids"])
def test_active_thread_id_span_streaming(sentry_init, capture_items, endpoint):
sentry_init(
@@ -1762,82 +1723,6 @@ def test_span_origin(sentry_init, capture_events, capture_items, span_streaming)
assert span["origin"] == "auto.http.starlette"
-class NonIterableContainer:
- """Wraps any container and makes it non-iterable.
-
- Used to test backwards compatibility with our old way of defining failed_request_status_codes, which allowed
- passing in a list of (possibly non-iterable) containers. The Python standard library does not provide any built-in
- non-iterable containers, so we have to define our own.
- """
-
- def __init__(self, inner):
- self.inner = inner
-
- def __contains__(self, item):
- return item in self.inner
-
-
-parametrize_test_configurable_status_codes_deprecated = pytest.mark.parametrize(
- "failed_request_status_codes,status_code,expected_error",
- [
- (None, 500, True),
- (None, 400, False),
- ([500, 501], 500, True),
- ([500, 501], 401, False),
- ([range(400, 499)], 401, True),
- ([range(400, 499)], 500, False),
- ([range(400, 499), range(500, 599)], 300, False),
- ([range(400, 499), range(500, 599)], 403, True),
- ([range(400, 499), range(500, 599)], 503, True),
- ([range(400, 403), 500, 501], 401, True),
- ([range(400, 403), 500, 501], 405, False),
- ([range(400, 403), 500, 501], 501, True),
- ([range(400, 403), 500, 501], 503, False),
- ([], 500, False),
- ([NonIterableContainer(range(500, 600))], 500, True),
- ([NonIterableContainer(range(500, 600))], 404, False),
- ],
-)
-"""Test cases for configurable status codes (deprecated API).
-Also used by the FastAPI tests.
-"""
-
-
-@parametrize_test_configurable_status_codes_deprecated
-def test_configurable_status_codes_deprecated(
- sentry_init,
- capture_events,
- failed_request_status_codes,
- status_code,
- expected_error,
-):
- with pytest.warns(DeprecationWarning):
- starlette_integration = StarletteIntegration(
- failed_request_status_codes=failed_request_status_codes
- )
-
- sentry_init(integrations=[starlette_integration])
-
- events = capture_events()
-
- async def _error(request):
- raise HTTPException(status_code)
-
- app = starlette.applications.Starlette(
- routes=[
- starlette.routing.Route("/error", _error, methods=["GET"]),
- ],
- )
-
- client = TestClient(app)
- client.get("/error")
-
- if expected_error:
- assert len(events) == 1
- else:
- assert not events
-
-
@pytest.mark.skipif(
STARLETTE_VERSION < (0, 21),
reason="Requires Starlette >= 0.21, because earlier versions do not support HTTP 'HEAD' requests",
diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py
index 9c06ce24a7..70a4d47e37 100644
--- a/tests/integrations/threading/test_threading.py
+++ b/tests/integrations/threading/test_threading.py
@@ -38,77 +38,6 @@ def crash():
assert not events
-@pytest.mark.filterwarnings("ignore:.*:pytest.PytestUnhandledThreadExceptionWarning")
-@pytest.mark.parametrize("propagate_hub", (True, False))
-def test_propagates_hub(sentry_init, capture_events, propagate_hub):
- sentry_init(
- default_integrations=False,
- integrations=[ThreadingIntegration(propagate_hub=propagate_hub)],
- )
- events = capture_events()
-
- def stage1():
- sentry_sdk.get_isolation_scope().set_tag("stage1", "true")
-
- t = Thread(target=stage2)
- t.start()
- t.join()
-
- def stage2():
- 1 / 0
-
- t = Thread(target=stage1)
- t.start()
- t.join()
-
- (event,) = events
-
- (exception,) = event["exception"]["values"]
-
- assert exception["type"] == "ZeroDivisionError"
- assert exception["mechanism"]["type"] == "threading"
- assert not exception["mechanism"]["handled"]
-
- # Free-threaded builds set thread_inherit_context to True, otherwise thread_inherit_context is False
- if propagate_hub or getattr(sys.flags, "thread_inherit_context", None):
- assert event["tags"]["stage1"] == "true"
- else:
- assert "stage1" not in event.get("tags", {})
-
-
-@pytest.mark.parametrize("propagate_hub", (True, False))
-def test_propagates_threadpool_hub(sentry_init, capture_events, propagate_hub):
- sentry_init(
- traces_sample_rate=1.0,
- integrations=[ThreadingIntegration(propagate_hub=propagate_hub)],
- )
- events = capture_events()
-
- def double(number):
- with sentry_sdk.start_span(op="task", name=str(number)):
- return number * 2
-
- with sentry_sdk.start_transaction(name="test_handles_threadpool"):
- with futures.ThreadPoolExecutor(max_workers=1) as executor:
- tasks = [executor.submit(double, number) for number in [1, 2, 3, 4]]
- for future in futures.as_completed(tasks):
- print("Getting future value!", future.result())
-
- sentry_sdk.flush()
-
- # Free-threaded builds set thread_inherit_context to True, otherwise thread_inherit_context is False
- if propagate_hub or getattr(sys.flags, "thread_inherit_context", None):
- assert len(events) == 1
- (event,) = events
- assert event["spans"][0]["trace_id"] == event["spans"][1]["trace_id"]
- assert event["spans"][1]["trace_id"] == event["spans"][2]["trace_id"]
- assert event["spans"][2]["trace_id"] == event["spans"][3]["trace_id"]
- assert event["spans"][3]["trace_id"] == event["spans"][0]["trace_id"]
- else:
- (event,) = events
- assert len(event["spans"]) == 0
-
-
@pytest.mark.skip(reason="Temporarily disable to release SDK 2.0a1.")
def test_circular_references(sentry_init, request):
sentry_init(default_integrations=False, integrations=[ThreadingIntegration()])
diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py
index 27c1c29b11..6d15e6d529 100644
--- a/tests/integrations/wsgi/test_wsgi.py
+++ b/tests/integrations/wsgi/test_wsgi.py
@@ -600,33 +600,6 @@ def sample_app(environ, start_response):
assert sum(agg.get("crashed", 0) for agg in session_aggregates) == 1
-@mock.patch("sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0)
-def test_profile_sent(
- sentry_init,
- capture_envelopes,
- teardown_profiling,
-):
- def test_app(environ, start_response):
- start_response("200 OK", [])
- return ["Go get the ball! Good dog!"]
-
- sentry_init(
- traces_sample_rate=1.0,
- _experiments={"profiles_sample_rate": 1.0},
- )
- app = SentryWsgiMiddleware(test_app)
- envelopes = capture_envelopes()
-
- client = Client(app)
- client.get("/")
-
- envelopes = [envelope for envelope in envelopes]
- assert len(envelopes) == 1
-
- profiles = [item for item in envelopes[0].items if item.type == "profile"]
- assert len(profiles) == 1
-
-
@pytest.mark.parametrize("span_streaming", [True, False])
def test_span_origin_manual(sentry_init, capture_events, capture_items, span_streaming):
def dogpark(environ, start_response):
diff --git a/tests/new_scopes_compat/__init__.py b/tests/new_scopes_compat/__init__.py
deleted file mode 100644
index 45391bd9ad..0000000000
--- a/tests/new_scopes_compat/__init__.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""
-Separate module for tests that check backwards compatibility of the Hub API with 1.x.
-These tests should be removed once we remove the Hub API, likely in the next major.
-
-All tests in this module are run with hub isolation, provided by `isolate_hub` autouse
-fixture, defined in `conftest.py`.
-"""
diff --git a/tests/new_scopes_compat/conftest.py b/tests/new_scopes_compat/conftest.py
deleted file mode 100644
index 7023b9a63f..0000000000
--- a/tests/new_scopes_compat/conftest.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import pytest
-
-import sentry_sdk
-
-
-@pytest.fixture(autouse=True)
-def isolate_hub(suppress_deprecation_warnings):
- with sentry_sdk.Hub(None):
- yield
diff --git a/tests/new_scopes_compat/test_new_scopes_compat.py b/tests/new_scopes_compat/test_new_scopes_compat.py
deleted file mode 100644
index 21e2ac27d3..0000000000
--- a/tests/new_scopes_compat/test_new_scopes_compat.py
+++ /dev/null
@@ -1,275 +0,0 @@
-import sentry_sdk
-from sentry_sdk.hub import Hub
-
-"""
-Those tests are meant to check the compatibility of the new scopes in SDK 2.0 with the old Hub/Scope system in SDK 1.x.
-
-Those tests have been run with the latest SDK 1.x versiona and the data used in the `assert` statements represents
-the behvaior of the SDK 1.x.
-
-This makes sure that we are backwards compatible. (on a best effort basis, there will probably be some edge cases that are not covered here)
-"""
-
-
-def test_configure_scope_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with configure_scope` block.
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with sentry_sdk.configure_scope() as scope: # configure scope
- sentry_sdk.set_tag("B1", 1)
- scope.set_tag("B2", 1)
- sentry_sdk.capture_message("Event B")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1}
- assert event_z["tags"] == {"A": 1, "B1": 1, "B2": 1, "Z": 1}
-
-
-def test_push_scope_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with push_scope` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with sentry_sdk.push_scope() as scope: # push scope
- sentry_sdk.set_tag("B1", 1)
- scope.set_tag("B2", 1)
- sentry_sdk.capture_message("Event B")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1}
- assert event_z["tags"] == {"A": 1, "Z": 1}
-
-
-def test_with_hub_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with Hub:` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with Hub.current as hub: # with hub
- sentry_sdk.set_tag("B1", 1)
- hub.scope.set_tag("B2", 1)
- sentry_sdk.capture_message("Event B")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1}
- assert event_z["tags"] == {"A": 1, "B1": 1, "B2": 1, "Z": 1}
-
-
-def test_with_hub_configure_scope_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with Hub:` containing a `with configure_scope` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with Hub.current as hub: # with hub
- sentry_sdk.set_tag("B1", 1)
- with hub.configure_scope() as scope: # configure scope
- sentry_sdk.set_tag("B2", 1)
- hub.scope.set_tag("B3", 1)
- scope.set_tag("B4", 1)
- sentry_sdk.capture_message("Event B")
- sentry_sdk.set_tag("B5", 1)
- sentry_sdk.capture_message("Event C")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_c, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1, "B3": 1, "B4": 1}
- assert event_c["tags"] == {"A": 1, "B1": 1, "B2": 1, "B3": 1, "B4": 1, "B5": 1}
- assert event_z["tags"] == {
- "A": 1,
- "B1": 1,
- "B2": 1,
- "B3": 1,
- "B4": 1,
- "B5": 1,
- "Z": 1,
- }
-
-
-def test_with_hub_push_scope_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with Hub:` containing a `with push_scope` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with Hub.current as hub: # with hub
- sentry_sdk.set_tag("B1", 1)
- with hub.push_scope() as scope: # push scope
- sentry_sdk.set_tag("B2", 1)
- hub.scope.set_tag("B3", 1)
- scope.set_tag("B4", 1)
- sentry_sdk.capture_message("Event B")
- sentry_sdk.set_tag("B5", 1)
- sentry_sdk.capture_message("Event C")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_c, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1, "B3": 1, "B4": 1}
- assert event_c["tags"] == {"A": 1, "B1": 1, "B5": 1}
- assert event_z["tags"] == {"A": 1, "B1": 1, "B5": 1, "Z": 1}
-
-
-def test_with_cloned_hub_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with cloned Hub:` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with Hub(Hub.current) as hub: # clone hub
- sentry_sdk.set_tag("B1", 1)
- hub.scope.set_tag("B2", 1)
- sentry_sdk.capture_message("Event B")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1}
- assert event_z["tags"] == {"A": 1, "Z": 1}
-
-
-def test_with_cloned_hub_configure_scope_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with cloned Hub:` containing a `with configure_scope` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with Hub(Hub.current) as hub: # clone hub
- sentry_sdk.set_tag("B1", 1)
- with hub.configure_scope() as scope: # configure scope
- sentry_sdk.set_tag("B2", 1)
- hub.scope.set_tag("B3", 1)
- scope.set_tag("B4", 1)
- sentry_sdk.capture_message("Event B")
- sentry_sdk.set_tag("B5", 1)
- sentry_sdk.capture_message("Event C")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_c, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1, "B3": 1, "B4": 1}
- assert event_c["tags"] == {"A": 1, "B1": 1, "B2": 1, "B3": 1, "B4": 1, "B5": 1}
- assert event_z["tags"] == {"A": 1, "Z": 1}
-
-
-def test_with_cloned_hub_push_scope_sdk1(sentry_init, capture_events):
- """
- Mutate data in a `with cloned Hub:` containing a `with push_scope` block
-
- Checks the results of SDK 2.x against the results the same code returned in SDK 1.x.
- """
- sentry_init()
-
- events = capture_events()
-
- sentry_sdk.set_tag("A", 1)
- sentry_sdk.capture_message("Event A")
-
- with Hub(Hub.current) as hub: # clone hub
- sentry_sdk.set_tag("B1", 1)
- with hub.push_scope() as scope: # push scope
- sentry_sdk.set_tag("B2", 1)
- hub.scope.set_tag("B3", 1)
- scope.set_tag("B4", 1)
- sentry_sdk.capture_message("Event B")
- sentry_sdk.set_tag("B5", 1)
- sentry_sdk.capture_message("Event C")
-
- sentry_sdk.set_tag("Z", 1)
- sentry_sdk.capture_message("Event Z")
-
- (event_a, event_b, event_c, event_z) = events
-
- # Check against the results the same code returned in SDK 1.x
- assert event_a["tags"] == {"A": 1}
- assert event_b["tags"] == {"A": 1, "B1": 1, "B2": 1, "B3": 1, "B4": 1}
- assert event_c["tags"] == {"A": 1, "B1": 1, "B5": 1}
- assert event_z["tags"] == {"A": 1, "Z": 1}
diff --git a/tests/new_scopes_compat/test_new_scopes_compat_event.py b/tests/new_scopes_compat/test_new_scopes_compat_event.py
deleted file mode 100644
index 0b8a4ecba9..0000000000
--- a/tests/new_scopes_compat/test_new_scopes_compat_event.py
+++ /dev/null
@@ -1,498 +0,0 @@
-from unittest import mock
-
-import pytest
-
-import sentry_sdk
-from sentry_sdk.hub import Hub
-from sentry_sdk.integrations import iter_default_integrations
-from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber
-
-"""
-Those tests are meant to check the compatibility of the new scopes in SDK 2.0 with the old Hub/Scope system in SDK 1.x.
-
-Those tests have been run with the latest SDK 1.x version and the data used in the `assert` statements represents
-the behvaior of the SDK 1.x.
-
-This makes sure that we are backwards compatible. (on a best effort basis, there will probably be some edge cases that are not covered here)
-"""
-
-
-@pytest.fixture
-def integrations():
- return [
- integration.identifier
- for integration in iter_default_integrations(
- with_auto_enabling_integrations=False
- )
- ]
-
-
-@pytest.fixture
-def expected_error(integrations):
- def create_expected_error_event(trx, span):
- return {
- "level": "warning-X",
- "exception": {
- "values": [
- {
- "mechanism": {"type": "generic", "handled": True},
- "module": None,
- "type": "ValueError",
- "value": "This is a test exception",
- "stacktrace": {
- "frames": [
- {
- "filename": "tests/new_scopes_compat/test_new_scopes_compat_event.py",
- "abs_path": mock.ANY,
- "function": "_faulty_function",
- "module": "tests.new_scopes_compat.test_new_scopes_compat_event",
- "lineno": mock.ANY,
- "pre_context": [
- " return create_expected_transaction_event",
- "",
- "",
- "def _faulty_function():",
- " try:",
- ],
- "context_line": ' raise ValueError("This is a test exception")',
- "post_context": [
- " except ValueError as ex:",
- " sentry_sdk.capture_exception(ex)",
- "",
- "",
- "def _test_before_send(event, hint):",
- ],
- "vars": {
- "ex": mock.ANY,
- },
- "in_app": True,
- }
- ]
- },
- }
- ]
- },
- "event_id": mock.ANY,
- "timestamp": mock.ANY,
- "contexts": {
- "character": {
- "name": "Mighty Fighter changed by before_send",
- "age": 19,
- "attack_type": "melee",
- },
- "trace": {
- "trace_id": trx.trace_id,
- "span_id": span.span_id,
- "parent_span_id": span.parent_span_id,
- "op": "test_span",
- "origin": "manual",
- "description": None,
- "data": {
- "thread.id": mock.ANY,
- "thread.name": "MainThread",
- },
- },
- "runtime": {
- "name": "CPython",
- "version": mock.ANY,
- "build": mock.ANY,
- },
- },
- "user": {
- "id": "123",
- "email": "jane.doe@example.com",
- },
- "transaction": "test_transaction",
- "transaction_info": {"source": "custom"},
- "tags": {"tag1": "tag1_value", "tag2": "tag2_value"},
- "extra": {
- "extra1": "extra1_value",
- "extra2": "extra2_value",
- "should_be_removed_by_event_scrubber": "[Filtered]",
- "sys.argv": "[Filtered]",
- },
- "breadcrumbs": {
- "values": [
- {
- "category": "error-level",
- "message": "Authenticated user %s",
- "level": "error",
- "data": {"breadcrumb2": "somedata"},
- "timestamp": mock.ANY,
- "type": "default",
- }
- ]
- },
- "modules": mock.ANY,
- "release": "0.1.2rc3",
- "environment": "checking-compatibility-with-sdk1",
- "server_name": mock.ANY,
- "sdk": {
- "name": "sentry.python",
- "version": mock.ANY,
- "packages": [{"name": "pypi:sentry-sdk", "version": mock.ANY}],
- "integrations": integrations,
- },
- "platform": "python",
- "_meta": {
- "extra": {
- "should_be_removed_by_event_scrubber": {
- "": {"rem": [["!config", "s"]]}
- },
- "sys.argv": {"": {"rem": [["!config", "s"]]}},
- },
- },
- }
-
- return create_expected_error_event
-
-
-@pytest.fixture
-def expected_transaction(integrations):
- def create_expected_transaction_event(trx, span):
- return {
- "type": "transaction",
- "transaction": "test_transaction changed by before_send_transaction",
- "transaction_info": {"source": "custom"},
- "contexts": {
- "trace": {
- "trace_id": trx.trace_id,
- "span_id": trx.span_id,
- "parent_span_id": None,
- "op": "test_transaction_op",
- "origin": "manual",
- "description": None,
- "data": {
- "thread.id": mock.ANY,
- "thread.name": "MainThread",
- },
- },
- "character": {
- "name": "Mighty Fighter changed by before_send_transaction",
- "age": 19,
- "attack_type": "melee",
- },
- "runtime": {
- "name": "CPython",
- "version": mock.ANY,
- "build": mock.ANY,
- },
- },
- "tags": {"tag1": "tag1_value", "tag2": "tag2_value"},
- "timestamp": mock.ANY,
- "start_timestamp": mock.ANY,
- "spans": [
- {
- "data": {
- "thread.id": mock.ANY,
- "thread.name": "MainThread",
- },
- "trace_id": trx.trace_id,
- "span_id": span.span_id,
- "parent_span_id": span.parent_span_id,
- "same_process_as_parent": True,
- "op": "test_span",
- "origin": "manual",
- "description": None,
- "start_timestamp": mock.ANY,
- "timestamp": mock.ANY,
- }
- ],
- "measurements": {"memory_used": {"value": 456, "unit": "byte"}},
- "event_id": mock.ANY,
- "level": "warning-X",
- "user": {
- "id": "123",
- "email": "jane.doe@example.com",
- },
- "extra": {
- "extra1": "extra1_value",
- "extra2": "extra2_value",
- "should_be_removed_by_event_scrubber": "[Filtered]",
- "sys.argv": "[Filtered]",
- },
- "release": "0.1.2rc3",
- "environment": "checking-compatibility-with-sdk1",
- "server_name": mock.ANY,
- "sdk": {
- "name": "sentry.python",
- "version": mock.ANY,
- "packages": [{"name": "pypi:sentry-sdk", "version": mock.ANY}],
- "integrations": integrations,
- },
- "platform": "python",
- "_meta": {
- "extra": {
- "should_be_removed_by_event_scrubber": {
- "": {"rem": [["!config", "s"]]}
- },
- "sys.argv": {"": {"rem": [["!config", "s"]]}},
- },
- },
- }
-
- return create_expected_transaction_event
-
-
-def _faulty_function():
- try:
- raise ValueError("This is a test exception")
- except ValueError as ex:
- sentry_sdk.capture_exception(ex)
-
-
-def _test_before_send(event, hint):
- event["contexts"]["character"]["name"] += " changed by before_send"
- return event
-
-
-def _test_before_send_transaction(event, hint):
- event["transaction"] += " changed by before_send_transaction"
- event["contexts"]["character"]["name"] += " changed by before_send_transaction"
- return event
-
-
-def _test_before_breadcrumb(breadcrumb, hint):
- if breadcrumb["category"] == "info-level":
- return None
- return breadcrumb
-
-
-def _generate_event_data(scope=None):
- """
- Generates some data to be used in the events sent by the tests.
- """
- sentry_sdk.set_level("warning-X")
-
- sentry_sdk.add_breadcrumb(
- category="info-level",
- message="Authenticated user %s",
- level="info",
- data={"breadcrumb1": "somedata"},
- )
- sentry_sdk.add_breadcrumb(
- category="error-level",
- message="Authenticated user %s",
- level="error",
- data={"breadcrumb2": "somedata"},
- )
-
- sentry_sdk.set_context(
- "character",
- {
- "name": "Mighty Fighter",
- "age": 19,
- "attack_type": "melee",
- },
- )
-
- sentry_sdk.set_extra("extra1", "extra1_value")
- sentry_sdk.set_extra("extra2", "extra2_value")
- sentry_sdk.set_extra("should_be_removed_by_event_scrubber", "XXX")
-
- sentry_sdk.set_tag("tag1", "tag1_value")
- sentry_sdk.set_tag("tag2", "tag2_value")
-
- sentry_sdk.set_user(
- {"id": "123", "email": "jane.doe@example.com", "ip_address": "211.161.1.124"}
- )
-
- sentry_sdk.set_measurement("memory_used", 456, "byte")
-
- if scope is not None:
- scope.add_attachment(bytes=b"Hello World", filename="hello.txt")
-
-
-def _init_sentry_sdk(sentry_init):
- sentry_init(
- environment="checking-compatibility-with-sdk1",
- release="0.1.2rc3",
- before_send=_test_before_send,
- before_send_transaction=_test_before_send_transaction,
- before_breadcrumb=_test_before_breadcrumb,
- event_scrubber=EventScrubber(
- denylist=DEFAULT_DENYLIST
- + ["should_be_removed_by_event_scrubber", "sys.argv"]
- ),
- send_default_pii=False,
- traces_sample_rate=1.0,
- auto_enabling_integrations=False,
- )
-
-
-#
-# The actual Tests start here!
-#
-
-
-def test_event(sentry_init, capture_envelopes, expected_error, expected_transaction):
- _init_sentry_sdk(sentry_init)
-
- envelopes = capture_envelopes()
-
- with sentry_sdk.start_transaction(
- name="test_transaction", op="test_transaction_op"
- ) as trx:
- with sentry_sdk.start_span(op="test_span") as span:
- with sentry_sdk.configure_scope() as scope: # configure scope
- _generate_event_data(scope)
- _faulty_function()
-
- (error_envelope, transaction_envelope) = envelopes
-
- error = error_envelope.get_event()
- transaction = transaction_envelope.get_transaction_event()
- attachment = error_envelope.items[-1]
-
- assert error == expected_error(trx, span)
- assert transaction == expected_transaction(trx, span)
- assert attachment.headers == {
- "filename": "hello.txt",
- "type": "attachment",
- "content_type": "text/plain",
- }
- assert attachment.payload.bytes == b"Hello World"
-
-
-def test_event2(sentry_init, capture_envelopes, expected_error, expected_transaction):
- _init_sentry_sdk(sentry_init)
-
- envelopes = capture_envelopes()
-
- with Hub(Hub.current):
- sentry_sdk.set_tag("A", 1) # will not be added
-
- with Hub.current: # with hub
- with sentry_sdk.push_scope() as scope:
- scope.set_tag("B", 1) # will not be added
-
- with sentry_sdk.start_transaction(
- name="test_transaction", op="test_transaction_op"
- ) as trx:
- with sentry_sdk.start_span(op="test_span") as span:
- with sentry_sdk.configure_scope() as scope: # configure scope
- _generate_event_data(scope)
- _faulty_function()
-
- (error_envelope, transaction_envelope) = envelopes
-
- error = error_envelope.get_event()
- transaction = transaction_envelope.get_transaction_event()
- attachment = error_envelope.items[-1]
-
- assert error == expected_error(trx, span)
- assert transaction == expected_transaction(trx, span)
- assert attachment.headers == {
- "filename": "hello.txt",
- "type": "attachment",
- "content_type": "text/plain",
- }
- assert attachment.payload.bytes == b"Hello World"
-
-
-def test_event3(sentry_init, capture_envelopes, expected_error, expected_transaction):
- _init_sentry_sdk(sentry_init)
-
- envelopes = capture_envelopes()
-
- with Hub(Hub.current):
- sentry_sdk.set_tag("A", 1) # will not be added
-
- with Hub.current: # with hub
- with sentry_sdk.push_scope() as scope:
- scope.set_tag("B", 1) # will not be added
-
- with sentry_sdk.push_scope() as scope: # push scope
- with sentry_sdk.start_transaction(
- name="test_transaction", op="test_transaction_op"
- ) as trx:
- with sentry_sdk.start_span(op="test_span") as span:
- _generate_event_data(scope)
- _faulty_function()
-
- (error_envelope, transaction_envelope) = envelopes
-
- error = error_envelope.get_event()
- transaction = transaction_envelope.get_transaction_event()
- attachment = error_envelope.items[-1]
-
- assert error == expected_error(trx, span)
- assert transaction == expected_transaction(trx, span)
- assert attachment.headers == {
- "filename": "hello.txt",
- "type": "attachment",
- "content_type": "text/plain",
- }
- assert attachment.payload.bytes == b"Hello World"
-
-
-def test_event4(sentry_init, capture_envelopes, expected_error, expected_transaction):
- _init_sentry_sdk(sentry_init)
-
- envelopes = capture_envelopes()
-
- with Hub(Hub.current):
- sentry_sdk.set_tag("A", 1) # will not be added
-
- with Hub(Hub.current): # with hub clone
- with sentry_sdk.push_scope() as scope:
- scope.set_tag("B", 1) # will not be added
-
- with sentry_sdk.start_transaction(
- name="test_transaction", op="test_transaction_op"
- ) as trx:
- with sentry_sdk.start_span(op="test_span") as span:
- with sentry_sdk.configure_scope() as scope: # configure scope
- _generate_event_data(scope)
- _faulty_function()
-
- (error_envelope, transaction_envelope) = envelopes
-
- error = error_envelope.get_event()
- transaction = transaction_envelope.get_transaction_event()
- attachment = error_envelope.items[-1]
-
- assert error == expected_error(trx, span)
- assert transaction == expected_transaction(trx, span)
- assert attachment.headers == {
- "filename": "hello.txt",
- "type": "attachment",
- "content_type": "text/plain",
- }
- assert attachment.payload.bytes == b"Hello World"
-
-
-def test_event5(sentry_init, capture_envelopes, expected_error, expected_transaction):
- _init_sentry_sdk(sentry_init)
-
- envelopes = capture_envelopes()
-
- with Hub(Hub.current):
- sentry_sdk.set_tag("A", 1) # will not be added
-
- with Hub(Hub.current): # with hub clone
- with sentry_sdk.push_scope() as scope:
- scope.set_tag("B", 1) # will not be added
-
- with sentry_sdk.push_scope() as scope: # push scope
- with sentry_sdk.start_transaction(
- name="test_transaction", op="test_transaction_op"
- ) as trx:
- with sentry_sdk.start_span(op="test_span") as span:
- _generate_event_data(scope)
- _faulty_function()
-
- (error_envelope, transaction_envelope) = envelopes
-
- error = error_envelope.get_event()
- transaction = transaction_envelope.get_transaction_event()
- attachment = error_envelope.items[-1]
-
- assert error == expected_error(trx, span)
- assert transaction == expected_transaction(trx, span)
- assert attachment.headers == {
- "filename": "hello.txt",
- "type": "attachment",
- "content_type": "text/plain",
- }
- assert attachment.payload.bytes == b"Hello World"
diff --git a/tests/profiler/test_transaction_profiler.py b/tests/profiler/test_transaction_profiler.py
deleted file mode 100644
index 749e91add0..0000000000
--- a/tests/profiler/test_transaction_profiler.py
+++ /dev/null
@@ -1,838 +0,0 @@
-import inspect
-import os
-import sys
-import threading
-import time
-import warnings
-from collections import defaultdict
-from unittest import mock
-
-import pytest
-
-import sentry_sdk
-from sentry_sdk import start_transaction
-from sentry_sdk._lru_cache import LRUCache
-from sentry_sdk.profiler.transaction_profiler import (
- GeventScheduler,
- Profile,
- Scheduler,
- ThreadScheduler,
- setup_profiler,
-)
-from sentry_sdk.profiler.utils import (
- extract_frame,
- extract_stack,
- frame_id,
- get_frame_name,
-)
-
-try:
- import gevent
-except ImportError:
- gevent = None
-
-
-requires_gevent = pytest.mark.skipif(gevent is None, reason="gevent not enabled")
-
-
-def process_test_sample(sample):
- # insert a mock hashable for the stack
- return [(tid, (stack, stack)) for tid, stack in sample]
-
-
-def non_experimental_options(mode=None, sample_rate=None):
- return {"profiler_mode": mode, "profiles_sample_rate": sample_rate}
-
-
-def experimental_options(mode=None, sample_rate=None):
- return {
- "_experiments": {"profiler_mode": mode, "profiles_sample_rate": sample_rate}
- }
-
-
-@pytest.mark.parametrize(
- "mode",
- [pytest.param("foo")],
-)
-@pytest.mark.parametrize(
- "make_options",
- [
- pytest.param(experimental_options, id="experiment"),
- pytest.param(non_experimental_options, id="non experimental"),
- ],
-)
-def test_profiler_invalid_mode(mode, make_options, teardown_profiling):
- with pytest.raises(ValueError):
- setup_profiler(make_options(mode))
-
-
-@pytest.mark.parametrize(
- "mode",
- [
- pytest.param("thread"),
- pytest.param("sleep"),
- pytest.param("gevent", marks=requires_gevent),
- ],
-)
-@pytest.mark.parametrize(
- "make_options",
- [
- pytest.param(experimental_options, id="experiment"),
- pytest.param(non_experimental_options, id="non experimental"),
- ],
-)
-def test_profiler_valid_mode(mode, make_options, teardown_profiling):
- # should not raise any exceptions
- setup_profiler(make_options(mode))
-
-
-@pytest.mark.parametrize(
- "make_options",
- [
- pytest.param(experimental_options, id="experiment"),
- pytest.param(non_experimental_options, id="non experimental"),
- ],
-)
-def test_profiler_setup_twice(make_options, teardown_profiling):
- # setting up the first time should return True to indicate success
- assert setup_profiler(make_options())
- # setting up the second time should return False to indicate no-op
- assert not setup_profiler(make_options())
-
-
-@pytest.mark.parametrize(
- "mode",
- [
- pytest.param("thread"),
- pytest.param("gevent", marks=requires_gevent),
- ],
-)
-@pytest.mark.parametrize(
- ("profiles_sample_rate", "profile_count"),
- [
- pytest.param(1.00, 1, id="profiler sampled at 1.00"),
- pytest.param(0.75, 1, id="profiler sampled at 0.75"),
- pytest.param(0.25, 0, id="profiler sampled at 0.25"),
- pytest.param(0.00, 0, id="profiler sampled at 0.00"),
- pytest.param(None, 0, id="profiler not enabled"),
- ],
-)
-@pytest.mark.parametrize(
- "make_options",
- [
- pytest.param(experimental_options, id="experiment"),
- pytest.param(non_experimental_options, id="non experimental"),
- ],
-)
-@mock.patch("sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0)
-def test_profiles_sample_rate(
- sentry_init,
- capture_envelopes,
- capture_record_lost_event_calls,
- teardown_profiling,
- profiles_sample_rate,
- profile_count,
- make_options,
- mode,
-):
- options = make_options(mode=mode, sample_rate=profiles_sample_rate)
- sentry_init(
- traces_sample_rate=1.0,
- profiler_mode=options.get("profiler_mode"),
- profiles_sample_rate=options.get("profiles_sample_rate"),
- _experiments=options.get("_experiments", {}),
- )
-
- envelopes = capture_envelopes()
- record_lost_event_calls = capture_record_lost_event_calls()
-
- with mock.patch(
- "sentry_sdk.profiler.transaction_profiler.random.random", return_value=0.5
- ):
- with start_transaction(name="profiling"):
- pass
-
- items = defaultdict(list)
- for envelope in envelopes:
- for item in envelope.items:
- items[item.type].append(item)
-
- assert len(items["transaction"]) == 1
- assert len(items["profile"]) == profile_count
- if profiles_sample_rate is None or profiles_sample_rate == 0:
- assert record_lost_event_calls == []
- elif profile_count:
- assert record_lost_event_calls == []
- else:
- assert record_lost_event_calls == [("sample_rate", "profile", None, 1)]
-
-
-@pytest.mark.parametrize(
- "mode",
- [
- pytest.param("thread"),
- pytest.param("gevent", marks=requires_gevent),
- ],
-)
-@pytest.mark.parametrize(
- ("profiles_sampler", "profile_count"),
- [
- pytest.param(lambda _: 1.00, 1, id="profiler sampled at 1.00"),
- pytest.param(lambda _: 0.75, 1, id="profiler sampled at 0.75"),
- pytest.param(lambda _: 0.25, 0, id="profiler sampled at 0.25"),
- pytest.param(lambda _: 0.00, 0, id="profiler sampled at 0.00"),
- pytest.param(lambda _: None, 0, id="profiler not enabled"),
- pytest.param(
- lambda ctx: 1 if ctx["transaction_context"]["name"] == "profiling" else 0,
- 1,
- id="profiler sampled for transaction name",
- ),
- pytest.param(
- lambda ctx: 0 if ctx["transaction_context"]["name"] == "profiling" else 1,
- 0,
- id="profiler not sampled for transaction name",
- ),
- pytest.param(
- lambda _: "1", 0, id="profiler not sampled because string sample rate"
- ),
- pytest.param(lambda _: True, 1, id="profiler sampled at True"),
- pytest.param(lambda _: False, 0, id="profiler sampled at False"),
- ],
-)
-@mock.patch("sentry_sdk.profiler.transaction_profiler.PROFILE_MINIMUM_SAMPLES", 0)
-def test_profiles_sampler(
- sentry_init,
- capture_envelopes,
- capture_record_lost_event_calls,
- teardown_profiling,
- profiles_sampler,
- profile_count,
- mode,
-):
- sentry_init(
- traces_sample_rate=1.0,
- profiles_sampler=profiles_sampler,
- )
-
- envelopes = capture_envelopes()
- record_lost_event_calls = capture_record_lost_event_calls()
-
- with mock.patch(
- "sentry_sdk.profiler.transaction_profiler.random.random", return_value=0.5
- ):
- with start_transaction(name="profiling"):
- pass
-
- items = defaultdict(list)
- for envelope in envelopes:
- for item in envelope.items:
- items[item.type].append(item)
-
- assert len(items["transaction"]) == 1
- assert len(items["profile"]) == profile_count
- if profile_count:
- assert record_lost_event_calls == []
- else:
- assert record_lost_event_calls == [("sample_rate", "profile", None, 1)]
-
-
-def test_minimum_unique_samples_required(
- sentry_init,
- capture_envelopes,
- capture_record_lost_event_calls,
- teardown_profiling,
-):
- sentry_init(
- traces_sample_rate=1.0,
- _experiments={"profiles_sample_rate": 1.0},
- )
-
- envelopes = capture_envelopes()
- record_lost_event_calls = capture_record_lost_event_calls()
-
- with start_transaction(name="profiling"):
- pass
-
- items = defaultdict(list)
- for envelope in envelopes:
- for item in envelope.items:
- items[item.type].append(item)
-
- assert len(items["transaction"]) == 1
- # because we dont leave any time for the profiler to
- # take any samples, it should be not be sent
- assert len(items["profile"]) == 0
- assert record_lost_event_calls == [("insufficient_data", "profile", None, 1)]
-
-
-@pytest.mark.forked
-def test_profile_captured(
- sentry_init,
- capture_envelopes,
- teardown_profiling,
-):
- sentry_init(
- traces_sample_rate=1.0,
- _experiments={"profiles_sample_rate": 1.0},
- )
-
- envelopes = capture_envelopes()
-
- with start_transaction(name="profiling"):
- time.sleep(0.05)
-
- items = defaultdict(list)
- for envelope in envelopes:
- for item in envelope.items:
- items[item.type].append(item)
-
- assert len(items["transaction"]) == 1
- assert len(items["profile"]) == 1
-
-
-def get_frame(depth=1):
- """
- This function is not exactly true to its name. Depending on
- how it is called, the true depth of the stack can be deeper
- than the argument implies.
- """
- if depth <= 0:
- raise ValueError("only positive integers allowed")
- if depth > 1:
- return get_frame(depth=depth - 1)
- return inspect.currentframe()
-
-
-class GetFrameBase:
- def inherited_instance_method(self):
- return inspect.currentframe()
-
- def inherited_instance_method_wrapped(self):
- def wrapped():
- return inspect.currentframe()
-
- return wrapped
-
- @classmethod
- def inherited_class_method(cls):
- return inspect.currentframe()
-
- @classmethod
- def inherited_class_method_wrapped(cls):
- def wrapped():
- return inspect.currentframe()
-
- return wrapped
-
- @staticmethod
- def inherited_static_method():
- return inspect.currentframe()
-
-
-class GetFrame(GetFrameBase):
- def instance_method(self):
- return inspect.currentframe()
-
- def instance_method_wrapped(self):
- def wrapped():
- return inspect.currentframe()
-
- return wrapped
-
- @classmethod
- def class_method(cls):
- return inspect.currentframe()
-
- @classmethod
- def class_method_wrapped(cls):
- def wrapped():
- return inspect.currentframe()
-
- return wrapped
-
- @staticmethod
- def static_method():
- return inspect.currentframe()
-
-
-@pytest.mark.parametrize(
- ("frame", "frame_name"),
- [
- pytest.param(
- get_frame(),
- "get_frame",
- id="function",
- ),
- pytest.param(
- (lambda: inspect.currentframe())(),
- "",
- id="lambda",
- ),
- pytest.param(
- GetFrame().instance_method(),
- "GetFrame.instance_method",
- id="instance_method",
- ),
- pytest.param(
- GetFrame().instance_method_wrapped()(),
- (
- "wrapped"
- if sys.version_info < (3, 11)
- else "GetFrame.instance_method_wrapped..wrapped"
- ),
- id="instance_method_wrapped",
- ),
- pytest.param(
- GetFrame().class_method(),
- "GetFrame.class_method",
- id="class_method",
- ),
- pytest.param(
- GetFrame().class_method_wrapped()(),
- (
- "wrapped"
- if sys.version_info < (3, 11)
- else "GetFrame.class_method_wrapped..wrapped"
- ),
- id="class_method_wrapped",
- ),
- pytest.param(
- GetFrame().static_method(),
- "static_method" if sys.version_info < (3, 11) else "GetFrame.static_method",
- id="static_method",
- ),
- pytest.param(
- GetFrame().inherited_instance_method(),
- "GetFrameBase.inherited_instance_method",
- id="inherited_instance_method",
- ),
- pytest.param(
- GetFrame().inherited_instance_method_wrapped()(),
- (
- "wrapped"
- if sys.version_info < (3, 11)
- else "GetFrameBase.inherited_instance_method_wrapped..wrapped"
- ),
- id="instance_method_wrapped",
- ),
- pytest.param(
- GetFrame().inherited_class_method(),
- "GetFrameBase.inherited_class_method",
- id="inherited_class_method",
- ),
- pytest.param(
- GetFrame().inherited_class_method_wrapped()(),
- (
- "wrapped"
- if sys.version_info < (3, 11)
- else "GetFrameBase.inherited_class_method_wrapped..wrapped"
- ),
- id="inherited_class_method_wrapped",
- ),
- pytest.param(
- GetFrame().inherited_static_method(),
- (
- "inherited_static_method"
- if sys.version_info < (3, 11)
- else "GetFrameBase.inherited_static_method"
- ),
- id="inherited_static_method",
- ),
- ],
-)
-def test_get_frame_name(frame, frame_name):
- assert get_frame_name(frame) == frame_name
-
-
-@pytest.mark.parametrize(
- ("get_frame", "function"),
- [
- pytest.param(lambda: get_frame(depth=1), "get_frame", id="simple"),
- ],
-)
-def test_extract_frame(get_frame, function):
- cwd = os.getcwd()
- frame = get_frame()
- extracted_frame = extract_frame(frame_id(frame), frame, cwd)
-
- # the abs_path should be equal toe the normalized path of the co_filename
- assert extracted_frame["abs_path"] == os.path.normpath(frame.f_code.co_filename)
-
- # the module should be pull from this test module
- assert extracted_frame["module"] == __name__
-
- # the filename should be the file starting after the cwd
- assert extracted_frame["filename"] == __file__[len(cwd) + 1 :]
-
- assert extracted_frame["function"] == function
-
- # the lineno will shift over time as this file is modified so just check
- # that it is an int
- assert isinstance(extracted_frame["lineno"], int)
-
-
-@pytest.mark.parametrize(
- ("depth", "max_stack_depth", "actual_depth"),
- [
- pytest.param(1, 128, 1, id="less than"),
- pytest.param(256, 128, 128, id="greater than"),
- pytest.param(128, 128, 128, id="equals"),
- ],
-)
-def test_extract_stack_with_max_depth(depth, max_stack_depth, actual_depth):
- # introduce a lambda that we'll be looking for in the stack
- frame = (lambda: get_frame(depth=depth))()
-
- # plus 1 because we introduced a lambda intentionally that we'll
- # look for in the final stack to make sure its in the right position
- base_stack_depth = len(inspect.stack()) + 1
-
- # increase the max_depth by the `base_stack_depth` to account
- # for the extra frames pytest will add
- _, frame_ids, frames = extract_stack(
- frame,
- LRUCache(max_size=1),
- max_stack_depth=max_stack_depth + base_stack_depth,
- cwd=os.getcwd(),
- )
- assert len(frame_ids) == base_stack_depth + actual_depth
- assert len(frames) == base_stack_depth + actual_depth
-
- for i in range(actual_depth):
- assert frames[i]["function"] == "get_frame", i
-
- # index 0 contains the inner most frame on the stack, so the lamdba
- # should be at index `actual_depth`
- if sys.version_info >= (3, 11):
- assert (
- frames[actual_depth]["function"]
- == "test_extract_stack_with_max_depth.."
- ), actual_depth
- else:
- assert frames[actual_depth]["function"] == "", actual_depth
-
-
-@pytest.mark.parametrize(
- ("frame", "depth"),
- [(get_frame(depth=1), len(inspect.stack()))],
-)
-def test_extract_stack_with_cache(frame, depth):
- # make sure cache has enough room or this test will fail
- cache = LRUCache(max_size=depth)
- cwd = os.getcwd()
- _, _, frames1 = extract_stack(frame, cache, cwd=cwd)
- _, _, frames2 = extract_stack(frame, cache, cwd=cwd)
-
- assert len(frames1) > 0
- assert len(frames2) > 0
- assert len(frames1) == len(frames2)
- for i, (frame1, frame2) in enumerate(zip(frames1, frames2)):
- # DO NOT use `==` for the assertion here since we are
- # testing for identity, and using `==` would test for
- # equality which would always pass since we're extract
- # the same stack.
- assert frame1 is frame2, i
-
-
-def get_scheduler_threads(scheduler):
- return [thread for thread in threading.enumerate() if thread.name == scheduler.name]
-
-
-@pytest.mark.parametrize(
- ("scheduler_class",),
- [
- pytest.param(ThreadScheduler, id="thread scheduler"),
- pytest.param(
- GeventScheduler,
- marks=[
- requires_gevent,
- pytest.mark.skip(
- reason="cannot find this thread via threading.enumerate()"
- ),
- ],
- id="gevent scheduler",
- ),
- ],
-)
-def test_thread_scheduler_single_background_thread(scheduler_class):
- scheduler = scheduler_class(frequency=1000)
-
- # not yet setup, no scheduler threads yet
- assert len(get_scheduler_threads(scheduler)) == 0
-
- scheduler.setup()
-
- # setup but no profiles started so still no threads
- assert len(get_scheduler_threads(scheduler)) == 0
-
- scheduler.ensure_running()
-
- # the scheduler will start always 1 thread
- assert len(get_scheduler_threads(scheduler)) == 1
-
- scheduler.ensure_running()
-
- # the scheduler still only has 1 thread
- assert len(get_scheduler_threads(scheduler)) == 1
-
- scheduler.teardown()
-
- # once finished, the thread should stop
- assert len(get_scheduler_threads(scheduler)) == 0
-
-
-@pytest.mark.parametrize(
- ("scheduler_class",),
- [
- pytest.param(ThreadScheduler, id="thread scheduler"),
- pytest.param(
- GeventScheduler,
- marks=[
- requires_gevent,
- pytest.mark.skip(
- reason="cannot find this thread via threading.enumerate()"
- ),
- ],
- id="gevent scheduler",
- ),
- ],
-)
-def test_thread_scheduler_no_thread_on_shutdown(scheduler_class):
- scheduler = scheduler_class(frequency=1000)
-
- # not yet setup, no scheduler threads yet
- assert len(get_scheduler_threads(scheduler)) == 0
-
- scheduler.setup()
-
- # setup but no profiles started so still no threads
- assert len(get_scheduler_threads(scheduler)) == 0
-
- # mock RuntimeError as if the 3.12 interpreter was shutting down
- with mock.patch(
- "threading.Thread.start",
- side_effect=RuntimeError("can't create new thread at interpreter shutdown"),
- ):
- scheduler.ensure_running()
-
- assert scheduler.running is False
-
- # still no thread
- assert len(get_scheduler_threads(scheduler)) == 0
-
- scheduler.teardown()
-
- assert len(get_scheduler_threads(scheduler)) == 0
-
-
-@pytest.mark.parametrize(
- ("scheduler_class",),
- [
- pytest.param(ThreadScheduler, id="thread scheduler"),
- pytest.param(GeventScheduler, marks=requires_gevent, id="gevent scheduler"),
- ],
-)
-@mock.patch("sentry_sdk.profiler.transaction_profiler.MAX_PROFILE_DURATION_NS", 1)
-def test_max_profile_duration_reached(scheduler_class):
- sample = [
- (
- "1",
- extract_stack(
- get_frame(),
- LRUCache(max_size=1),
- cwd=os.getcwd(),
- ),
- ),
- ]
-
- with scheduler_class(frequency=1000) as scheduler:
- with Profile(True, 0, scheduler=scheduler) as profile:
- # profile just started, it's active
- assert profile.active
-
- # write a sample at the start time, so still active
- profile.write(profile.start_ns + 0, sample)
- assert profile.active
-
- # write a sample at max time, so still active
- profile.write(profile.start_ns + 1, sample)
- assert profile.active
-
- # write a sample PAST the max time, so now inactive
- profile.write(profile.start_ns + 2, sample)
- assert not profile.active
-
-
-class NoopScheduler(Scheduler):
- def setup(self) -> None:
- pass
-
- def teardown(self) -> None:
- pass
-
- def ensure_running(self) -> None:
- pass
-
-
-current_thread = threading.current_thread()
-thread_metadata = {
- str(current_thread.ident): {
- "name": str(current_thread.name),
- },
-}
-
-
-sample_stacks = [
- extract_stack(
- get_frame(),
- LRUCache(max_size=1),
- max_stack_depth=1,
- cwd=os.getcwd(),
- ),
- extract_stack(
- get_frame(),
- LRUCache(max_size=1),
- max_stack_depth=2,
- cwd=os.getcwd(),
- ),
-]
-
-
-@pytest.mark.parametrize(
- ("samples", "expected"),
- [
- pytest.param(
- [],
- {
- "frames": [],
- "samples": [],
- "stacks": [],
- "thread_metadata": thread_metadata,
- },
- id="empty",
- ),
- pytest.param(
- [(6, [("1", sample_stacks[0])])],
- {
- "frames": [],
- "samples": [],
- "stacks": [],
- "thread_metadata": thread_metadata,
- },
- id="single sample out of range",
- ),
- pytest.param(
- [(0, [("1", sample_stacks[0])])],
- {
- "frames": [sample_stacks[0][2][0]],
- "samples": [
- {
- "elapsed_since_start_ns": "0",
- "thread_id": "1",
- "stack_id": 0,
- },
- ],
- "stacks": [[0]],
- "thread_metadata": thread_metadata,
- },
- id="single sample in range",
- ),
- pytest.param(
- [
- (0, [("1", sample_stacks[0])]),
- (1, [("1", sample_stacks[0])]),
- ],
- {
- "frames": [sample_stacks[0][2][0]],
- "samples": [
- {
- "elapsed_since_start_ns": "0",
- "thread_id": "1",
- "stack_id": 0,
- },
- {
- "elapsed_since_start_ns": "1",
- "thread_id": "1",
- "stack_id": 0,
- },
- ],
- "stacks": [[0]],
- "thread_metadata": thread_metadata,
- },
- id="two identical stacks",
- ),
- pytest.param(
- [
- (0, [("1", sample_stacks[0])]),
- (1, [("1", sample_stacks[1])]),
- ],
- {
- "frames": [
- sample_stacks[0][2][0],
- sample_stacks[1][2][0],
- ],
- "samples": [
- {
- "elapsed_since_start_ns": "0",
- "thread_id": "1",
- "stack_id": 0,
- },
- {
- "elapsed_since_start_ns": "1",
- "thread_id": "1",
- "stack_id": 1,
- },
- ],
- "stacks": [[0], [1, 0]],
- "thread_metadata": thread_metadata,
- },
- id="two different stacks",
- ),
- ],
-)
-@mock.patch("sentry_sdk.profiler.transaction_profiler.MAX_PROFILE_DURATION_NS", 5)
-def test_profile_processing(
- DictionaryContaining, # noqa: N803
- samples,
- expected,
-):
- with NoopScheduler(frequency=1000) as scheduler:
- with Profile(True, 0, scheduler=scheduler) as profile:
- for ts, sample in samples:
- # force the sample to be written at a time relative to the
- # start of the profile
- now = profile.start_ns + ts
- profile.write(now, sample)
-
- processed = profile.process()
-
- assert processed["thread_metadata"] == DictionaryContaining(
- expected["thread_metadata"]
- )
- assert processed["frames"] == expected["frames"]
- assert processed["stacks"] == expected["stacks"]
- assert processed["samples"] == expected["samples"]
-
-
-def test_hub_backwards_compatibility(suppress_deprecation_warnings):
- hub = sentry_sdk.Hub()
-
- with pytest.warns(DeprecationWarning):
- profile = Profile(True, 0, hub=hub)
-
- with pytest.warns(DeprecationWarning):
- assert profile.hub is hub
-
- new_hub = sentry_sdk.Hub()
-
- with pytest.warns(DeprecationWarning):
- profile.hub = new_hub
-
- with pytest.warns(DeprecationWarning):
- assert profile.hub is new_hub
-
-
-def test_no_warning_without_hub():
- with warnings.catch_warnings():
- warnings.simplefilter("error")
- Profile(True, 0)
diff --git a/tests/test_api.py b/tests/test_api.py
index c25ed3397d..4c83f03cb6 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -6,7 +6,6 @@
import sentry_sdk
from sentry_sdk import (
capture_exception,
- configure_scope,
continue_trace,
get_baggage,
get_client,
@@ -16,7 +15,6 @@
get_isolation_scope,
get_traceparent,
is_initialized,
- push_scope,
set_tags,
start_transaction,
)
@@ -314,18 +312,6 @@ def test_set_tags(sentry_init, capture_events):
}, "Updating tags with empty dict changed tags"
-def test_configure_scope_deprecation():
- with pytest.warns(DeprecationWarning):
- with configure_scope():
- ...
-
-
-def test_push_scope_deprecation():
- with pytest.warns(DeprecationWarning):
- with push_scope():
- ...
-
-
def test_init_context_manager_deprecation():
with pytest.warns(DeprecationWarning):
with sentry_sdk.init():
diff --git a/tests/test_basics.py b/tests/test_basics.py
index 3f9331df40..06b51d9867 100644
--- a/tests/test_basics.py
+++ b/tests/test_basics.py
@@ -11,7 +11,6 @@
import sentry_sdk
import sentry_sdk.scope
from sentry_sdk import (
- Hub,
add_breadcrumb,
capture_event,
capture_exception,
@@ -20,10 +19,8 @@
isolation_scope,
last_event_id,
new_scope,
- push_scope,
start_transaction,
)
-from sentry_sdk.client import Client
from sentry_sdk.integrations import (
_AUTO_ENABLING_INTEGRATIONS,
_DEFAULT_INTEGRATIONS,
@@ -34,7 +31,6 @@
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.stdlib import StdlibIntegration
from sentry_sdk.scope import add_global_event_processor
-from sentry_sdk.tracing_utils import has_tracing_enabled
from sentry_sdk.utils import datetime_from_isoformat, get_sdk_name, reraise
@@ -251,32 +247,6 @@ def do_this():
assert crumb["type"] == "default"
-@pytest.mark.parametrize(
- "enable_tracing, traces_sample_rate, tracing_enabled, updated_traces_sample_rate",
- [
- (None, None, False, None),
- (False, 0.0, False, 0.0),
- (False, 1.0, False, 1.0),
- (None, 1.0, True, 1.0),
- (True, 1.0, True, 1.0),
- (None, 0.0, True, 0.0), # We use this as - it's configured but turned off
- (True, 0.0, True, 0.0), # We use this as - it's configured but turned off
- (True, None, True, 1.0),
- ],
-)
-def test_option_enable_tracing(
- sentry_init,
- enable_tracing,
- traces_sample_rate,
- tracing_enabled,
- updated_traces_sample_rate,
-):
- sentry_init(enable_tracing=enable_tracing, traces_sample_rate=traces_sample_rate)
- options = sentry_sdk.get_client().options
- assert has_tracing_enabled(options) is tracing_enabled
- assert options["traces_sample_rate"] == updated_traces_sample_rate
-
-
def test_breadcrumb_arguments(sentry_init, capture_events):
assert_hint = {"bar": 42}
@@ -296,76 +266,6 @@ def before_breadcrumb(crumb, hint):
add_breadcrumb(crumb=dict(foo=42))
-def test_push_scope(sentry_init, capture_events, suppress_deprecation_warnings):
- sentry_init()
- events = capture_events()
-
- with push_scope() as scope:
- scope.level = "warning"
- try:
- 1 / 0
- except Exception as e:
- capture_exception(e)
-
- (event,) = events
-
- assert event["level"] == "warning"
- assert "exception" in event
-
-
-def test_push_scope_null_client(
- sentry_init, capture_events, suppress_deprecation_warnings
-):
- """
- This test can be removed when we remove push_scope and the Hub from the SDK.
- """
- sentry_init()
- events = capture_events()
-
- Hub.current.bind_client(None)
-
- with push_scope() as scope:
- scope.level = "warning"
- try:
- 1 / 0
- except Exception as e:
- capture_exception(e)
-
- assert len(events) == 0
-
-
-@pytest.mark.skip(
- reason="This test is not valid anymore, because push_scope just returns the isolation scope. This test should be removed once the Hub is removed"
-)
-@pytest.mark.parametrize("null_client", (True, False))
-def test_push_scope_callback(sentry_init, null_client, capture_events):
- """
- This test can be removed when we remove push_scope and the Hub from the SDK.
- """
- sentry_init()
-
- if null_client:
- Hub.current.bind_client(None)
-
- outer_scope = Hub.current.scope
-
- calls = []
-
- @push_scope
- def _(scope):
- assert scope is Hub.current.scope
- assert scope is not outer_scope
- calls.append(1)
-
- # push_scope always needs to execute the callback regardless of
- # client state, because that actually runs usercode in it, not
- # just scope config code
- assert calls == [1]
-
- # Assert scope gets popped correctly
- assert Hub.current.scope is outer_scope
-
-
def test_breadcrumbs(sentry_init, capture_events):
sentry_init(max_breadcrumbs=10)
events = capture_events()
@@ -636,71 +536,6 @@ def test_integrations(
} == expected_integrations
-@pytest.mark.skip(
- reason="This test is not valid anymore, because with the new Scopes calling bind_client on the Hub sets the client on the global scope. This test should be removed once the Hub is removed"
-)
-def test_client_initialized_within_scope(sentry_init, caplog):
- """
- This test can be removed when we remove push_scope and the Hub from the SDK.
- """
- caplog.set_level(logging.WARNING)
-
- sentry_init()
-
- with push_scope():
- Hub.current.bind_client(Client())
-
- (record,) = (x for x in caplog.records if x.levelname == "WARNING")
-
- assert record.msg.startswith("init() called inside of pushed scope.")
-
-
-@pytest.mark.skip(
- reason="This test is not valid anymore, because with the new Scopes the push_scope just returns the isolation scope. This test should be removed once the Hub is removed"
-)
-def test_scope_leaks_cleaned_up(sentry_init, caplog):
- """
- This test can be removed when we remove push_scope and the Hub from the SDK.
- """
- caplog.set_level(logging.WARNING)
-
- sentry_init()
-
- old_stack = list(Hub.current._stack)
-
- with push_scope():
- push_scope()
-
- assert Hub.current._stack == old_stack
-
- (record,) = (x for x in caplog.records if x.levelname == "WARNING")
-
- assert record.message.startswith("Leaked 1 scopes:")
-
-
-@pytest.mark.skip(
- reason="This test is not valid anymore, because with the new Scopes there is not pushing and popping of scopes. This test should be removed once the Hub is removed"
-)
-def test_scope_popped_too_soon(sentry_init, caplog):
- """
- This test can be removed when we remove push_scope and the Hub from the SDK.
- """
- caplog.set_level(logging.ERROR)
-
- sentry_init()
-
- old_stack = list(Hub.current._stack)
-
- with push_scope():
- Hub.current.pop_scope_unsafe()
-
- assert Hub.current._stack == old_stack
-
- (record,) = (x for x in caplog.records if x.levelname == "ERROR")
-
- assert record.message == ("Scope popped too soon. Popped 1 scopes too many.")
-
-
def test_scope_event_processor_order(sentry_init, capture_events):
def before_send(event, hint):
event["message"] += "baz"
@@ -1140,24 +975,6 @@ def test_last_event_id_scope(sentry_init):
assert scope.last_event_id() is None
-def test_hub_constructor_deprecation_warning():
- with pytest.warns(sentry_sdk.hub.SentryHubDeprecationWarning):
- Hub()
-
-
-def test_hub_current_deprecation_warning():
- with pytest.warns(sentry_sdk.hub.SentryHubDeprecationWarning) as warning_records:
- Hub.current
-
- # Make sure we only issue one deprecation warning
- assert len(warning_records) == 1
-
-
-def test_hub_main_deprecation_warnings():
- with pytest.warns(sentry_sdk.hub.SentryHubDeprecationWarning):
- Hub.main
-
-
@pytest.mark.skipif(sys.version_info < (3, 11), reason="add_note() not supported")
def test_notes(sentry_init, capture_events):
sentry_init()
diff --git a/tests/test_client.py b/tests/test_client.py
index 78868e434a..82ea2fcf73 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -15,12 +15,10 @@
import sentry_sdk
from sentry_sdk import (
Client,
- Hub,
add_breadcrumb,
capture_event,
capture_exception,
capture_message,
- configure_scope,
set_tag,
start_transaction,
)
@@ -635,31 +633,6 @@ def capture_envelope(self, envelope):
assert output.count(b"HI") == num_messages
-def test_configure_scope_available(
- sentry_init, request, monkeypatch, suppress_deprecation_warnings
-):
- """
- Test that scope is configured if client is configured
-
- This test can be removed once configure_scope and the Hub are removed.
- """
- sentry_init()
-
- with configure_scope() as scope:
- assert scope is Hub.current.scope
- scope.set_tag("foo", "bar")
-
- calls = []
-
- def callback(scope):
- calls.append(scope)
- scope.set_tag("foo", "bar")
-
- assert configure_scope(callback) is None
- assert len(calls) == 1
- assert calls[0] is Hub.current.scope
-
-
@pytest.mark.tests_internal_exceptions
def test_client_debug_option_enabled(sentry_init, caplog):
sentry_init(debug=True)
@@ -678,27 +651,6 @@ def test_client_debug_option_disabled(with_client, sentry_init, caplog):
assert "OK" not in caplog.text
-@pytest.mark.skip(
- reason="New behavior in SDK 2.0: You have a scope before init and add data to it."
-)
-def test_scope_initialized_before_client(sentry_init, capture_events):
- """
- This is a consequence of how configure_scope() works. We must
- make `configure_scope()` a noop if no client is configured. Even
- if the user later configures a client: We don't know that.
- """
- with configure_scope() as scope:
- scope.set_tag("foo", 42)
-
- sentry_init()
-
- events = capture_events()
- capture_message("hi")
- (event,) = events
-
- assert "tags" not in event
-
-
def test_weird_chars(sentry_init, capture_events):
sentry_init()
events = capture_events()
@@ -1351,7 +1303,7 @@ def test_uwsgi_warnings(sentry_init, recwarn, opt, missing_flags):
uwsgi = mock.MagicMock()
uwsgi.opt = opt
with mock.patch.dict("sys.modules", uwsgi=uwsgi):
- sentry_init(profiles_sample_rate=1.0)
+ sentry_init()
if missing_flags:
assert len(recwarn) == 1
record = recwarn.pop()
@@ -1519,12 +1471,6 @@ def test_dropped_transaction(sentry_init, capture_record_lost_event_calls, test_
test_config.run(sentry_init, capture_record_lost_event_calls)
-@pytest.mark.parametrize("enable_tracing", [True, False])
-def test_enable_tracing_deprecated(sentry_init, enable_tracing):
- with pytest.warns(DeprecationWarning):
- sentry_init(enable_tracing=enable_tracing)
-
-
def test_ignore_spans_warns_without_streaming(sentry_init):
with pytest.warns(UserWarning, match=r"`ignore_spans` parameter only works"):
sentry_init(ignore_spans=["/health"], trace_lifecycle="static")
diff --git a/tests/test_envelope.py b/tests/test_envelope.py
index 5549cc6e36..f6a7c4b541 100644
--- a/tests/test_envelope.py
+++ b/tests/test_envelope.py
@@ -250,7 +250,6 @@ def test_envelope_item_data_category_mapping():
("session", "session"),
("attachment", "attachment"),
("client_report", "internal"),
- ("profile", "profile"),
("profile_chunk", "profile_chunk"),
("check_in", "monitor"),
("unknown_type", "default"),
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index 731b188727..7f4fdf6748 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -1,7 +1,7 @@
from unittest import mock
import sentry_sdk
-from sentry_sdk.sessions import auto_session_tracking, track_session
+from sentry_sdk.sessions import track_session
def sorted_aggregates(item):
@@ -83,47 +83,6 @@ def test_aggregates(sentry_init, capture_envelopes):
assert aggregates[0]["errored"] == 1
-def test_aggregates_deprecated(
- sentry_init, capture_envelopes, suppress_deprecation_warnings
-):
- sentry_init(
- release="fun-release",
- environment="not-fun-env",
- )
- envelopes = capture_envelopes()
-
- with auto_session_tracking(session_mode="request"):
- with sentry_sdk.new_scope() as scope:
- try:
- scope.set_user({"id": "42"})
- raise Exception("all is wrong")
- except Exception:
- sentry_sdk.capture_exception()
-
- with auto_session_tracking(session_mode="request"):
- pass
-
- sentry_sdk.get_isolation_scope().start_session(session_mode="request")
- sentry_sdk.get_isolation_scope().end_session()
- sentry_sdk.flush()
-
- assert len(envelopes) == 2
- assert envelopes[0].get_event() is not None
-
- sess = envelopes[1]
- assert len(sess.items) == 1
- sess_event = sess.items[0].payload.json
- assert sess_event["attrs"] == {
- "release": "fun-release",
- "environment": "not-fun-env",
- }
-
- aggregates = sorted_aggregates(sess_event)
- assert len(aggregates) == 1
- assert aggregates[0]["exited"] == 2
- assert aggregates[0]["errored"] == 1
-
-
def test_aggregates_explicitly_disabled_session_tracking_request_mode(
sentry_init, capture_envelopes
):
@@ -157,38 +116,6 @@ def test_aggregates_explicitly_disabled_session_tracking_request_mode(
assert "errored" not in aggregates[0]
-def test_aggregates_explicitly_disabled_session_tracking_request_mode_deprecated(
- sentry_init, capture_envelopes, suppress_deprecation_warnings
-):
- sentry_init(
- release="fun-release", environment="not-fun-env", auto_session_tracking=False
- )
- envelopes = capture_envelopes()
-
- with auto_session_tracking(session_mode="request"):
- with sentry_sdk.new_scope():
- try:
- raise Exception("all is wrong")
- except Exception:
- sentry_sdk.capture_exception()
-
- with auto_session_tracking(session_mode="request"):
- pass
-
- sentry_sdk.get_isolation_scope().start_session(session_mode="request")
- sentry_sdk.get_isolation_scope().end_session()
- sentry_sdk.flush()
-
- sess = envelopes[1]
- assert len(sess.items) == 1
- sess_event = sess.items[0].payload.json
-
- aggregates = sorted_aggregates(sess_event)
- assert len(aggregates) == 1
- assert aggregates[0]["exited"] == 1
- assert "errored" not in aggregates[0]
-
-
def test_no_thread_on_shutdown_no_errors(sentry_init):
sentry_init(
release="fun-release",
@@ -218,36 +145,6 @@ def test_no_thread_on_shutdown_no_errors(sentry_init):
# If we reach this point without error, the test is successful.
-def test_no_thread_on_shutdown_no_errors_deprecated(
- sentry_init, suppress_deprecation_warnings
-):
- sentry_init(
- release="fun-release",
- environment="not-fun-env",
- )
-
- # make it seem like the interpreter is shutting down
- with mock.patch(
- "threading.Thread.start",
- side_effect=RuntimeError("can't create new thread at interpreter shutdown"),
- ):
- with auto_session_tracking(session_mode="request"):
- with sentry_sdk.new_scope():
- try:
- raise Exception("all is wrong")
- except Exception:
- sentry_sdk.capture_exception()
-
- with auto_session_tracking(session_mode="request"):
- pass
-
- sentry_sdk.get_isolation_scope().start_session(session_mode="request")
- sentry_sdk.get_isolation_scope().end_session()
- sentry_sdk.flush()
-
- # If we reach this point without error, the test is successful.
-
-
def test_top_level_start_session_basic(sentry_init, capture_envelopes):
"""Test that top-level start_session starts a session on the isolation scope."""
sentry_init(release="test-release", environment="test-env")
diff --git a/tests/test_transport.py b/tests/test_transport.py
index 8f74b66eed..044594f457 100644
--- a/tests/test_transport.py
+++ b/tests/test_transport.py
@@ -29,7 +29,6 @@
import sentry_sdk
from sentry_sdk import (
Client,
- Hub,
add_breadcrumb,
capture_message,
get_isolation_scope,
@@ -42,7 +41,6 @@
from sentry_sdk.transport import (
KEEP_ALIVE_SOCKET_OPTIONS,
AsyncHttpTransport,
- HttpTransport,
_parse_rate_limits,
)
@@ -802,24 +800,6 @@ def test_log_item_limits(capturing_server, response_code, item, make_client):
} in report["discarded_events"]
-def test_hub_cls_backwards_compat():
- class TestCustomHubClass(Hub):
- pass
-
- transport = HttpTransport(
- defaultdict(lambda: None, {"dsn": "https://123abc@example.com/123"})
- )
-
- with pytest.deprecated_call():
- assert transport.hub_cls is Hub
-
- with pytest.deprecated_call():
- transport.hub_cls = TestCustomHubClass
-
- with pytest.deprecated_call():
- assert transport.hub_cls is TestCustomHubClass
-
-
@pytest.mark.parametrize("quantity", (1, 2, 10))
def test_record_lost_event_quantity(capturing_server, make_client, quantity):
client = make_client()
diff --git a/tests/tracing/test_deprecated.py b/tests/tracing/test_deprecated.py
index f030a465ff..8b8150f5d1 100644
--- a/tests/tracing/test_deprecated.py
+++ b/tests/tracing/test_deprecated.py
@@ -1,36 +1,9 @@
import warnings
-import pytest
-
import sentry_sdk
import sentry_sdk.tracing
-@pytest.mark.parametrize(
- "parameter_value_getter",
- # Use lambda to avoid Hub deprecation warning here (will suppress it in the test)
- (lambda: sentry_sdk.Hub(), lambda: sentry_sdk.Scope()),
-)
-def test_passing_hub_parameter_to_transaction_finish(
- suppress_deprecation_warnings, parameter_value_getter
-):
- parameter_value = parameter_value_getter()
- transaction = sentry_sdk.tracing.Transaction()
- with pytest.warns(DeprecationWarning):
- transaction.finish(hub=parameter_value)
-
-
-def test_passing_hub_object_to_scope_transaction_finish(suppress_deprecation_warnings):
- transaction = sentry_sdk.tracing.Transaction()
-
- # Do not move the following line under the `with` statement. Otherwise, the Hub.__init__ deprecation
- # warning will be confused with the transaction.finish deprecation warning that we are testing.
- hub = sentry_sdk.Hub()
-
- with pytest.warns(DeprecationWarning):
- transaction.finish(hub)
-
-
def test_no_warnings_scope_to_transaction_finish():
transaction = sentry_sdk.tracing.Transaction()
with warnings.catch_warnings():
diff --git a/tests/tracing/test_integration_tests.py b/tests/tracing/test_integration_tests.py
index 8bc380b8b6..a174550044 100644
--- a/tests/tracing/test_integration_tests.py
+++ b/tests/tracing/test_integration_tests.py
@@ -311,20 +311,6 @@ def test_continue_trace_span_streaming(
assert message_payload["message"] == "hello"
-@pytest.mark.parametrize("sample_rate", [0.0, 1.0])
-def test_propagate_traces_deprecation_warning(sentry_init, sample_rate):
- sentry_init(traces_sample_rate=sample_rate, propagate_traces=False)
-
- with start_transaction(name="hi"):
- with start_span() as old_span:
- with pytest.warns(DeprecationWarning):
- dict(
- sentry_sdk.get_current_scope().iter_trace_propagation_headers(
- old_span
- )
- )
-
-
@pytest.mark.parametrize("sample_rate", [0.5, 1.0])
def test_dynamic_sampling_head_sdk_creates_dsc(
sentry_init, capture_envelopes, sample_rate, monkeypatch
diff --git a/tests/tracing/test_misc.py b/tests/tracing/test_misc.py
index 4fb881c9da..31cf4d55b2 100644
--- a/tests/tracing/test_misc.py
+++ b/tests/tracing/test_misc.py
@@ -4,7 +4,7 @@
import pytest
import sentry_sdk
-from sentry_sdk import set_measurement, start_span, start_transaction
+from sentry_sdk import start_span, start_transaction
from sentry_sdk.consts import MATCH_ALL
from sentry_sdk.traces import StreamedSpan
from sentry_sdk.tracing import Span, Transaction
@@ -260,90 +260,6 @@ def test_finds_non_orphan_span_on_scope_span_streaming(sentry_init):
assert scope._span.name == "sniffing"
-def test_set_measurement(sentry_init, capture_events):
- sentry_init(traces_sample_rate=1.0)
-
- events = capture_events()
-
- transaction = start_transaction(name="measuring stuff")
-
- with pytest.raises(TypeError):
- transaction.set_measurement()
-
- with pytest.raises(TypeError):
- transaction.set_measurement("metric.foo")
-
- transaction.set_measurement("metric.foo", 123)
- transaction.set_measurement("metric.bar", 456, unit="second")
- transaction.set_measurement("metric.baz", 420.69, unit="custom")
- transaction.set_measurement("metric.foobar", 12, unit="percent")
- transaction.set_measurement("metric.foobar", 17.99, unit="percent")
-
- transaction.finish()
-
- (event,) = events
- assert event["measurements"]["metric.foo"] == {"value": 123, "unit": ""}
- assert event["measurements"]["metric.bar"] == {"value": 456, "unit": "second"}
- assert event["measurements"]["metric.baz"] == {"value": 420.69, "unit": "custom"}
- assert event["measurements"]["metric.foobar"] == {"value": 17.99, "unit": "percent"}
-
-
-def test_set_measurement_public_api(sentry_init, capture_events):
- sentry_init(traces_sample_rate=1.0)
-
- events = capture_events()
-
- with start_transaction(name="measuring stuff"):
- set_measurement("metric.foo", 123)
- set_measurement("metric.bar", 456, unit="second")
-
- (event,) = events
- assert event["measurements"]["metric.foo"] == {"value": 123, "unit": ""}
- assert event["measurements"]["metric.bar"] == {"value": 456, "unit": "second"}
-
-
-def test_set_measurement_deprecated(sentry_init):
- sentry_init(traces_sample_rate=1.0)
-
- with start_transaction(name="measuring stuff") as trx:
- with pytest.warns(DeprecationWarning):
- set_measurement("metric.foo", 123)
-
- with pytest.warns(DeprecationWarning):
- trx.set_measurement("metric.bar", 456)
-
- with start_span(op="measuring span") as span:
- with pytest.warns(DeprecationWarning):
- span.set_measurement("metric.baz", 420.69, unit="custom")
-
-
-def test_set_meaurement_compared_to_set_data(sentry_init, capture_events):
- """
- This is just a test to see the difference
- between measurements and data in the resulting event payload.
- """
- sentry_init(traces_sample_rate=1.0)
-
- events = capture_events()
-
- with start_transaction(name="measuring stuff") as transaction:
- transaction.set_measurement("metric.foo", 123)
- transaction.set_data("metric.bar", 456)
-
- with start_span(op="measuring span") as span:
- span.set_measurement("metric.baz", 420.69, unit="custom")
- span.set_data("metric.qux", 789)
-
- (event,) = events
- assert event["measurements"]["metric.foo"] == {"value": 123, "unit": ""}
- assert event["contexts"]["trace"]["data"]["metric.bar"] == 456
- assert event["spans"][0]["measurements"]["metric.baz"] == {
- "value": 420.69,
- "unit": "custom",
- }
- assert event["spans"][0]["data"]["metric.qux"] == 789
-
-
@pytest.mark.parametrize(
"trace_propagation_targets,url,expected_propagation_decision",
[
diff --git a/tests/tracing/test_noop_span.py b/tests/tracing/test_noop_span.py
deleted file mode 100644
index 36778cd485..0000000000
--- a/tests/tracing/test_noop_span.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import sentry_sdk
-from sentry_sdk.tracing import NoOpSpan
-
-# These tests make sure that the examples from the documentation [1]
-# are working when OTel (OpenTelemetry) instrumentation is turned on,
-# and therefore, the Sentry tracing should not do anything.
-#
-# 1: https://docs.sentry.io/platforms/python/performance/instrumentation/custom-instrumentation/
-
-
-def test_noop_start_transaction(sentry_init):
- sentry_init(instrumenter="otel")
-
- with sentry_sdk.start_transaction(
- op="task", name="test_transaction_name"
- ) as transaction:
- assert isinstance(transaction, NoOpSpan)
- assert sentry_sdk.get_current_scope().span is transaction
-
- transaction.name = "new name"
-
-
-def test_noop_start_span(sentry_init):
- sentry_init(instrumenter="otel")
-
- with sentry_sdk.start_span(op="http", name="GET /") as span:
- assert isinstance(span, NoOpSpan)
- assert sentry_sdk.get_current_scope().span is span
-
- span.set_tag("http.response.status_code", 418)
- span.set_data("http.entity_type", "teapot")
-
-
-def test_noop_transaction_start_child(sentry_init):
- sentry_init(instrumenter="otel")
-
- transaction = sentry_sdk.start_transaction(name="task")
- assert isinstance(transaction, NoOpSpan)
-
- with transaction.start_child(op="child_task") as child:
- assert isinstance(child, NoOpSpan)
- assert sentry_sdk.get_current_scope().span is child
-
-
-def test_noop_span_start_child(sentry_init):
- sentry_init(instrumenter="otel")
- span = sentry_sdk.start_span(name="task")
- assert isinstance(span, NoOpSpan)
-
- with span.start_child(op="child_task") as child:
- assert isinstance(child, NoOpSpan)
- assert sentry_sdk.get_current_scope().span is child
diff --git a/tox.ini b/tox.ini
index 2c390e69c9..1fc2dee54a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -42,15 +42,9 @@ envlist =
# GCP
{py3.7}-gcp
- # OpenTelemetry (OTel)
- {py3.7,py3.9,py3.12,py3.13,py3.14,py3.14t}-opentelemetry
-
# OpenTelemetry with OTLP
{py3.7,py3.9,py3.12,py3.13,py3.14}-otlp
- # OpenTelemetry Experimental (POTel)
- {py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-potel
-
# === Integrations - Auto-generated ===
# These come from the populate_tox.py script.
@@ -456,17 +450,10 @@ deps =
aws_lambda: uvicorn
aws_lambda: pyyaml
- # OpenTelemetry (OTel)
- opentelemetry: opentelemetry-distro
- opentelemetry: pytest-forked
-
# OpenTelemetry with OTLP
otlp: opentelemetry-distro[otlp]
otlp: responses
-
- # OpenTelemetry Experimental (POTel)
- potel: opentelemetry-distro
- potel: pytest-forked
+ otlp: pytest-forked
# === Integrations - Auto-generated ===
# These come from the populate_tox.py script.
@@ -18513,9 +18500,7 @@ setenv =
aws_lambda: _TESTPATH=tests/integrations/aws_lambda
cloud_resource_context: _TESTPATH=tests/integrations/cloud_resource_context
gcp: _TESTPATH=tests/integrations/gcp
- opentelemetry: _TESTPATH=tests/integrations/opentelemetry
otlp: _TESTPATH=tests/integrations/otlp
- potel: _TESTPATH=tests/integrations/opentelemetry
socket: _TESTPATH=tests/integrations/socket
# These TESTPATH definitions are auto-generated by toxgen