diff --git a/docs/platforms/python/migration/2.x-to-3.x.mdx b/docs/platforms/python/migration/2.x-to-3.x.mdx new file mode 100644 index 00000000000000..cd0ac66ae9b8eb --- /dev/null +++ b/docs/platforms/python/migration/2.x-to-3.x.mdx @@ -0,0 +1,340 @@ +--- +title: Migrate from 2.x to 3.x +sidebar_order: 8997 +description: "Learn about migrating from sentry-python 2.x to 3.x" +--- + + + +Version 3.0 of the Sentry Python SDK is currently in pre-release. If you feel like giving it a spin, check out [our most recent releases](https://pypi.org/project/sentry-sdk/#history). Your feedback at this stage is invaluable, so please let us know about your experience, whether positive or negative, [on GitHub](https://github.com/getsentry/sentry-python/discussions/3936) or [on Discord](https://discord.gg/wdNEHETs87): How did the migration go? Did you encounter any issues? Is everything working as expected? + + + +This guide describes the common patterns involved in migrating to version `3.x` of the `sentry-python` SDK. For the full list of changes, check out the [detailed migration guide in the repository](https://github.com/getsentry/sentry-python/blob/potel-base/MIGRATION_GUIDE.md). + + +## Python Version Support + +Sentry Python SDK `3.x` only supports Python 3.7 and higher. If you're on an older Python version, you'll need to stay on an older version of the SDK: + +- Python 2.7-3.5: SDK `1.x` +- Python 3.6: SDK `2.x` + + +## Configuration + +The `enable_tracing` option was removed. Use [`traces_sample_rate`](/platforms/python/configuration/options/#traces_sample_rate) directly, or configure a [`traces_sampler`](/platforms/python/configuration/options/#traces_sampler) for more fine-grained control over which spans should be sampled. + +```python diff + sentry_sdk.init( +- enable_tracing=True, ++ traces_sample_rate=1.0, + ) +``` + +The deprecated `propagate_traces` option was removed. Use [`trace_propagation_targets`](/platforms/python/configuration/options/#trace_propagation_targets) instead. + +```python diff + sentry_sdk.init( + # don't propagate trace info downstream +- propagate_traces=False, ++ trace_propagation_targets=[], + ) +``` + +Note that this only affects the global SDK option. The [`propagate_traces`](/platforms/python/integrations/celery/#options) option of the Celery integration remains unchanged. + +The `profiles_sample_rate` and `profiler_mode` options previously nested under `_experiments` have been removed. They're replaced by top-level options of the same name: + +```python diff + sentry_sdk.init( +- _experiments={ +- "profiles_sample_rate": 1.0, +- "profiler_mode": "thread", +- }, ++ profiles_sample_rate=1.0, ++ profiler_mode="thread", + ) +``` + +## API Changes + +`add_attachment()` is now a part of the top-level level API and should be imported and used directly from `sentry_sdk`. + +```python diff + import sentry_sdk + +- scope = sentry_sdk.get_current_scope() +- scope.add_attachment(bytes=b"Hello World!", filename="attachment.txt") ++ sentry_sdk.add_attachment(bytes=b"Hello World!", filename="attachment.txt") +``` + +Using `sentry_sdk.add_attachment()` directly also makes sure the attachment is added to the correct scope internally. + +### Custom Tracing API + +Tracing in the Sentry Python SDK `3.x` is powered by [OpenTelemetry](https://opentelemetry.io/) in the background, which also means we're moving away from the Sentry-specific concept of transactions and towards a span-only future. `sentry_sdk.start_transaction()` is now deprecated in favor of `sentry_sdk.start_span()`. + +```python diff +- with sentry_sdk.start_transaction(): ++ with sentry_sdk.start_span(): + ... +``` + +Any spans without a parent span will become transactions by default. If you want to avoid promoting a span without a parent to a transaction, you can pass the `only_if_parent=True` keyword argument to `sentry_sdk.start_span()`. + +`sentry_sdk.start_transaction()` and `sentry_sdk.start_span()` no longer take the following arguments: `trace_id`, `baggage`, `span_id`, `parent_span_id`. Use `sentry_sdk.continue_trace()` for propagating trace data. + +`sentry_sdk.continue_trace()` no longer returns a `Transaction` and is now a context manager. To continue a trace from headers or environment variables, start a new span inside `sentry_sdk.continue_trace()`: + +```python diff +- transaction = sentry_sdk.continue_trace({...}) +- with sentry_sdk.start_transaction(transaction=transaction): +- ... ++ with sentry_sdk.continue_trace({...}): ++ with sentry_sdk.start_span(): ++ ... +``` + +The functions `continue_from_headers`, `continue_from_environ` and `from_traceparent` have been removed. Use the `sentry_sdk.continue_trace()` context manager instead. + + +## Span Data + +In OpenTelemetry, there is no concept of separate categories of data on a span: everything is simply a span attribute. This is a concept the Sentry SDK is also adopting. We deprecated `set_data()` and added a new span method called `set_attribute()`: + +```python diff + with sentry_sdk.start_span(...) as span: +- span.set_data("my_attribute", "my_value") ++ span.set_attribute("my_attribute", "my_value") +``` + +You can also set attributes directly when creating the span. This has the advantage that these initial attributes will be accessible in the sampling context in your `traces_sampler`/`profiles_sampler` (see also the [Sampling section](#sampling)). + +```python +with sentry_sdk.start_span(attributes={"my_attribute": "my_value"}): + ... +``` + + + +There are important type restrictions to consider when setting attributes on a span via `span.set_attribute()` and `start_span(attributes={...})`. The keys must be non-empty strings and the values can only be several primitive types (excluding `None`) or a list of a single primitive type. See [the OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/common/#attribute) for details. + +Note that since the SDK is now exclusively using span attributes, this restriction applies to other ways of setting data on a span as well like `span.set_data()`, `span.set_measurement()`, `span.set_context()`. + + + + +## Sampling + +It's no longer possible to change the sampling decision of a span by setting `span.sampled` directly after the span has been created. Use either a custom `traces_sampler` (preferred) or the `sampled` argument to `start_span()` for determining whether a span should be sampled. + +```python +with sentry_sdk.start_span(sampled=True) as span: + ... +``` + + + +Both `traces_sampler` and the `sampled` argument will only influence whether root spans (transactions) are sampled. They can't be used for sampling child spans. + + + +The `sampling_context` argument of `traces_sampler` and `profiles_sampler` has changed considerably for spans coming from our auto-instrumented integrations. As a consequence of using OpenTelemetry under the hood, spans can only carry specific, primitive types of data. This prevents us from making custom objects, for example, the `Request` object for several web frameworks, accessible on the span. + + + The AIOHTTP integration doesn't add the `aiohttp_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: + + | Request property | Sampling context key(s) | + | ----------------- | ------------------------------- | + | `path` | `url.path` | + | `query_string` | `url.query` | + | `method` | `http.request.method` | + | `host` | `server.address`, `server.port` | + | `scheme` | `url.scheme` | + | full URL | `url.full` | + | `request.headers` | `http.request.header.{header}` | + + + + The Celery integration doesn't add the `celery_job` dictionary anymore. Instead, the individual keys are now available as: + + | Dictionary keys | Sampling context key | Example | + | ---------------------- | --------------------------- | ------------------------------ | + | `celery_job["args"]` | `celery.job.args.{index}` | `celery.job.args.0` | + | `celery_job["kwargs"]` | `celery.job.kwargs.{kwarg}` | `celery.job.kwargs.kwarg_name` | + | `celery_job["task"]` | `celery.job.task` | | + + + + The Tornado integration doesn't add the `tornado_request` object anymore. Instead, some of the individual properties of the request are accessible, if available, as follows: + + | Request property | Sampling context key(s) | + | ----------------- | --------------------------------------------------- | + | `path` | `url.path` | + | `query` | `url.query` | + | `protocol` | `url.scheme` | + | `method` | `http.request.method` | + | `host` | `server.address`, `server.port` | + | `version` | `network.protocol.name`, `network.protocol.version` | + | full URL | `url.full` | + | `request.headers` | `http.request.header.{header}` | + + + + The WSGI integration doesn't add the `wsgi_environ` object anymore. Instead, the individual properties of the environment are accessible, if available, as follows: + + | Env property | Sampling context key(s) | + | ----------------- | ------------------------------------------------- | + | `PATH_INFO` | `url.path` | + | `QUERY_STRING` | `url.query` | + | `REQUEST_METHOD` | `http.request.method` | + | `SERVER_NAME` | `server.address` | + | `SERVER_PORT` | `server.port` | + | `SERVER_PROTOCOL` | `server.protocol.name`, `server.protocol.version` | + | `wsgi.url_scheme` | `url.scheme` | + | full URL | `url.full` | + | `HTTP_*` | `http.request.header.{header}` | + + + + The ASGI integration doesn't add the `asgi_scope` object anymore. Instead, the individual properties of the scope, if available, are accessible as follows: + + | Scope property | Sampling context key(s) | + | -------------- | ------------------------------- | + | `type` | `network.protocol.name` | + | `scheme` | `url.scheme` | + | `path` | `url.path` | + | `query` | `url.query` | + | `http_version` | `network.protocol.version` | + | `method` | `http.request.method` | + | `server` | `server.address`, `server.port` | + | `client` | `client.address`, `client.port` | + | full URL | `url.full` | + | `headers` | `http.request.header.{header}` | + + + + The RQ integration doesn't add the `rq_job` object anymore. Instead, the individual properties of the job and the queue, if available, are accessible as follows: + + | RQ property | Sampling context key | Example | + | --------------- | ---------------------------- | ---------------------- | + | `rq_job.args` | `rq.job.args.{index}` | `rq.job.args.0` | + | `rq_job.kwargs` | `rq.job.kwargs.{kwarg}` | `rq.job.args.my_kwarg` | + | `rq_job.func` | `rq.job.func` | | + | `queue.name` | `messaging.destination.name` | | + | `rq_job.id` | `messaging.message.id` | | + + Note that `rq.job.args`, `rq.job.kwargs`, and `rq.job.func` are serialized and not the actual objects on the job. + + + + The AWS Lambda integration doesn't add the `aws_event` and `aws_context` objects anymore. Instead, the following, if available, is accessible: + + | AWS property | Sampling context key(s) | + | ------------------------------------------- | ------------------------------- | + | `aws_event["httpMethod"]` | `http.request.method` | + | `aws_event["queryStringParameters"]` | `url.query` | + | `aws_event["path"]` | `url.path` | + | full URL | `url.full` | + | `aws_event["headers"]["X-Forwarded-Proto"]` | `network.protocol.name` | + | `aws_event["headers"]["Host"]` | `server.address` | + | `aws_context["function_name"]` | `faas.name` | + | `aws_event["headers"]` | `http.request.headers.{header}` | + + + + The GCP integration doesn't add the `gcp_env` and `gcp_event` keys anymore. Instead, the following, if available, is accessible: + + | Old sampling context key | New sampling context key | + | --------------------------------- | ------------------------------ | + | `gcp_env["function_name"]` | `faas.name` | + | `gcp_env["function_region"]` | `faas.region` | + | `gcp_env["function_project"]` | `gcp.function.project` | + | `gcp_env["function_identity"]` | `gcp.function.identity` | + | `gcp_env["function_entry_point"]` | `gcp.function.entry_point` | + | `gcp_event.method` | `http.request.method` | + | `gcp_event.query_string` | `url.query` | + | `gcp_event.headers` | `http.request.header.{header}` | + + +The ability to set `custom_sampling_context` on `start_transaction` was removed. If there is custom data that you want to have accessible in the `sampling_context` of a `traces_sampler` or `profiles_sampler`, set it on the span via the `attributes` argument, as all span attributes are now included in the `sampling_context` by default: + +```python diff +- with start_transaction(custom_sampling_context={"custom_attribute": "custom_value"}): ++ with start_span(attributes={"custom_attribute": "custom_value"}) as span: + # custom_attribute will now be accessible in the sampling context + # of your traces_sampler/profiles_sampler + ... +``` + + + +As mentioned above, span attribute keys must be non-empty strings and values can only be several primitive types (excluding `None`) or a list of a single primitive type. See [the OpenTelemetry specification](https://opentelemetry.io/docs/specs/otel/common/#attribute) for details. + + + + +## Errors + +We've updated how we handle `ExceptionGroup`s. You will now get more data if `ExceptionGroup`s appear in chained exceptions. As an indirect consequence, you might notice a change in how issues are grouped in Sentry. + + +## Integrations + +Additional integrations will now be activated automatically if the SDK detects the respective package is installed: Ariadne, ARQ, asyncpg, Chalice, clickhouse-driver, GQL, Graphene, huey, Loguru, PyMongo, Quart, Starlite, Strawberry. You can [opt-out of specific integrations with the `disabled_integrations` option](/platforms/python/integrations/#disabling-integrations). + +We no longer support Django older than 2.0, trytond older than 5.0, and Falcon older than 3.0. + +### Logging + +The logging integration, which implements out-of-the-box support for the Python standard library `logging` framework, doesn't capture error logs as events anymore by default. The original behavior can still be achieved by providing a custom `event_level` to the `LoggingIntegration`: + +```python +sentry_sdk.init( + integrations=[ + # capture error, critical, exception logs + # and send them to Sentry as errors + LoggingIntegration(event_level="ERROR"), + ], +) +``` + +### clickhouse-driver + +The query being executed is now available under the `db.query.text` span attribute (only if `send_default_pii` is `True`). + +### PyMongo + +The PyMongo integration no longer sets tags automatically. The data is still accessible via span attributes. + +The PyMongo integration doesn't set `operation_ids` anymore. The individual IDs (`operation_id`, `request_id`, `session_id`) are now accessible as separate span attributes. + +### Redis + +In Redis pipeline spans, there is no `span["data"]["redis.commands"]` that contains a dictionary `{"count": 3, "first_ten": ["cmd1", "cmd2", ...]}`. Instead, there is `span["data"]["redis.commands.count"]` (containing `3`) and `span["data"]["redis.commands.first_ten"]` (containing `["cmd1", "cmd2", ...]`). + + +## Measurements + +The `set_measurement()` API was removed. You can set custom attributes on the span instead with `set_attribute()`. + + +## Sessions + +The `auto_session_tracking()` context manager was removed. Use `track_session()` instead. + + +## Scope + +Setting `Scope.user` directly is no longer supported. Use `Scope.set_user()` instead. + + +## Metrics + +The `sentry_sdk.metrics` API doesn't exist anymore in SDK `3.x` as the [metrics beta has come to an end](https://sentry.zendesk.com/hc/en-us/articles/26369339769883-Metrics-Beta-Coming-to-an-End). The associated experimental options `enable_metrics`, `before_emit_metric` and `metric_code_locations` have been removed as well. + + +## Internals + +There is no concept of a hub anymore and all APIs and attributes that were connected to hubs have been removed.