diff --git a/docs/platforms/python/configuration/filtering/index.mdx b/docs/platforms/python/configuration/filtering/index.mdx index 2b0fb75373c637..614ecc574d79d2 100644 --- a/docs/platforms/python/configuration/filtering/index.mdx +++ b/docs/platforms/python/configuration/filtering/index.mdx @@ -68,8 +68,7 @@ When the SDK creates an event or breadcrumb for transmission, that transmission Hints are available in two places: -1. / - +1. / 2. `eventProcessors` Event and breadcrumb `hints` are objects containing various information used to put together an event or a breadcrumb. Typically `hints` hold the original exception so that additional data can be extracted or grouping can be affected. @@ -102,97 +101,24 @@ When a string or a non-error object is raised, Sentry creates a synthetic except -## Filtering Transactions and Spans +## Filtering Transaction Events -To prevent certain transactions (or spans in stream mode) from being reported to Sentry, use the , (for transaction mode), or (for stream mode) configuration options. These allow you to provide a function to evaluate the current transaction or span and drop it if it's not one you want. - -You can also use (for transaction mode) or (for stream mode) to modify transaction/span data. +To prevent certain transactions from being reported to Sentry, use the or configuration option, which allows you to provide a function to evaluate the current transaction and drop it if it's not one you want. ### Using -**Note:** The and config options are mutually exclusive. If you define a to filter out certain transactions/service spans, you must also handle the case of non-filtered transactions/service spans by returning the rate at which you'd like them sampled. +**Note:** The and config options are mutually exclusive. If you define a to filter out certain transactions, you must also handle the case of non-filtered transactions by returning the rate at which you'd like them sampled. -In its simplest form, used just for filtering the transaction/service span, it looks like this: +In its simplest form, used just for filtering the transaction, it looks like this: -It also allows you to sample different transactions/service spans at different rates. +It also allows you to sample different transactions at different rates. -If the transaction or span currently being processed has a parent (from an upstream service calling this service), the parent's (upstream) sampling decision will always be included in the sampling context data, so that your can choose whether and when to inherit that decision. In most cases, inheritance is the right choice, to avoid breaking distributed traces. A broken trace will not include all your services. +If the transaction currently being processed has a parent transaction (from an upstream service calling this service), the parent (upstream) sampling decision will always be included in the sampling context data, so that your can choose whether and when to inherit that decision. In most cases, inheritance is the right choice, to avoid breaking distributed traces. A broken trace will not include all your services. See Inheriting the parent sampling decision to learn more. -Learn more about configuring sampling. +Learn more about configuring the sample rate. ### Using - - -Not available in stream mode. Use instead. - - - - -### Using - - - -Only available in stream mode. - - - -Use the configuration option, which lets you provide a function to modify a span. -This function is called for the service span and all child spans. - -If you want to drop the service span, including its child spans, use [`ignore_spans`](#using-ignore-spans). - - - -See `before_send_span` for details. - -### Using - - - -Only available in stream mode. - - - -You can use the option to filter out spans that match a certain pattern by providing a list of strings, compiled regular expressions, or dictionaries. When using strings, a span name containing the string will be filtered out. For exact or more complex pattern matching, use compiled regular expressions instead. - -You can also provide a dictionary with `name` and/or `attributes` keys to match on multiple conditions. At least one key must be provided. - -```python -import re -import sentry_sdk - -sentry_sdk.init( - _experiments={ - "trace_lifecycle": "stream", - "ignore_spans": [ - # String match against span name - "/health", - - # Regex match against span name - re.compile(r"/flow/.*"), - - # Match by attributes (all must match) - { - "attributes": { - "service.id": "15def9a", - "flow.pipeline": "legacy", - } - }, - - # Match by name and attributes - { - "name": re.compile(r"/flow/.*"), - "attributes": { - "service.id": re.compile(r".*\.facade"), - }, - }, - ], - }, -) -``` - -See `ignore_spans` for details. diff --git a/docs/platforms/python/configuration/options.mdx b/docs/platforms/python/configuration/options.mdx index b6fd92e76a8c9a..4f1b705e256662 100644 --- a/docs/platforms/python/configuration/options.mdx +++ b/docs/platforms/python/configuration/options.mdx @@ -252,45 +252,6 @@ This function is called with a transaction event object, and can return a modifi - - - - -Only available in stream mode. - - - -This function is called with a span event object and can return a modified span object. Use it, for example, to manually strip PII from spans or filter data from spans before they're sent to Sentry. - -Note that `before_send_span` can only modify span data, meaning you cannot use it to drop spans. Use [`ignore_spans`](#_experiments.ignore_spans) to drop spans. - - - -```python {tabTitle:Stream Mode} -import sentry_sdk - -def before_send_span(span, hint): - # Runs once per span, as it's flushed. - # Only attributes set at span-creation time are available here. - span["attributes"].update({ - "component_version": "2.0.0", - "deployment_stage": "production", - }) - return span - -sentry_sdk.init( - # ... - _experiments={ - "trace_lifecycle": "stream", - "before_send_span": before_send_span, - }, -) -``` - - - - - This function is called with a breadcrumb object before the breadcrumb is added to the scope. When nothing is returned from the function, the breadcrumb is dropped. To pass the breadcrumb through, return the first argument, which contains the breadcrumb object. @@ -395,7 +356,7 @@ sentry_sdk.init( -A number between `0` and `1`, controlling the percentage chance a given transaction (or service span in stream mode) will be sent to Sentry. (`0` represents 0% while `1` represents 100%.) Applies equally to all transactions and spans created in the app. Either this or `traces_sampler` must be defined to enable tracing. +A number between `0` and `1`, controlling the percentage chance a given transaction will be sent to Sentry. (`0` represents 0% while `1` represents 100%.) Applies equally to all transactions created in the app. Either this or `traces_sampler` must be defined to enable tracing. If `traces_sample_rate` is `0`, this means that no new traces will be created. However, if you have another service (for example a JS frontend) that makes requests to your service that include trace information, those traces will be continued and thus transactions will be sent to Sentry. @@ -405,70 +366,6 @@ If you want to disable all tracing you need to set `traces_sample_rate=None`. In - - -Controls how spans are sent to Sentry: - -- In transaction mode (`"static"`, the default), all spans are collected in memory and sent to Sentry as a single transaction once the root span ends. -- In stream mode (`"stream"`), spans are sent in batches as they finish. - - - - - - - -Only available in stream mode. - - - -A list of strings, compiled regular expressions, or dictionaries describing spans that shouldn't be sent to Sentry. When using strings, a span name containing the string will be filtered out. For exact or more complex pattern matching, use compiled regular expressions instead. You can also provide a dictionary with `name` and/or `attributes` keys to match on multiple conditions. At least one key must be provided. - -If the dropped span is a service span, its children are dropped too. If it's a child span, only that span is dropped and its children are reparented to the nearest ancestor. -By default, no spans are ignored. - -`ignore_spans` is evaluated at span start, so only the span name and attributes available at that point are taken into account. Any name updates or additional attributes added while the span is active won't influence whether the span is dropped. - - - -```python -import re -import sentry_sdk - -sentry_sdk.init( - _experiments={ - "trace_lifecycle": "stream", - "ignore_spans": [ - # String match against span name - "/health", - - # Regex match against span name - re.compile(r"/flow/.*"), - - # Match by attributes (all must match) - { - "attributes": { - "service.id": "15def9a", - "flow.pipeline": "legacy", - } - }, - - # Match by name and attributes - { - "name": re.compile(r"/flow/.*"), - "attributes": { - "service.id": re.compile(r".*\.facade"), - }, - }, - ], - }, -) -``` - - - - - An optional property that controls which downstream services receive tracing data, in the form of a `sentry-trace` and a `baggage` header attached to any outgoing HTTP requests. diff --git a/docs/platforms/python/data-management/sensitive-data/index.mdx b/docs/platforms/python/data-management/sensitive-data/index.mdx index 81f614dee1f203..a167fca104a693 100644 --- a/docs/platforms/python/data-management/sensitive-data/index.mdx +++ b/docs/platforms/python/data-management/sensitive-data/index.mdx @@ -45,9 +45,9 @@ You can use the configuration param -### , , & +### & -The SDK provides a hook, which is invoked before an error or message event is sent and can be used to modify event data to remove sensitive information. The SDK also provides a hook, that does the same thing for transactions, and a hook, that does the same thing for spans in stream mode. We recommend using these hooks in the SDK to **scrub any data before it is sent**, to ensure that sensitive data never leaves the local environment. +The SDK provides a hook, which is invoked before an error or message event is sent and can be used to modify event data to remove sensitive information. The SDK also provide a hook which does the same thing for transactions. We recommend using and in the SDK to **scrub any data before it is sent**, to ensure that sensitive data never leaves the local environment. diff --git a/docs/platforms/python/profiling/index.mdx b/docs/platforms/python/profiling/index.mdx index 9b210b4b0e244d..fe8621adb8686e 100644 --- a/docs/platforms/python/profiling/index.mdx +++ b/docs/platforms/python/profiling/index.mdx @@ -16,10 +16,7 @@ With [profiling](/product/profiling/), Sentry tracks your software's performance Continuous profiling is available starting in SDK version `2.24.1`. - - Transaction-based profiling - -is available starting in SDK version `1.18.0`. +Transaction-based profiling is available starting in SDK version `1.18.0`. @@ -95,7 +92,6 @@ Continuous profiling was experimental in SDK versions prior to `2.24.1` and will Transaction-based profiling is available starting in SDK version `1.18.0`. -It's not available in stream mode. Use continuous profiling instead. diff --git a/docs/platforms/python/sampling.mdx b/docs/platforms/python/sampling.mdx index 3e8a5df7683ff5..6bbece4c0d7d91 100644 --- a/docs/platforms/python/sampling.mdx +++ b/docs/platforms/python/sampling.mdx @@ -42,12 +42,6 @@ You can define at most one of the an ## Sampling Transaction Events - - -If you're using stream mode, refer to our Configure Sampling guide for setup instructions. - - - We recommend sampling your transactions for two reasons: 1. Capturing a single trace involves minimal overhead, but capturing traces for _every_ page load or _every_ API request may add an undesirable load to your system. @@ -64,8 +58,8 @@ The Sentry SDKs have two configuration options to control the volume of transact - Uses default [inheritance](#inheritance) and [precedence](#precedence) behavior 2. Sampling function () which: - Samples different transactions at different rates - - Filters out - some transactions entirely + - Filters out some + transactions entirely - Modifies default [precedence](#precedence) and [inheritance](#inheritance) behavior By default, none of these options are set, meaning no transactions will be sent to Sentry. You must set either one of the options to start sending transactions. @@ -111,6 +105,7 @@ sentry_sdk.start_transaction( ) ``` + ## Inheritance Whatever a transaction's sampling decision, that decision will be passed to its child spans and from there to any transactions they subsequently cause in other services. diff --git a/docs/platforms/python/tracing/configure-sampling/index.mdx b/docs/platforms/python/tracing/configure-sampling/index.mdx index 60cc21b899c0f6..187534743633ed 100644 --- a/docs/platforms/python/tracing/configure-sampling/index.mdx +++ b/docs/platforms/python/tracing/configure-sampling/index.mdx @@ -8,29 +8,23 @@ If you find that Sentry's tracing functionality is generating too much data, for Effective sampling is key to getting the most value from Sentry's performance monitoring while minimizing overhead. The Python SDK provides two ways to control the sampling rate. You can review the options and [examples](#traces-sampler-examples) below. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Sampling Configuration Options ### 1. Uniform Sample Rate (`traces_sample_rate`) -`traces_sample_rate` is a floating-point value between `0.0` and `1.0`, inclusive, which controls the probability with which each transaction (or service spans in stream mode) will be sampled. This works the same way in both transaction mode and stream mode: +`traces_sample_rate` is a floating-point value between `0.0` and `1.0`, inclusive, which controls the probability with which each transaction will be sampled: -With `traces_sample_rate` set to `0.25`, each transaction/service span in your application is randomly sampled with a probability of `0.25`, so you can expect that one in every four transactions/service spans will be sent to Sentry. +With `traces_sample_rate` set to `0.25`, each transaction in your application is randomly sampled with a probability of `0.25`, so you can expect that one in every four transactions will be sent to Sentry. ### 2. Sampling Function (`traces_sampler`) For more granular control, you can provide a `traces_sampler` function. This approach allows you to: -- Apply different sampling rates to different types of transactions/service spans -- Filter out specific transactions/service spans entirely -- Make sampling decisions based on transaction/service span data +- Apply different sampling rates to different types of transactions +- Filter out specific transactions entirely +- Make sampling decisions based on transaction data - Control the inheritance of sampling decisions in distributed traces - Use custom attributes to modify sampling @@ -44,11 +38,14 @@ In distributed systems, implementing inheritance logic when trace information is - +
+Traces Sampler Examples + +#### Traces Sampler Examples 1. Prioritizing Critical User Flows -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk from sentry_sdk.types import SamplingContext @@ -76,49 +73,14 @@ def traces_sampler(sampling_context: SamplingContext) -> float: return 0.1 sentry_sdk.init( - # ... - traces_sampler=traces_sampler, -) -``` - -```python {tabTitle:Stream Mode} -import sentry_sdk -from sentry_sdk.types import SamplingContext - -def traces_sampler(sampling_context: SamplingContext) -> float: - # Use the parent sampling decision if we have an incoming trace. - # Note: we strongly recommend respecting the parent sampling decision, - # as this ensures your traces will be complete! - parent_sampling_decision = sampling_context["span_context"]["parent_sampled"] - if parent_sampling_decision is not None: - return float(parent_sampling_decision) - - span_ctx = sampling_context["span_context"] - name = span_ctx["name"] - op = span_ctx["attributes"].get("sentry.op") - - # Sample all checkout spans - if name and ('/checkout' in name or op == 'checkout'): - return 1.0 - - # Sample 50% of login spans - if name and ('/login' in name or op == 'login'): - return 0.5 - - # Sample 10% of everything else - return 0.1 - -sentry_sdk.init( - # ... + dsn="your-dsn", traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, ) ``` 2. Handling Different Environments and Error Rates -```python {tabTitle:Transaction Mode (Default)} -import os +```python import sentry_sdk from sentry_sdk.types import SamplingContext @@ -130,7 +92,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: if parent_sampling_decision is not None: return float(parent_sampling_decision) - custom_sampling_ctx = sampling_context.get("custom_sampling_context") or {} + custom_sampling_ctx = sampling_context["custom_sampling_context"] environment = os.environ.get("ENVIRONMENT", "development") # Sample all transactions in development @@ -153,7 +115,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: # Initialize the SDK with the sampling function sentry_sdk.init( - # ... + dsn="your-dsn", traces_sampler=traces_sampler, ) @@ -168,61 +130,9 @@ with sentry_sdk.start_transaction( # your code here ``` -```python {tabTitle:Stream Mode} -import os -import sentry_sdk -from sentry_sdk.types import SamplingContext - -def traces_sampler(sampling_context: SamplingContext) -> float: - # Use the parent sampling decision if we have an incoming trace. - # Note: we strongly recommend respecting the parent sampling decision, - # as this ensures your traces will be complete! - parent_sampling_decision = sampling_context["span_context"]["parent_sampled"] - if parent_sampling_decision is not None: - return float(parent_sampling_decision) - - environment = os.environ.get("ENVIRONMENT", "development") - - # Sample all spans in development - if environment == "development": - return 1.0 - - # Sample more spans if there are recent errors - # Note: hasRecentErrors is a custom attribute that needs to be set - if sampling_context.get("hasRecentErrors") is True: - return 0.8 - - # Sample based on environment - if environment == "production": - return 0.05 # 5% in production - elif environment == "staging": - return 0.2 # 20% in staging - - # Default sampling rate - return 0.1 - -# Initialize the SDK with the sampling function -sentry_sdk.init( - # ... - traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, -) - -# Custom attributes need to be set on the scope, after continue_trace -# (which resets the propagation context) and before start_span -# (which is when sampling happens), in order to be available -# in the traces_sampler -sentry_sdk.Scope.set_custom_sampling_context({"hasRecentErrors": True}) -with sentry_sdk.traces.start_span( - name="GET /api/users", - attributes={"sentry.op": "http.request"}, -) as span: - # your code here -``` - 3. Controlling Sampling Based on User and Transaction Properties -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk from sentry_sdk.types import SamplingContext @@ -234,7 +144,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: if parent_sampling_decision is not None: return float(parent_sampling_decision) - custom_sampling_ctx = sampling_context.get("custom_sampling_context") or {} + custom_sampling_ctx = sampling_context["custom_sampling_context"] # Always sample for premium users # Note: user.tier is a custom attribute that needs to be set @@ -256,7 +166,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: # Initialize the SDK with the sampling function sentry_sdk.init( - # ... + dsn="your-dsn", traces_sampler=traces_sampler, ) @@ -269,57 +179,9 @@ with sentry_sdk.start_transaction( # Your code here ``` -```python {tabTitle:Stream Mode} -import sentry_sdk -from sentry_sdk.types import SamplingContext - -def traces_sampler(sampling_context: SamplingContext) -> float: - # Use the parent sampling decision if we have an incoming trace. - # Note: we strongly recommend respecting the parent sampling decision, - # as this ensures your traces will be complete! - parent_sampling_decision = sampling_context["span_context"]["parent_sampled"] - if parent_sampling_decision is not None: - return float(parent_sampling_decision) - - # Always sample for premium users - # Note: user.tier is a custom attribute that needs to be set - if sampling_context.get("user", {}).get("tier") == "premium": - return 1.0 - - # Sample more spans for users experiencing errors - # Note: hasRecentErrors is a custom attribute - if sampling_context.get("hasRecentErrors") is True: - return 0.8 - - # Sample less for high-volume, low-value paths - name = sampling_context["span_context"]["name"] - if name and name.startswith("/api/metrics"): - return 0.01 - - # Default sampling rate - return 0.2 - -# Initialize the SDK with the sampling function -sentry_sdk.init( - # ... - traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, -) - -# To set custom attributes for this example: -sentry_sdk.Scope.set_custom_sampling_context( - {"user": {"tier": "premium"}, "hasRecentErrors": True} -) -with sentry_sdk.traces.start_span( - name="GET /api/users", - attributes={"sentry.op": "http.request"}, -) as span: - # Your code here -``` - 4. Complex Business Logic Sampling -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk from sentry_sdk.types import SamplingContext @@ -337,7 +199,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: if transaction_ctx["op"] in ["payment.process", "order.create", "user.verify"]: return 1.0 - custom_sampling_context = sampling_context.get("custom_sampling_context") or {} + custom_sampling_context = sampling_context["custom_sampling_context"] # Sample based on user segment # Note: user.segment is a custom attribute @@ -364,7 +226,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: # Initialize the SDK with the sampling function sentry_sdk.init( - # ... + dsn="your-dsn", traces_sampler=traces_sampler, ) @@ -375,70 +237,12 @@ with sentry_sdk.start_transaction( custom_sampling_context={"user": {"segment": "enterprise"}, "transaction": {"value": 1500}, "service": {"error_rate": 0.03}}, ) as transaction: # Your code here -``` - -```python {tabTitle:Stream Mode} -import sentry_sdk -from sentry_sdk.types import SamplingContext - -def traces_sampler(sampling_context: SamplingContext) -> float: - # Use the parent sampling decision if we have an incoming trace. - # Note: we strongly recommend respecting the parent sampling decision, - # as this ensures your traces will be complete! - parent_sampling_decision = sampling_context["span_context"]["parent_sampled"] - if parent_sampling_decision is not None: - return float(parent_sampling_decision) - - # Always sample critical business operations - # Note: sentry.op is an SDK-provided attribute - span_ctx = sampling_context["span_context"] - if span_ctx["attributes"].get("sentry.op") in ["payment.process", "order.create", "user.verify"]: - return 1.0 - - # Sample based on user segment - # Note: user.segment is a custom attribute - user_segment = sampling_context.get("user", {}).get("segment") - if user_segment == "enterprise": - return 0.8 - elif user_segment == "premium": - return 0.5 - - # Sample based on transaction value - # Note: transaction.value is a custom attribute - transaction_value = sampling_context.get("transaction", {}).get("value") - if transaction_value is not None and transaction_value > 1000: # High-value transactions - return 0.7 - - # Sample based on error rate in the service - # Note: service.error_rate is a custom attribute - error_rate = sampling_context.get("service", {}).get("error_rate") - if error_rate is not None and error_rate > 0.05: # Error rate above 5% - return 0.9 - - # Default sampling rate - return 0.1 -# Initialize the SDK with the sampling function -sentry_sdk.init( - # ... - traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, -) - -# To set custom attributes for this example: -sentry_sdk.Scope.set_custom_sampling_context( - {"user": {"segment": "enterprise"}, "transaction": {"value": 1500}, "service": {"error_rate": 0.03}} -) -with sentry_sdk.traces.start_span( - name="Process Payment", - attributes={"sentry.op": "payment.process"}, -) as span: - # Your code here ``` 5. Performance-Based Sampling -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk from sentry_sdk.types import SamplingContext @@ -450,7 +254,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: if parent_sampling_decision is not None: return float(parent_sampling_decision) - custom_sampling_ctx = sampling_context.get("custom_sampling_context") or {} + custom_sampling_ctx = sampling_context["custom_sampling_context"] # Sample more transactions with high memory usage # Note: memory_usage_mb is a custom attribute @@ -475,7 +279,7 @@ def traces_sampler(sampling_context: SamplingContext) -> float: # Initialize the SDK with the sampling function sentry_sdk.init( - # ... + dsn="your-dsn", traces_sampler=traces_sampler, ) @@ -487,66 +291,12 @@ with sentry_sdk.start_transaction( ) as transaction: # Your code here ``` - -```python {tabTitle:Stream Mode} -import sentry_sdk -from sentry_sdk.types import SamplingContext - -def traces_sampler(sampling_context: SamplingContext) -> float: - # Use the parent sampling decision if we have an incoming trace. - # Note: we strongly recommend respecting the parent sampling decision, - # as this ensures your traces will be complete! - parent_sampling_decision = sampling_context["span_context"]["parent_sampled"] - if parent_sampling_decision is not None: - return float(parent_sampling_decision) - - # Sample more spans with high memory usage - # Note: memory_usage_mb is a custom attribute - memory_usage = sampling_context.get("memory_usage_mb") - if memory_usage is not None and memory_usage > 500: - return 0.8 - - # Sample more spans with high CPU usage - # Note: cpu_percent is a custom attribute - cpu_percent = sampling_context.get("cpu_percent") - if cpu_percent is not None and cpu_percent > 80: - return 0.8 - - # Sample more spans with high database load - # Note: db_connections is a custom attribute - db_connections = sampling_context.get("db_connections") - if db_connections is not None and db_connections > 100: - return 0.7 - - # Default sampling rate - return 0.1 - -# Initialize the SDK with the sampling function -sentry_sdk.init( - # ... - traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, -) - -# To set custom attributes for this example: -sentry_sdk.Scope.set_custom_sampling_context( - {"memory_usage_mb": 600, "cpu_percent": 85, "db_connections": 120} -) -with sentry_sdk.traces.start_span( - name="Process Data", - attributes={"sentry.op": "data.process"}, -) as span: - # Your code here -``` - - +
## The Sampling Context Object When the `traces_sampler` function is called, the Sentry SDK passes a `sampling_context` object with information from the relevant span to help make sampling decisions: -### Transaction Mode (Default) - ```python { "transaction_context": { @@ -560,76 +310,28 @@ When the `traces_sampler` function is called, the Sentry SDK passes a `sampling_ } ``` -### Stream Mode - -```python -{ - "span_context": { - "name": str, # span title at creation time (SDK-provided) - "trace_id": str, # the trace ID (SDK-provided) - "parent_span_id": Optional[str], # the parent span's ID, if any (SDK-provided) - "parent_sampled": Optional[bool], # whether the parent span was sampled (SDK-provided) - "parent_sample_rate": Optional[float], # the sample rate used by the parent (SDK-provided) - "attributes": dict[str, Any] # attributes set at span-creation time (SDK- and user-provided) - }, - ... # additional custom key-value pairs for sampling set via custom sampling context -} -``` - ### SDK-Provided vs. Custom Attributes The sampling context contains both SDK-provided attributes and custom attributes: -#### Transaction Mode (Default) - **SDK-Provided Attributes:** - - `transaction_context.name`: The name of the transaction - `transaction_context.op`: The operation type - `parent_sampled`: Whether the parent transaction was sampled - `parent_sample_rate`: The sample rate used by the parent **Custom Attributes:** - - Any data you add to the `custom_sampling_context` parameter in `start_transaction`. Use this for data that you want to use for sampling decisions but don't want to include in the transaction data that gets sent to Sentry. Read more about sampling context [here](/platforms/python/sampling/#sampling-context). -#### Stream Mode - -**SDK-Provided Attributes:** - -- `span_context.name`: The name of the span -- `span_context.attributes`: Attributes set on the span at creation time, including `sentry.op` -- `span_context.parent_sampled`: Whether the parent span was sampled -- `span_context.parent_sample_rate`: The sample rate used by the parent - -**Custom Attributes:** - -- Any data you add via `sentry_sdk.Scope.set_custom_sampling_context()`, called after `continue_trace` and before `start_span`. Use this for data that you want to use for sampling decisions but don't want to include in the span data that gets sent to Sentry. Read more about sampling context [here](/platforms/python/configuration/sampling/#sampling-context). - ## Sampling Decision Precedence When multiple sampling mechanisms could apply, Sentry follows this order of precedence: -### Transaction Mode (Default) - -1. If a sampling decision is passed to `start_transaction`, that decision is used. -2. If `traces_sampler` is defined, its decision is used. Although the `traces_sampler` can override the parent sampling decision, most users will want to ensure their `traces_sampler` respects the parent sampling decision. -3. If no `traces_sampler` is defined, but there is a parent sampling decision from an incoming distributed trace, we use the parent sampling decision. -4. If neither of the above, `traces_sample_rate` is used. -5. If none of the above are set, no transactions are sampled. This is equivalent to setting `traces_sample_rate=0.0`. - -### Stream Mode - -1. If `traces_sampler` is defined, its decision is used. Although the `traces_sampler` can override the parent sampling decision, most users will want to ensure their `traces_sampler` respects the parent sampling decision. -2. If no `traces_sampler` is defined, but there is a parent sampling decision from an incoming distributed trace, we use the parent sampling decision. -3. If neither of the above, `traces_sample_rate` is used -4. If none of the above are set, no spans are sampled. This is equivalent to setting `traces_sample_rate=0.0`. - - - -Sampling decisions are made when spans are created. Child spans inherit the sampling decision of their parent span unless filtered by `ignore_spans`. - - +1. If a sampling decision is passed to `start_transaction`, that decision is used +2. If `traces_sampler` is defined, its decision is used. Although the `traces_sampler` can override the parent sampling decision, most users will want to ensure their `traces_sampler` respects the parent sampling decision +3. If no `traces_sampler` is defined, but there is a parent sampling decision from an incoming distributed trace, we use the parent sampling decision +4. If neither of the above, `traces_sample_rate` is used +5. If none of the above are set, no transactions are sampled. This is equivalent to setting `traces_sample_rate=0.0` ## How Sampling Propagates in Distributed Traces @@ -639,8 +341,7 @@ Sentry uses a "head-based" sampling approach: - This decision is propagated to all downstream services The two key headers are: - - `sentry-trace`: Contains trace ID, span ID, and sampling decision - `baggage`: Contains additional trace metadata including sample rate -The Sentry Python SDK automatically attaches these headers to outgoing HTTP requests when using auto-instrumentation with libraries like `requests`, `urllib3`, or `httpx`. For other communication channels, you can manually propagate trace information. Learn more about customizing tracing in [custom trace propagation](/platforms/python/tracing/distributed-tracing/custom-trace-propagation/). +The Sentry Python SDK automatically attaches these headers to outgoing HTTP requests when using auto-instrumentation with libraries like `requests`, `urllib3`, or `httpx`. For other communication channels, you can manually propagate trace information. Learn more about customizing tracing in [custom trace propagation](/platforms/python/tracing/distributed-tracing/custom-trace-propagation/) diff --git a/docs/platforms/python/tracing/instrumentation/automatic-instrumentation.mdx b/docs/platforms/python/tracing/instrumentation/automatic-instrumentation.mdx index 87aa8c71e319a0..fc44a217d9a721 100644 --- a/docs/platforms/python/tracing/instrumentation/automatic-instrumentation.mdx +++ b/docs/platforms/python/tracing/instrumentation/automatic-instrumentation.mdx @@ -17,7 +17,7 @@ supported: description: "Learn what instrumentation automatically captures transactions." --- -Many integrations for popular frameworks automatically capture transactions (or service spans in stream mode). If you already have any of the following frameworks set up for Sentry error reporting, you will start to see traces immediately: +Many integrations for popular frameworks automatically capture transactions. If you already have any of the following frameworks set up for Sentry error reporting, you will start to see traces immediately: - All WSGI-based web frameworks (Django, Flask, Pyramid, Falcon, Bottle) - Celery @@ -26,13 +26,11 @@ Many integrations for popular frameworks automatically capture transactions (or See the full [list of available integrations](/platforms/python/integrations/). -Spans are instrumented for the following operations within a transaction/service span: +Spans are instrumented for the following operations within a transaction: - Database queries that use SQLAlchemy or the Django ORM - HTTP requests made with HTTPX, requests, the stdlib, AIOHTTP, or pyreqwest - Spawned subprocesses - Redis operations -In transaction mode, spans are only created within an existing transaction. If you're not using any of the supported frameworks, you'll need to create transactions manually. - -Stream mode removes this limitation. Since there are no transactions, any span started without a parent is automatically promoted to a service span (the equivalent of a transaction). You can also force any span to become a service span when starting it by setting its parent to `None`. See Custom Instrumentation to learn more. +Spans are only created within an existing transaction. If you're not using any of the supported frameworks, you'll need to create transactions manually. diff --git a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.mdx b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.mdx index 9c8f72b282ebf7..c5528fefe9daff 100644 --- a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.mdx +++ b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module.mdx @@ -10,12 +10,6 @@ With Sentry AI Agent Monitoringset up tracing. Once this is done, the Python SDK will automatically instrument AI agents created with supported libraries. If that doesn't fit your use case, you can use custom instrumentation described below. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Automatic Instrumentation The Python SDK supports automatic instrumentation for some AI libraries. We recommend adding their integrations to your Sentry configuration to automatically capture spans for AI agents. @@ -23,9 +17,7 @@ The Python SDK supports automatic instrumentation for some AI libraries. We reco - Anthropic - Google Gen AI - OpenAI -- - OpenAI Agents SDK - +- OpenAI Agents SDK - LangChain - LangGraph - LiteLLM @@ -35,8 +27,7 @@ The Python SDK supports automatic instrumentation for some AI libraries. We reco For your AI agents data to show up in the [AI Agents Dashboards](https://sentry.io/orgredirect/organizations/:orgslug/dashboards/?filter=onlyPrebuilt&query=agents&sort=mostPopular), at least one of the AI spans needs to be created and have well-defined names and data attributes. See below. -In transaction mode, you can also use the [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator to create these spans, using its `template` parameter. -In stream mode, these spans need to be created directly with `sentry_sdk.traces.start_span()`, as shown below. +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create these spans. ## Span Hierarchy @@ -64,7 +55,7 @@ This span represents a request to an LLM model or service that generates a respo #### Example AI Request span -```python {tabTitle:Transaction Mode (Default)} +```python import json import sentry_sdk @@ -87,41 +78,11 @@ with sentry_sdk.start_span(op="gen_ai.chat", name="chat o3-mini") as span: span.set_data("gen_ai.usage.output_tokens", result.usage.completion_tokens) ``` -```python {tabTitle:Stream Mode} -import json -import sentry_sdk - -messages = [{"role": "user", "parts": [{"type": "text", "content": "Tell me a joke"}]}] - -with sentry_sdk.traces.start_span( - name="chat o3-mini", - attributes={"sentry.op": "gen_ai.chat"}, -) as span: - span.set_attributes({ - "gen_ai.operation.name": "chat", - "gen_ai.request.model": "o3-mini", - "gen_ai.provider.name": "openai", - "gen_ai.input.messages": json.dumps(messages), - }) - - result = client.chat.completions.create(model="o3-mini", messages=messages) - - span.set_attributes({ - "gen_ai.response.model": result.model, - "gen_ai.output.messages": json.dumps([ - {"role": "assistant", "parts": [{"type": "text", "content": result.choices[0].message.content}]} - ]), - "gen_ai.response.finish_reasons": json.dumps([result.choices[0].finish_reason]), - "gen_ai.usage.input_tokens": result.usage.prompt_tokens, - "gen_ai.usage.output_tokens": result.usage.completion_tokens, - }) -``` - #### Thinking / reasoning messages Models with extended thinking (such as Anthropic's `thinking` blocks, Gemini's `thought`, or DeepSeek's `reasoning_content`) produce internal reasoning that isn't part of the user-visible reply. Represent this content as a `reasoning` part inside the assistant message, alongside the user-facing `text` part. Sentry surfaces reasoning parts separately and filters them out of the user-facing Conversations view, so don't fold thinking into a `text` part. -```python {tabTitle:Transaction Mode (Default)} +```python import json import sentry_sdk @@ -150,45 +111,6 @@ with sentry_sdk.start_span(op="gen_ai.chat", name="chat o3-mini") as span: span.set_data("gen_ai.usage.output_tokens.reasoning", result.usage.completion_tokens_details.reasoning_tokens) ``` -```python {tabTitle:Stream Mode} -import json -import sentry_sdk - -messages = [{"role": "user", "parts": [{"type": "text", "content": "What is 6 * 7?"}]}] - -with sentry_sdk.traces.start_span( - name="chat o3-mini", - attributes={"sentry.op": "gen_ai.chat"}, -) as span: - span.set_attributes({ - "gen_ai.operation.name": "chat", - "gen_ai.request.model": "o3-mini", - "gen_ai.provider.name": "openai", - "gen_ai.input.messages": json.dumps(messages), - }) - - result = client.chat.completions.create(model="o3-mini", messages=messages) - - span.set_attributes({ - "gen_ai.response.model": result.model, - "gen_ai.output.messages": json.dumps([ - { - "role": "assistant", - "parts": [ - {"type": "reasoning", "content": "6 times 7 is 42."}, - {"type": "text", "content": "The answer is 42."}, - ], - } - ]), - "gen_ai.usage.output_tokens": result.usage.completion_tokens, - }) - # If the provider reports reasoning tokens, record them as a subset of output tokens - span.set_attribute( - "gen_ai.usage.output_tokens.reasoning", - result.usage.completion_tokens_details.reasoning_tokens, - ) -``` - When previous thinking is fed back into a multi-turn request, include the same `reasoning` parts in the assistant messages within `gen_ai.input.messages`. ### Invoke Agent Span @@ -207,7 +129,7 @@ For a complete guide on naming agents across all supported frameworks, see [Nami #### Example of an Invoke Agent Span: -```python {tabTitle:Transaction Mode (Default)} +```python import json import sentry_sdk @@ -225,31 +147,6 @@ with sentry_sdk.start_span(op="gen_ai.invoke_agent", name="invoke_agent Weather span.set_data("gen_ai.usage.output_tokens", final_output.usage.output_tokens) ``` -```python {tabTitle:Stream Mode} -import json -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="invoke_agent Weather Agent", - attributes={"sentry.op": "gen_ai.invoke_agent"}, -) as span: - span.set_attributes({ - "gen_ai.operation.name": "invoke_agent", - "gen_ai.request.model": "o3-mini", - "gen_ai.agent.name": "Weather Agent", - }) - - final_output = my_agent.run() - - span.set_attributes({ - "gen_ai.output.messages": json.dumps([ - {"role": "assistant", "parts": [{"type": "text", "content": str(final_output)}]} - ]), - "gen_ai.usage.input_tokens": final_output.usage.input_tokens, - "gen_ai.usage.output_tokens": final_output.usage.output_tokens, - }) -``` - ### Execute Tool Span This span represents the execution of a tool or function that was requested by an AI model, including the input arguments and resulting output. @@ -260,7 +157,7 @@ This span represents the execution of a tool or function that was requested by a #### Example Execute Tool span -```python {tabTitle:Transaction Mode (Default)} +```python import json import sentry_sdk @@ -274,30 +171,10 @@ with sentry_sdk.start_span(op="gen_ai.execute_tool", name="execute_tool get_weat span.set_data("gen_ai.tool.call.result", json.dumps(result)) ``` -```python {tabTitle:Stream Mode} -import json -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="execute_tool get_weather", - attributes={"sentry.op": "gen_ai.execute_tool"}, -) as span: - span.set_attributes({ - "gen_ai.operation.name": "execute_tool", - "gen_ai.tool.name": "get_weather", - "gen_ai.tool.call.arguments": json.dumps({"location": "Paris"}), - }) - - result = get_weather(location="Paris") - - span.set_attribute("gen_ai.tool.call.result", json.dumps(result)) -``` - ## Tracking Conversations - Tracking Conversations has **beta** stability. Configuration options and - behavior may change. + Tracking Conversations has **beta** stability. Configuration options and behavior may change. For AI applications that involve multi-turn conversations, you can use `sentry_sdk.ai.set_conversation_id()` to associate all AI spans from the same conversation. This enables you to track and analyze complete conversation flows within Sentry. diff --git a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/caches-module.mdx b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/caches-module.mdx index 71a27e01f00581..2ae6ef48f87b88 100644 --- a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/caches-module.mdx +++ b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/caches-module.mdx @@ -3,22 +3,15 @@ title: Instrument Caches sidebar_order: 1000 description: "Learn how to manually instrument your code to use Sentry's Caches module. " --- - A cache can be used to speed up data retrieval, thereby improving application performance. Because instead of getting data from a potentially slow data layer, your application will be getting data from memory (in a best case scenario). Caching can speed up read-heavy workloads for applications like Q&A portals, gaming, media sharing, and social networking. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - Sentry offers a [cache-monitoring dashboard](https://sentry.io/orgredirect/organizations/:orgslug/dashboards/) that can be auto-instrumented for popular Python caching setups (like Django and Redis). If you're using a custom caching solution that doesn't have auto instrumentation, you can manually instrument it and use Sentry to get a look into how your caching solution is performing by following the setup instructions below. To make it possible for Sentry to give you an overview of your cache performance, you'll need to create two spans - one indicating that something is being put into the cache, and a second one indicating that something is being fetched from the cache. -In transaction mode, make sure that there's a transaction running when you create the spans. If you're using a web framework those transactions will be created for you automatically. See Tracing for more information. +Make sure that there's a transaction running when you create the spans. If you're using a web framework those transactions will be created for you automatically. See Tracing for more information. For detailed information about which data can be set, see the [Cache Module Developer Specification](https://develop.sentry.dev/sdk/performance/modules/caches/). @@ -28,16 +21,16 @@ If you're using anything other than Dja ### Add Span When Putting Data Into the Cache -If the cache you're using isn't supported by auto instrumentation mentioned above, you can use the custom instrumentation instructions below to emit cache spans: +If the cache you’re using isn’t supported by auto instrumentation mentioned above, you can use the custom instrumentation instructions below to emit cache spans: 1. Set the cache value with whatever cache library you happen to be using. -2. Wrap the part of your application that uses the cached value with `with sentry_sdk.start_span(...)` (transaction mode) or `with sentry_sdk.traces.start_span(...)` (stream mode). -3. Set `op` (transaction mode) or the `sentry.op` attribute (stream mode) to `cache.put`. +2. Wrap the part of your application that uses the cached value with `with sentry_sdk.start_span(...)` +3. Set `op` to `cache.put`. 4. Set `cache.item_size` to an integer representing the size of the cached item. (The steps described above are documented in the snippet.) -```python {tabTitle:Transaction Mode (Default)} +```python import my_caching import sentry_sdk @@ -59,43 +52,20 @@ with sentry_sdk.start_span(op="cache.put") as span: span.set_data("cache.item_size", len(value)) # Warning: if value is very big this could use lots of memory ``` -```python {tabTitle:Stream Mode} -import my_caching -import sentry_sdk - -key = "myCacheKey123" -value = "The value I want to cache." - -with sentry_sdk.traces.start_span(attributes={"sentry.op": "cache.put"}) as span: - # Set a key in your caching using your custom caching solution - my_caching.set(key, value) - - # Describe the cache server you are accessing - span.set_attributes({ - "network.peer.address": "cache.example.com/supercache", - "network.peer.port": 9000, - - # Add the key(s) you want to set - "cache.key": [key], - - # Add the size of the value you stored in the cache - "cache.item_size": len(value), # Warning: if value is very big this could use lots of memory - }) -``` ### Add Span When Retrieving Data From the Cache -If the cache you're using isn't supported by auto instrumentation mentioned above, you can use the custom instrumentation instructions below to emit cache spans: +If the cache you’re using isn’t supported by auto instrumentation mentioned above, you can use the custom instrumentation instructions below to emit cache spans: 1. Fetch the cached value from whatever cache library you happen to be using. -2. Wrap the part of your application that uses the cached value with `with sentry_sdk.start_span(...)` (transaction mode) or `with sentry_sdk.traces.start_span(...)` (stream mode). -3. Set `op` (transaction mode) or the `sentry.op` attribute (stream mode) to `cache.get`. +2. Wrap the part of your application that uses the cached value with `with sentry_sdk.start_span(...)` +3. Set `op` to `cache.get`. 4. Set `cache.hit` to a boolean value representing whether the value was successfully fetched from the cache or not. 5. Set `cache.item_size` to an integer representing the size of the cached item. (The steps described above are documented in the snippet.) -```python {tabTitle:Transaction Mode (Default)} +```python import my_caching import sentry_sdk @@ -124,35 +94,4 @@ with sentry_sdk.start_span(op="cache.get") as span: span.set_data("cache.hit", False) ``` -```python {tabTitle:Stream Mode} -import my_caching -import sentry_sdk - -key = "myCacheKey123" -value = None - -with sentry_sdk.traces.start_span(attributes={"sentry.op": "cache.get"}) as span: - # Get a key from your caching solution - value = my_caching.get(key) - - # Describe the cache server you are accessing - span.set_attributes({ - "network.peer.address": "cache.example.com/supercache", - "network.peer.port": 9000, - - # Add the key(s) you just retrieved from the cache - "cache.key": [key], - }) - - if value is not None: - # If you retrieved a value, the cache was hit - span.set_attribute("cache.hit", True) - - # Optionally also add the size of the value you retrieved - span.set_attribute("cache.item_size", len(value)) - else: - # If you could not retrieve a value, it was a miss - span.set_attribute("cache.hit", False) -``` - You should now have the right spans in place. Head over to the [Cache dashboard](https://sentry.io/orgredirect/organizations/:orgslug/dashboards/) to see how your cache is performing. diff --git a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/index.mdx b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/index.mdx index 017579961d0ce7..1ba23a4fddb6b1 100644 --- a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/index.mdx +++ b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/index.mdx @@ -5,31 +5,19 @@ description: "Learn how to capture performance data on any action in your app." The Sentry SDK for Python does a very good job of auto instrumenting your application. If you use one of the popular frameworks, we've got you covered because well-known operations like HTTP calls and database queries will be instrumented out of the box. The Sentry SDK will also check your installed Python packages and auto-enable the matching SDK integrations. If you want to enable tracing in a piece of code that performs some other operations, add the `@sentry_sdk.trace` decorator. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - - - -The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`. - - +## Add a Transaction -## Add a Transaction/Service Span - -Adding transactions or service spans (in stream mode) will allow you to instrument and capture certain regions of your code. +Adding transactions will allow you to instrument and capture certain regions of your code. -If you're using one of Sentry's SDK integrations, transactions/service spans will be created for you automatically. +If you're using one of Sentry's SDK integrations, transactions will be created for you automatically. -The following example creates a transaction/service span for an expensive operation (in this case, `eat_pizza`), and then sends the result to Sentry: +The following example creates a transaction for an expensive operation (in this case, `eat_pizza`), and then sends the result to Sentry: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk def eat_slice(slice): @@ -41,48 +29,27 @@ def eat_pizza(pizza): eat_slice(pizza.slices.pop()) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -def eat_slice(slice): - ... - -def eat_pizza(pizza): - with sentry_sdk.traces.start_span( - name="Eat Pizza", - attributes={"sentry.op": "task"}, - # set parent span to None to create service span - parent_span=None, - ): - while pizza.slices > 0: - span = sentry_sdk.traces.start_span(name="Eat Slice") - eat_slice(pizza.slices.pop()) - span.end() -``` - -The [API reference](https://getsentry.github.io/sentry-python/api.html#performance-monitoring) documents `start_transaction` and `start_span`, along with their parameters. +The [API reference](https://getsentry.github.io/sentry-python/api.html#sentry_sdk.api.start_transaction) documents `start_transaction` and all its parameters. -Note that `sentry_sdk.start_transaction()` (transaction mode) is meant be used as a context manager. This ensures that the transaction/service span will be properly set as active and any spans created within will be attached to it. - -In stream mode, `sentry_sdk.traces.start_span()` can, but doesn't have to be, used as a context manager. If you don't use it as a context manager, make sure to call `span.end()` to finish the span. +Note that `sentry_sdk.start_transaction()` is meant be used as a context manager. This ensures that the transaction will be properly set as active and any spans created within will be attached to it. -## Add Spans to a Transaction/Service Span +## Add Spans to a Transaction -If you want to have more fine-grained performance monitoring, you can add child spans to your transaction/service span, which can be done by either: +If you want to have more fine-grained performance monitoring, you can add child spans to your transaction, which can be done by either: - Using a context manager - Using a decorator (this works on sync and async functions) - Manually starting and finishing a span -Calling `sentry_sdk.start_span()` in transaction mode or `sentry_sdk.traces.start_span()` in stream mode will find the current active transaction/span and attach the new span to it. +Calling `sentry_sdk.start_span()` will find the current active transaction and attach the span to it. ### Using a Context Manager -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk def eat_slice(slice): @@ -95,26 +62,15 @@ def eat_pizza(pizza): eat_slice(pizza.slices.pop()) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk + -def eat_slice(slice): - ... +The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`. -def eat_pizza(pizza): - with sentry_sdk.traces.start_span( - name="Eat Pizza", - attributes={"sentry.op": "task"}, - parent_span=None, - ): - while pizza.slices > 0: - with sentry_sdk.traces.start_span(name="Eat Slice"): - eat_slice(pizza.slices.pop()) -``` + ### Using a Decorator -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk @sentry_sdk.trace @@ -127,34 +83,17 @@ def eat_pizza(pizza): eat_slice(pizza.slices.pop()) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -@sentry_sdk.traces.trace -def eat_slice(slice): - ... - -def eat_pizza(pizza): - with sentry_sdk.traces.start_span( - name="Eat Pizza", - attributes={"sentry.op": "task"}, - parent_span=None, - ): - while pizza.slices > 0: - eat_slice(pizza.slices.pop()) -``` - See the [@sentry_sdk.trace decoration section](#sentry_sdktrace-decorator) below for more details. - + -When tracing a static or class method, you **must** add the `@sentry_sdk.trace` (transaction mode)/`@sentry_sdk.traces.trace` (stream mode) decorator **after** the `@staticmethod` or `@classmethod` decorator (i.e., **closer** to the function definition). Otherwise, your function will break! This applies in both transaction mode and stream mode. +When tracing a static or class method, you **must** add the `@sentry_sdk.trace` decorator **after** the `@staticmethod` or `@classmethod` decorator (i.e., **closer** to the function definition). Otherwise, your function will break! ### Manually Starting and Finishing a Span -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk def eat_slice(slice): @@ -168,25 +107,13 @@ def eat_pizza(pizza): span.finish() ``` -```python {tabTitle:Stream Mode} -import sentry_sdk + -def eat_slice(slice): - ... +The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`. -def eat_pizza(pizza): - with sentry_sdk.traces.start_span( - name="Eat Pizza", - attributes={"sentry.op": "task"}, - parent_span=None, - ): - while pizza.slices > 0: - span = sentry_sdk.traces.start_span(name="Eat Slice") - eat_slice(pizza.slices.pop()) - span.end() -``` + -When you create your span manually, make sure to call `span.finish()` (transaction mode) or `span.end()` (stream mode) after the block of code you want to wrap in a span to finish the span. If you do not finish the span it will not be sent to Sentry. +When you create your span manually, make sure to call `span.finish()` after the block of code you want to wrap in a span to finish the span. If you do not finish the span it will not be sent to Sentry. ## Nested Spans @@ -194,7 +121,7 @@ Spans can be nested to form a span tree. If you'd like to learn more, read our [ ### Using a Context Manager -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk def chew(): @@ -206,21 +133,15 @@ def eat_slice(slice): chew() ``` -```python {tabTitle:Stream Mode} -import sentry_sdk + -def chew(): - ... +The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`. -def eat_slice(slice): - with sentry_sdk.traces.start_span(name="Eat Slice"): - with sentry_sdk.traces.start_span(name="Chew"): - chew() -``` + ### Using a Decorator -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk @sentry_sdk.trace @@ -232,23 +153,11 @@ def eat_slice(slice): chew() ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -@sentry_sdk.traces.trace -def chew(): - ... - -@sentry_sdk.traces.trace -def eat_slice(slice): - chew() -``` - See the [@sentry_sdk.trace decoration section](#sentry_sdktrace-decorator) below for more details. -### Manually Starting and Finishing +### Manually -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk def chew(): @@ -264,96 +173,41 @@ def eat_slice(slice): parent_span.finish() ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -def chew(): - ... - -def eat_slice(slice): - parent_span = sentry_sdk.traces.start_span(name="Eat Slice") - - # pass the parent span via `parent_span` to create a child span - child_span = sentry_sdk.traces.start_span(name="Chew", parent_span=parent_span) - chew() - child_span.end() - - parent_span.end() -``` - -In transaction mode, the parameters of `start_span()` and `start_child()` are the same. See the [API reference](https://getsentry.github.io/sentry-python/api.html#sentry_sdk.api.start_span) for more details. -In stream mode, however, there's no separate `start_child()` method. Instead, the span will become the child of the currently active span. Alternatively, you can pass the parent span explicitly via `parent_span` when starting the child (as shown in the example above). - -When you create your span manually, make sure to call `span.finish()` (transaction mode) or `span.end()` (stream mode) after the block of code you want to wrap in a span to finish the span. If you do not finish the span it will not be sent to Sentry. - -### Sibling Spans - - + -Only available in stream mode. +The parameter `name` in `start_span()` used to be called `description`. In version 2.15.0 `description` was deprecated and from 2.15.0 on, only `name` should be used. `description` will be removed in `3.0.0`. -In stream mode, a span normally attaches to whatever span is currently active. To control parentage explicitly, for example to make a span a sibling instead of a child, pass `parent_span` directly: - -```python -import sentry_sdk - -def eat_slice(slice): - ... - -def chew(): - ... +The parameters of `start_span()` and `start_child()` are the same. See the [API reference](https://getsentry.github.io/sentry-python/api.html#sentry_sdk.api.start_span) for more details. -def eat_pizza(pizza): - with sentry_sdk.traces.start_span(name="Eat Pizza") as pizza_span: - with sentry_sdk.traces.start_span(name="Eat Slice"): - with sentry_sdk.traces.start_span(name="Chew", parent_span=pizza_span): - # "Chew" is a sibling of "Eat Slice", not its child - chew() - eat_slice(pizza.slices.pop()) -``` +When you create your span manually, make sure to call `span.finish()` after the block of code you want to wrap in a span to finish the span. If you do not finish the span it will not be sent to Sentry. ## @sentry_sdk.trace decorator -In transaction mode, you can set `op`, `name` and `attributes` parameters in the `@sentry_sdk.trace` decorator to customize your spans. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types. +You can set `op`, `name` and `attributes` parameters in the `@sentry_sdk.trace` decorator to customize your spans. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types. -In stream mode, the decorator is `@sentry_sdk.traces.trace` and accepts `name`, `attributes`, and `active`. Note that there's no `op` parameter; set the operation as the `sentry.op` key in `attributes` instead. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types. +When tracing a static or class method, you **must** add the `@sentry_sdk.trace` decorator **after** the `@staticmethod` or `@classmethod` decorator (i.e., **closer** to the function definition). Otherwise, your function will break. - + -When tracing a static or class method, you **must** add the decorator **after** the `@staticmethod` or `@classmethod` decorator (i.e., **closer** to the function definition). Otherwise, your function will break. +The parameters `op`, `name` and `attributes` were added to the `@sentry_sdk.trace` decorator in version 2.35.0. -```python {tabTitle:Transaction Mode (Default)} -import sentry_sdk - -@sentry_sdk.trace(op="my_op", name="Paul", attributes={"x": True}) -def my_function(i): - ... - -@sentry_sdk.trace -def root_function(): - for i in range(3): - my_function(i) - -root_function() -``` - -```python {tabTitle:Stream Mode} -import sentry_sdk +```python {diff} + import sentry_sdk -@sentry_sdk.traces.trace(name="Paul", attributes={"sentry.op": "my_op", "x": True}) -def my_function(i): - ... ++@sentry_sdk.trace(op="my_op", name="Paul", attributes={"x": True}) + def my_function(i): + ... -@sentry_sdk.traces.trace -def root_function(): - for i in range(3): - my_function(i) + @sentry_sdk.trace + def root_function(): + for i in range(3): + my_function(i) -root_function() + root_function() ``` The code above will customize the `my_function` spans like this: @@ -371,12 +225,6 @@ gantt ### Span Templates - - -The `template` parameter is only available in transaction mode. - - - In the `@sentry_sdk.trace` decorator you can also specify a `template`. This helps create spans that follow a certain template. Currently this is only available for spans that are created for the [AI Agents instrumentation](/platforms/python/tracing/instrumentation/custom-instrumentation/ai-agents-module/#spans) of Sentry. Available templates are `AI_AGENT`, `AI_TOOL`, and `AI_CHAT`. @@ -449,15 +297,9 @@ To enable performance monitoring for the functions specified in `functions_to_tr ## Accessing the Current Transaction - - -Only available in transaction mode. - - - The `sentry_sdk.get_current_scope().transaction` property returns the active transaction or `None` if no transaction is active. You can use this property to modify data on the transaction. -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk def eat_pizza(pizza): @@ -472,11 +314,11 @@ def eat_pizza(pizza): ## Accessing the Current Span -To change data in the current span, use ` sentry_sdk.get_current_span()` (transaction mode) or `sentry_sdk.traces.get_current_span()` (stream mode). This function will return a span if there's one running, otherwise it will return `None`. +To change data in the current span, use ` sentry_sdk.get_current_span()`. This function will return a span if there's one running, otherwise it will return `None`. -In this example, we'll set custom data in the span created by the `@sentry_sdk.trace` decorator. +In this example, we'll set a tag in the span created by the `@sentry_sdk.trace` decorator. -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk @sentry_sdk.trace @@ -487,22 +329,11 @@ def eat_slice(slice): span.set_tag("slice_id", slice.id) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -@sentry_sdk.traces.trace -def eat_slice(slice): - span = sentry_sdk.traces.get_current_span() - - if span is not None: - span.set_attribute("slice_id", slice.id) -``` - ## Improving Data on Transactions and Spans -In transaction mode, you can add data attributes to your transactions. This data is visible in the trace explorer in Sentry. Data attributes can be of type `string`, `number` or `boolean`, as well as (non-mixed) arrays of these types: +You can add data attributes to your transactions. This data is visible in the trace explorer in Sentry. Data attributes can be of type `string`, `number` or `boolean`, as well as (non-mixed) arrays of these types: -```python {tabTitle:Transaction Mode (Default)} +```python with sentry_sdk.start_transaction(name="my-transaction") as transaction: transaction.set_data("my-data-attribute-1", "value1") transaction.set_data("my-data-attribute-2", 42) @@ -513,9 +344,9 @@ with sentry_sdk.start_transaction(name="my-transaction") as transaction: transaction.set_data("my-data-attribute-6", [True, False, True]) ``` -You can add data attributes to any span the same way, with the same type restrictions as described above. +You can add data attributes to your spans the same way, with the same type restrictions as described above. -```python {tabTitle:Transaction Mode (Default)} +```python with sentry_sdk.start_span(name="my-span") as span: span.set_data("my-data-attribute-1", "value1") span.set_data("my-data-attribute-2", 42) @@ -526,41 +357,32 @@ with sentry_sdk.start_span(name="my-span") as span: span.set_data("my-data-attribute-6", [True, False, True]) ``` -```python {tabTitle:Stream Mode} -with sentry_sdk.traces.start_span(name="my-span") as span: - span.set_attribute("my-data-attribute-1", "value1") - span.set_attribute("my-data-attribute-2", 42) - span.set_attribute("my-data-attribute-3", True) - - # or use set_attributes to set multiple attributes at once - span.set_attributes({ - "my-data-attribute-4": ["value1", "value2", "value3"], - "my-data-attribute-5": [42, 43, 44], - "my-data-attribute-6": [True, False, True] - }) -``` - - +To attach data attributes to the transaction and all its spans, you can use `before_send_transaction`: - +```python +import sentry_sdk +from sentry_sdk.types import Event, Hint -`before_send_span` can only modify span data and you cannot use it to drop spans. See [Dropping Spans](#dropping-spans) below. +def before_send_transaction(event: Event, hint: Hint) -> Event | None: + # Set the data attribute "foo" to "bar" on every span belonging to this + # transaction event + for span in event["spans"]: + span["data"]["foo"] = "bar" - + # Set the data on the transaction itself, too + event["contexts"]["trace"]["data"]["foo"] = "bar" -## Dropping Spans + return event - +sentry_sdk.init( + traces_sample_rate=1.0, + before_send_transaction=before_send_transaction, +) +``` ## Update Current Span Shortcut - - -`sentry_sdk.update_current_span()` isn't available in stream mode. Retrieve the current span with `sentry_sdk.traces.get_current_span()` and update its attributes using `set_attribute()` or `set_attributes()`. - - - -In transaction mode, you can update the data of the currently running span using the `sentry_sdk.update_current_span()` function. You can set `op`, `name` and `attributes` to update your span. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types. +You can update the data of the currently running span using the `sentry_sdk.update_current_span()` function. You can set `op`, `name` and `attributes` to update your span. Attribute values can only be primitive types (like `int`, `float`, `bool`, `str`) or a list of those types without mixing types. ```python {diff} import sentry_sdk diff --git a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/mcp-module.mdx b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/mcp-module.mdx index d15f5d6b2da4a5..74b8af8cac9412 100644 --- a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/mcp-module.mdx +++ b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/mcp-module.mdx @@ -8,12 +8,6 @@ With Sentry's [MCP monitoring](/ai/monitoring/mcp/), you can track and debug MCP As a prerequisite to setting up MCP monitoring with Python, you'll need to first set up tracing. Once this is done, the Python SDK will automatically instrument MCP servers created with supported libraries. If that doesn't fit your use case, you can use custom instrumentation described below. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Automatic Instrumentation The Python SDK supports automatic instrumentation for MCP servers. We recommend adding the MCP integration to your Sentry configuration to automatically capture spans for MCP operations. @@ -24,8 +18,7 @@ The Python SDK supports automatic instrumentation for MCP servers. We recommend For your MCP data to show up in Sentry, spans must be created with well-defined names and data attributes. See below for the different types of MCP operations you can instrument. -In transaction mode, you can also use the [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator to create these spans, using its `template` parameter. -In stream mode, these spans need to be created directly with `sentry_sdk.traces.start_span()`, as shown below. +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create these spans. ## Spans @@ -35,7 +28,7 @@ In stream mode, these spans need to be created directly with `sentry_sdk.traces. #### Example Tool Execution Span: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk import json @@ -79,59 +72,13 @@ with sentry_sdk.start_span( raise ``` -```python {tabTitle:Stream Mode} -import sentry_sdk -import json - -sentry_sdk.init(...) - -# Example tool execution -tool_name = "get_weather" -tool_arguments = {"city": "San Francisco"} - -with sentry_sdk.traces.start_span( - name=f"tools/call {tool_name}", - attributes={"sentry.op": "mcp.server"}, -) as span: - # Set MCP-specific attributes - span.set_attributes({ - "mcp.tool.name": tool_name, - "mcp.method.name": "tools/call", - - # Set request metadata - "mcp.request.id": "req_123abc", - "mcp.session.id": "session_xyz789", - "mcp.transport": "stdio", # or "http", "sse" for HTTP/WebSocket/SSE - "network.transport": "pipe", # or "tcp" for HTTP/SSE - }) - - # Set tool arguments (optional, if send_default_pii=True) - for key, value in tool_arguments.items(): - span.set_attribute(f"mcp.request.argument.{key}", value) - - # Execute the tool - try: - result = execute_tool(tool_name, tool_arguments) - - # Set result data - span.set_attribute("mcp.tool.result.content", json.dumps(result)) - span.set_attribute("mcp.tool.result.is_error", False) - - # Set result content count if applicable - if isinstance(result, (list, dict)): - span.set_attribute("mcp.tool.result.content_count", len(result)) - except Exception as e: - span.set_attribute("mcp.tool.result.is_error", True) - raise -``` - ### Prompt Retrieval Span #### Example Prompt Retrieval Span: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk import json @@ -155,7 +102,7 @@ with sentry_sdk.start_span( span.set_data("mcp.transport", "http") span.set_data("network.transport", "tcp") - # Set prompt arguments (keep in mind this might contain PII data, so only set if that's ok) + # Set prompt arguments (optional, if send_default_pii=True) for key, value in prompt_arguments.items(): span.set_data(f"mcp.request.argument.{key}", value) @@ -169,67 +116,20 @@ with sentry_sdk.start_span( # For single-message prompts, set role and content if len(messages) == 1: span.set_data("mcp.prompt.result.message_role", messages[0].get("role")) - # Note that content might contain PII data + # Content is PII, only set if send_default_pii=True span.set_data( "mcp.prompt.result.message_content", json.dumps(messages[0].get("content")) ) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk -import json - -sentry_sdk.init(...) - -# Example prompt retrieval -prompt_name = "code_review" -prompt_arguments = {"language": "python"} - -with sentry_sdk.traces.start_span( - name=f"prompts/get {prompt_name}", - attributes={"sentry.op": "mcp.server"}, -) as span: - # Set MCP-specific attributes - span.set_attributes({ - "mcp.prompt.name": prompt_name, - "mcp.method.name": "prompts/get", - - # Set request metadata - "mcp.request.id": "req_456def", - "mcp.session.id": "session_xyz789", - "mcp.transport": "http", - "network.transport": "tcp", - }) - - # Set prompt arguments (keep in mind this might contain PII data, so only set if that's ok) - for key, value in prompt_arguments.items(): - span.set_attribute(f"mcp.request.argument.{key}", value) - - # Retrieve the prompt - prompt_result = get_prompt(prompt_name, prompt_arguments) - - # Set result data - messages = prompt_result.get("messages", []) - span.set_attribute("mcp.prompt.result.message_count", len(messages)) - - # For single-message prompts, set role and content - if len(messages) == 1: - span.set_attribute("mcp.prompt.result.message_role", messages[0].get("role")) - # Note that content might contain PII data - span.set_attribute( - "mcp.prompt.result.message_content", - json.dumps(messages[0].get("content")) - ) -``` - ### Resource Read Span #### Example Resource Read Span: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk sentry_sdk.init(...) @@ -255,34 +155,6 @@ with sentry_sdk.start_span( resource_data = read_resource(resource_uri) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -sentry_sdk.init(...) - -# Example resource access -resource_uri = "file:///path/to/resource.txt" - -with sentry_sdk.traces.start_span( - name=f"resources/read {resource_uri}", - attributes={"sentry.op": "mcp.server"}, -) as span: - # Set MCP-specific attributes - span.set_attributes({ - "mcp.resource.uri": resource_uri, - "mcp.method.name": "resources/read", - - # Set request metadata - "mcp.request.id": "req_789ghi", - "mcp.session.id": "session_xyz789", - "mcp.transport": "http", - "network.transport": "tcp", - }) - - # Access the resource - resource_data = read_resource(resource_uri) -``` - ## Common Span Attributes diff --git a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/queues-module.mdx b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/queues-module.mdx index 3b5c0b451a11f0..5741c398a766b8 100644 --- a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/queues-module.mdx +++ b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/queues-module.mdx @@ -3,34 +3,26 @@ title: Instrument Queues sidebar_order: 3000 description: "Learn how to manually instrument your code to use Sentry's Queues module. " --- - Sentry comes with a [queue-monitoring dashboard](https://sentry.io/orgredirect/organizations/:orgslug/dashboards/) that can be auto-instrumented for popular Python queue setups (like Celery). In case yours isn't supported, you can still instrument custom spans and transactions around your queue producers and consumers to ensure that you have performance data about your messaging queues. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Producer Instrumentation -To start capturing performance metrics, use the `start_span()` function to wrap your queue producer events. Your span `op` must be set to `queue.publish`. Include the following span data to enrich your producer spans with queue metrics: - -| Data Attribute | Type | Description | -| :---------------------------- | :----- | :-------------------------------- | -| `messaging.message.id ` | string | The message identifier | -| `messaging.destination.name` | string | The queue or topic name | -| `messaging.message.body.size` | int | Size of the message body in bytes | +To start capturing performance metrics, use the `sentry_sdk.start_span()` function to wrap your queue producer events. Your span `op` must be set to `queue.publish`. Include the following span data to enrich your producer spans with queue metrics: -In transaction mode, your `queue.publish` span must exist inside a transaction in order to be recognized as a producer span. If you are using a supported web framework, the transaction is created by the integration. If you use plain Python, you can start a new one using `sentry_sdk.start_transaction()`. +| Data Attribute | Type | Description | +|:--|:--|:--| +| `messaging.message.id ` | string | The message identifier | +| `messaging.destination.name` | string | The queue or topic name | +| `messaging.message.body.size` | int | Size of the message body in bytes | -In stream mode, there's no separate transaction to depend on. If your `queue.process` span has no parent, it's automatically promoted to a service span. If you'd rather group it under an explicit service span, start one with `sentry_sdk.traces.start_span(parent_span=None)` first. +Your `queue.publish` span must exist inside a transaction in order to be recognized as a producer span. If you are using a supported web framework, the transaction is created by the integration. If you use plain Python, you can start a new one using `sentry_sdk.start_transaction()`. You must also include trace headers (`sentry-trace` and `baggage`) in your message so that your consumers can continue your trace once your message is picked up. -```python {tabTitle:Transaction Mode (Default)} + +```python from datetime import datetime, timezone import sentry_sdk @@ -76,74 +68,25 @@ with sentry_sdk.start_transaction( ) ``` -```python {tabTitle:Stream Mode} -from datetime import datetime, timezone - -import sentry_sdk -import my_custom_queue - -# Initialize Sentry -sentry_sdk.init(...) - -connection = my_custom_queue.connect() - -# The message you want to send to the queue -queue = "messages" -message = "Hello World!" -message_id = "abc123" - -# Create the service span -# If you are using a web framework, the framework integration -# will create this for you and you can omit this. -with sentry_sdk.traces.start_span( - name="queue_producer_transaction", - attributes={"sentry.op": "function"}, - parent_span=None, -): - # Create the span - with sentry_sdk.traces.start_span( - name="queue_producer", - attributes={"sentry.op": "queue.publish"}, - ) as span: - # Set span data - span.set_attributes({ - "messaging.message.id": message_id, - "messaging.destination.name": queue, - "messaging.message.body.size": len(message.encode("utf-8")), - }) - - # Publish the message to the queue (including trace information and current time stamp) - now = int(datetime.now(timezone.utc).timestamp()) - connection.publish( - queue=queue, - body=message, - timestamp=now, - headers={ - "sentry-trace": sentry_sdk.get_traceparent(), - "baggage": sentry_sdk.get_baggage(), - }, - ) -``` ## Consumer Instrumentation -To start capturing performance metrics, use the `start_span()` function to wrap your queue consumers. Your span `op` must be set to `queue.process`. Include the following span data to enrich your consumer spans with queue metrics: +To start capturing performance metrics, use the `sentry_sdk.start_span()` function to wrap your queue consumers. Your span `op` must be set to `queue.process`. Include the following span data to enrich your consumer spans with queue metrics: -| Data Attribute | Type | Description | -| :----------------------------------- | :----- | :------------------------------------------------------------------ | -| `messaging.message.id ` | string | The message identifier | -| `messaging.destination.name` | string | The queue or topic name | -| `messaging.message.body.size` | number | Size of the message body in bytes | -| `messaging.message.retry.count ` | number | The number of times a message was attempted to be processed | +| Data Attribute | Type | Description | +|:--|:--|:--| +| `messaging.message.id ` | string | The message identifier | +| `messaging.destination.name` | string | The queue or topic name | +| `messaging.message.body.size` | number | Size of the message body in bytes | +| `messaging.message.retry.count ` | number | The number of times a message was attempted to be processed | | `messaging.message.receive.latency ` | number | The time in milliseconds that a message awaited processing in queue | -In transaction mode, your `queue.process` span must exist inside a transaction in order to be recognized as a consumer span. If you are using a supported web framework, the transaction is created by the integration. If you use plain Python, you can start a new one using `sentry_sdk.start_transaction()`. +Your `queue.process` span must exist inside a transaction in order to be recognized as a consumer span. If you are using a supported web framework, the transaction is created by the integration. If you use plain Python, you can start a new one using `sentry_sdk.start_transaction()`. -In stream mode, there's no separate transaction to depend on. If your `queue.process` span has no parent, it's automatically promoted to a service span. If you'd rather group it under an explicit service span, start one with `sentry_sdk.traces.start_span(parent_span=None)` first. +Use `sentry_sdk.continue_trace()` to connect your consumer spans to their associated producer spans, and `transaction.set_status()` to mark the trace of your message as success or failed. -Use `continue_trace()` to connect your consumer spans to their associated producer spans. To mark the trace of your message as success or failed, use `transaction.set_status()` in transaction mode, or set `span.status = "ok"/"error"` directly on the service span in stream mode. -```python {tabTitle:Transaction Mode (Default)} +```python from datetime import datetime, timezone import sentry_sdk @@ -180,8 +123,8 @@ with sentry_sdk.start_transaction(transaction): # Set span data span.set_data("messaging.message.id", message["message_id"]) span.set_data("messaging.destination.name", queue) - span.set_data("messaging.message.body.size", len(message["body"].encode("utf-8"))) - span.set_data("messaging.message.receive.latency", latency.total_seconds() * 1000) + span.set_data("messaging.message.body.size", message["body"]) + span.set_data("messaging.message.receive.latency", latency) span.set_data("messaging.message.retry.count", 0) try: @@ -191,54 +134,3 @@ with sentry_sdk.start_transaction(transaction): # In case of an error set the status to "internal_error" transaction.set_status("internal_error") ``` - -```python {tabTitle:Stream Mode} -from datetime import datetime, timezone - -import sentry_sdk -import my_custom_queue - -# Initialize Sentry -sentry_sdk.init(...) - -connection = my_custom_queue.connect() - -# Pick up message from queues -queue = "messages" -message = connection.consume(queue=queue) - -# Calculate latency (optional, but valuable) -now = datetime.now(timezone.utc) -message_time = datetime.fromtimestamp(message["timestamp"], timezone.utc) -latency = now - message_time - -# Continue the trace started in the producer -# If you are using a web framework, the framework integration -# will create this for you and you can omit this. -sentry_sdk.traces.continue_trace(message["headers"]) -with sentry_sdk.traces.start_span( - name="queue_consumer_transaction", - attributes={"sentry.op": "function"}, - parent_span=None, -) as service_span: - # Create the span - with sentry_sdk.traces.start_span( - name="queue_consumer", - attributes={"sentry.op": "queue.process"}, - ) as span: - # Set span data - span.set_attributes({ - "messaging.message.id": message["message_id"], - "messaging.destination.name": queue, - "messaging.message.body.size": len(message["body"].encode("utf-8")), - "messaging.message.receive.latency": latency.total_seconds() * 1000, - "messaging.message.retry.count": 0, - }) - - try: - # Process the message - process_message(message) - except Exception: - # In case of an error set the status to "error" - service_span.status = "error" -``` diff --git a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/requests-module.mdx b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/requests-module.mdx index 3a69ad85b4ade4..d25a4355f9f808 100644 --- a/docs/platforms/python/tracing/instrumentation/custom-instrumentation/requests-module.mdx +++ b/docs/platforms/python/tracing/instrumentation/custom-instrumentation/requests-module.mdx @@ -3,15 +3,8 @@ title: Instrument HTTP Requests sidebar_order: 2000 description: "Learn how to manually instrument your code to use Sentry's Requests module." --- - As a prerequisite to setting up [Requests](/product/dashboards/sentry-dashboards/outbound-api-requests/), you’ll need to first set up tracing. Once this is done, the Python SDK will automatically instrument outgoing HTTP requests made via `HTTPConnection` and show the data in the [requests-monitoring dashboard](https://sentry.io/orgredirect/organizations/:orgslug/dashboards/). If that doesn't fit your use case, you can set up using custom instrumentation described below. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Custom Instrumentation For detailed information about which data can be set, see the [Requests Module developer specifications](https://develop.sentry.dev/sdk/performance/modules/requests/). @@ -22,15 +15,14 @@ NOTE: Refer to [HTTP Span Data Conventions](https://develop.sentry.dev/sdk/perfo Here is an example of an instrumented function that makes HTTP requests: -```python {tabTitle:Transaction Mode (Default)} +```python from urllib.parse import urlparse import requests -import sentry_sdk def make_request(method, url): span = sentry_sdk.start_span( op="http.client", - name=f"{method} {url}", + name="%s %s" % (method, url), ) span.set_data("http.request.method", method) @@ -43,39 +35,10 @@ def make_request(method, url): response = requests.request(method=method, url=url) span.set_data("http.response.status_code", response.status_code) - span.set_data("http.response_content_length", response.headers.get("content-length")) + span.set_data("http.response_content_length", response.headers["content-length"]) span.finish() return response ``` - -```python {tabTitle:Stream Mode} -from urllib.parse import urlparse -import requests -import sentry_sdk - -def make_request(method, url): - span = sentry_sdk.traces.start_span( - name=f"{method} {url}", - attributes={"sentry.op": "http.client"}, - ) - parsed_url = urlparse(url) - span.set_attributes({ - "http.request.method": method, - "url": url, - "server.address": parsed_url.hostname, - "server.port": parsed_url.port, - }) - response = requests.request(method=method, url=url) - - span.set_attribute("http.response.status_code", response.status_code) - - content_length = response.headers.get("content-length") - if content_length is not None: - span.set_attribute("http.response.header.content-length", content_length) - - span.finish() - return response -``` diff --git a/docs/platforms/python/tracing/instrumentation/index.mdx b/docs/platforms/python/tracing/instrumentation/index.mdx index cbc92494f3ae8f..be7475828ecfc3 100644 --- a/docs/platforms/python/tracing/instrumentation/index.mdx +++ b/docs/platforms/python/tracing/instrumentation/index.mdx @@ -14,8 +14,8 @@ There are two ways that instrumentation is applied to your application: ## Automatic Instrumentation -Many integrations for popular frameworks automatically capture transactions and spans that can be sent to Sentry. Read more about automatic instrumentation [here](/platforms/python/tracing/instrumentation/automatic-instrumentation/). +Many integrations for popular frameworks automatically capture transactions that can be sent to Sentry. Read more about automatic instrumentation [here](/platforms/python/tracing/instrumentation/automatic-instrumentation/). ## Custom Instrumentation +To add custom performance data to your application, you need to add custom instrumentation in the form of [spans](/concepts/key-terms/tracing/distributed-tracing/#traces-transactions-and-spans). Spans are a way to measure the time it takes for a specific action to occur. For example, you can create a span to measure the time it takes for a function to execute. Learn more about span lifecycles [here](/platforms/python/tracing/span-lifecycle/). -To add custom performance data to your application, you need to add custom instrumentation in the form of [spans](/concepts/key-terms/tracing/distributed-tracing/#traces-transactions-and-spans). Spans are a way to measure the time it takes for a specific action to occur. For example, you can create a span to measure the time it takes for a function to execute. Learn more about span lifecycles [here](/platforms/python/tracing/span-lifecycle/). diff --git a/docs/platforms/python/tracing/instrumentation/opentelemetry.mdx b/docs/platforms/python/tracing/instrumentation/opentelemetry.mdx index 0ac6659d725667..934e40af23f002 100644 --- a/docs/platforms/python/tracing/instrumentation/opentelemetry.mdx +++ b/docs/platforms/python/tracing/instrumentation/opentelemetry.mdx @@ -26,4 +26,4 @@ With Sentry’s OpenTelemetry SDK, an OpenTelemetry `Span` becomes a Sentry `Tra ## Additional Configuration -If you need more fine-grained control over Sentry, take a look at the Configuration page. In case you'd like to apply client-side sampling or filter out transactions before sending them to Sentry (to get rid of health checks, for example), you may find the Filtering page helpful. +If you need more fine-grained control over Sentry, take a look at the Configuration page. In case you'd like to apply client-side sampling or filter out transactions before sending them to Sentry (to get rid of health checks, for example), you may find the Filtering page helpful. diff --git a/docs/platforms/python/tracing/span-lifecycle/index.mdx b/docs/platforms/python/tracing/span-lifecycle/index.mdx index 24e4eea3adc736..571bf58725f73a 100644 --- a/docs/platforms/python/tracing/span-lifecycle/index.mdx +++ b/docs/platforms/python/tracing/span-lifecycle/index.mdx @@ -12,19 +12,13 @@ To capture transactions and spans customized to your organization's needs, you m To add custom performance data to your application, you need to add custom instrumentation in the form of [spans](/concepts/key-terms/tracing/distributed-tracing/#traces-transactions-and-spans). Spans are a way to measure the time it takes for a specific action to occur. For example, you can create a span to measure the time it takes for a function to execute. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Span Lifecycle In Python, spans are typically created using a context manager, which automatically manages the span's lifecycle. When you create a span using a context manager, the span automatically starts when entering the context and ends when exiting it. This is the recommended approach for most scenarios. -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk # Start a span for a task @@ -36,28 +30,13 @@ with sentry_sdk.start_span(op="task", name="Create User"): # The span automatically ends here when the 'with' block exits ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -# Start a span for a task -with sentry_sdk.traces.start_span( - name="Create User", - attributes={"sentry.op": "task"}, -): - # Your code here - # The span will automatically end when exiting this block - user = create_user(email="user@example.com") - send_welcome_email(user) - # The span automatically ends here when the 'with' block exits -``` - You can call the context manager's `__enter__` and `__exit__` methods to more explicitly control the span's lifecycle. ## Span Context and Nesting When you create a span, it becomes the child of the current active span. This allows you to build a hierarchy of spans that represent the execution path of your application: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk with sentry_sdk.start_span(op="process", name="Process Data"): @@ -72,61 +51,21 @@ with sentry_sdk.start_span(op="process", name="Process Data"): transform_data() ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="Process Data", - attributes={"sentry.op": "process"}, -): - # This code is tracked in the "Process Data" span - - with sentry_sdk.traces.start_span( - name="Validate Input", - attributes={"sentry.op": "task"}, - ): - # This is now a child span of "Process Data" - validate_data() - - with sentry_sdk.traces.start_span( - name="Transform Data", - attributes={"sentry.op": "task"}, - ): - # Another child span - transform_data() -``` - ## Span Starting Options The following options can be used when creating spans: -### Transaction Mode (Default) - -| Option | Type | Description | -| ----------------- | ---------------- | --------------------------- | -| `op` | `string` | The operation of the span. | -| `name` | `string` | The name of the span. | -| `start_timestamp` | `datetime/float` | The start time of the span. | - -### Stream mode - -| Option | Type | Description | -| ----------------- | ---------------- | -------------------------------------------------------------------------- | -| `name` | `string` | The name of the span. | -| `attributes` | `dict` | Structured metadata for the span, including `sentry.op` for the operation. | -| `start_timestamp` | `datetime/float` | The start time of the span. | -| `parent_span` | `StreamedSpan/None` | The parent to attach to. Pass None to force a service span. | - - - `op` is not a dedicated argument in stream mode. Set it as the `sentry.op` - attribute instead. - +| Option | Type | Description | +| ------------- | --------------- | ----------------------------------------------- | +| `op` | `string` | The operation of the span. | +| `name` | `string` | The name of the span. | +| `start_timestamp` | `datetime/float`| The start time of the span. | ## Using the Context Manager -For most scenarios, we recommend using the context manager approach. This creates a new span that automatically starts when entering the context and ends when exiting it. +For most scenarios, we recommend using the context manager approach with `sentry_sdk.start_span()`. This creates a new span that automatically starts when entering the context and ends when exiting it. -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk with sentry_sdk.start_span(op="db", name="Query Users") as span: @@ -137,23 +76,9 @@ with sentry_sdk.start_span(op="db", name="Query Users") as span: span.set_data("user_count", len(users)) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="Query Users", - attributes={"sentry.op": "db"}, -) as span: - # Perform a database query - users = db.query("SELECT * FROM users") - - # You can set attributes on the span - span.set_attribute("user_count", len(users)) -``` - The context manager also correctly handles exceptions, marking the span as failed if an exception occurs: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk try: @@ -166,27 +91,11 @@ except Exception: pass ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -try: - with sentry_sdk.traces.start_span( - name="Call External API", - attributes={"sentry.op": "http"}, - ): - # If this raises an exception, the span will be marked as failed - response = requests.get("https://api.example.com/data") - response.raise_for_status() -except Exception: - # The span is already marked as failed and has ended - pass -``` - ## Getting the Current Span -You can access the currently active span: +You can access the currently active span using `sentry_sdk.get_current_span()`: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk # Get the current active span @@ -195,19 +104,8 @@ if current_span: current_span.set_data("key", "value") ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -# Get the current active span -current_span = sentry_sdk.traces.get_current_span() -if current_span: - current_span.set_attribute("key", "value") -``` - ## Working with Transactions -`start_transaction` is only available in transaction mode. - [Transactions](/product/dashboards/sentry-dashboards/transaction-summary/#what-is-a-transaction) are a special type of span that represent a complete operation in your application, such as a web request. You can create transactions explicitly: ```python @@ -222,38 +120,13 @@ with sentry_sdk.start_transaction(name="Background Task", op="task") as transact pass ``` -## Working with Service Spans - -Service spans are only available in stream mode. - -In stream mode, a service span (the equivalent of a transaction) is just a span started with no parent in the current application. Pass `parent_span=None` to force this, even if a span is currently active: - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="Background Task", - attributes={"sentry.op": "task"}, - parent_span=None, -) as service_span: - # Your code here - - # You can add child spans to the service span - with sentry_sdk.traces.start_span( - name="Data Processing", - attributes={"sentry.op": "subtask"}, - ): - # Process data - pass -``` - ## Improving Span Data ### Adding Span Attributes Span attributes customize information you can get through tracing. This information can be found in the traces views in Sentry, once you drill into a span. You can capture additional context with span attributes. These can be key-value pairs of various Python types. -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk with sentry_sdk.start_span(op="db", name="Query Users") as span: @@ -264,23 +137,9 @@ with sentry_sdk.start_span(op="db", name="Query Users") as span: span.set_data("result_count", len(users)) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="Query Users", - attributes={"sentry.op": "db"}, -) as span: - # Execute the query - users = db.query("SELECT * FROM users WHERE active = true") - - # You can add more attributes during execution - span.set_attribute("result_count", len(users)) -``` - You can also add attributes to an existing span: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk # Get the current span @@ -291,22 +150,42 @@ if span: span.set_data("request_size", len(request.body)) ``` -```python {tabTitle:Stream Mode} +### Adding Attributes to All Spans + +To add attributes to all spans, use the `before_send_transaction` callback: + +```python import sentry_sdk +from sentry_sdk.types import Event, Hint -# Get the current span -span = sentry_sdk.traces.get_current_span() -if span: - # Set individual attributes - span.set_attribute("user_id", user.id) - span.set_attribute("request_size", len(request.body)) -``` +def before_send_transaction(event: Event, hint: Hint) -> Event | None: + # Add attributes to the root span (transaction) + if "trace" in event.get("contexts", {}): + if "data" not in event["contexts"]["trace"]: + event["contexts"]["trace"]["data"] = {} -### Adding Attributes to All Spans + event["contexts"]["trace"]["data"].update({ + "app_version": "1.2.3", + "environment_region": "us-west-2" + }) + + # Add attributes to all child spans + for span in event.get("spans", []): + if "data" not in span: + span["data"] = {} - + span["data"].update({ + "component_version": "2.0.0", + "deployment_stage": "production" + }) -Note that `before_send_span` can only modify span data and not drop spans – use [`ignore_spans`](#dropping-spans) instead. + return event + +sentry_sdk.init( + # ... + before_send_transaction=before_send_transaction +) +``` ### Adding Span Operations ("op") @@ -314,7 +193,7 @@ Spans can have an operation associated with them, which helps Sentry understand Sentry maintains a [list of well-known span operations](https://develop.sentry.dev/sdk/performance/span-operations/#list-of-operations) that you should use when applicable: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk # HTTP client operation @@ -334,41 +213,11 @@ with sentry_sdk.start_span(op="file.read", name="Read Config"): config = json.load(f) ``` -```python {tabTitle:Stream Mode} -import sentry_sdk - -# HTTP client operation -with sentry_sdk.traces.start_span( - name="Fetch User Data", - attributes={"sentry.op": "http.client"}, -): - response = requests.get("https://api.example.com/users") - -# Database operation -with sentry_sdk.traces.start_span( - name="Save User", - attributes={"sentry.op": "db"}, -): - db.execute( - "INSERT INTO users (name, email) VALUES (%s, %s)", - (user.name, user.email), - ) - -# File I/O operation -with sentry_sdk.traces.start_span( - name="Read Config", - attributes={"sentry.op": "file.read"}, -): - with open("config.json", "r") as f: - config = json.load(f) -``` - ### Updating the Span Status -You can update the status of a span to indicate whether it succeeded or failed. -In stream mode, status can only be `ok` (default) or `error`. +You can update the status of a span to indicate whether it succeeded or failed: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk with sentry_sdk.start_span(op="task", name="Process Payment") as span: @@ -385,28 +234,3 @@ with sentry_sdk.start_span(op="task", name="Process Payment") as span: # Span will automatically be marked as failed when an exception occurs raise ``` - -```python {tabTitle:Stream Mode} -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="Process Payment", - attributes={"sentry.op": "task"}, -) as span: - try: - result = process_payment(payment_id) - if result.success: - # Mark the span as successful (this is also the default) - span.status = "ok" - else: - # Mark the span as failed - span.status = "error" - span.set_attribute("error_reason", result.error) - except Exception: - # Span will automatically be marked as failed when an exception occurs - raise -``` - -## Dropping Spans - - diff --git a/docs/platforms/python/tracing/span-metrics/examples.mdx b/docs/platforms/python/tracing/span-metrics/examples.mdx index 5983244ac7568c..5fe2452ebcc23c 100644 --- a/docs/platforms/python/tracing/span-metrics/examples.mdx +++ b/docs/platforms/python/tracing/span-metrics/examples.mdx @@ -12,10 +12,6 @@ These examples assume you have already set up traci This guide provides practical examples of using span attributes and metrics to solve common monitoring and debugging challenges in Python applications. Each example demonstrates how to instrument different components, showing how they work together within a distributed trace to provide end-to-end visibility. - - This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - ## File Upload and Processing Pipeline **Challenge:** Understanding bottlenecks and failures in multi-step file processing operations across request handling and processing services. @@ -23,71 +19,7 @@ This guide provides practical examples of using span attributes and metrics to s **Solution:** Track the entire file processing pipeline with detailed metrics at each stage, from initial file handling through processing and storage. **Client Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} -# File upload request handling -import sentry_sdk -import time -from pathlib import Path -import requests -from contextlib import contextmanager - -def upload_file(): - with sentry_sdk.start_span(op="upload", name="Upload File") as span: - try: - # Begin upload process with the requests library - file_path = Path("/path/to/uploads/user-profile.jpg") - - # Track progress with a context manager - @contextmanager - def track_progress(total_size): - start = time.time() - bytes_sent = 0 - - class ProgressTracker: - def __call__(self, monitor): - nonlocal bytes_sent - bytes_sent = monitor.bytes_read - progress_percent = (bytes_sent / total_size) * 100 - span.set_data("upload.percent_complete", progress_percent) - span.set_data("upload.bytes_transferred", bytes_sent) - - yield ProgressTracker() - - # Record total time after context exits - span.set_data("upload.total_time_ms", (time.time() - start) * 1000) - - # Use the progress tracker with requests - with track_progress(file_path.stat().st_size) as progress_callback: - with open(file_path, "rb") as f: - response = requests.post( - "https://api.example.com/upload", - files={"file": f}, - headers={"X-Sentry-Trace": sentry_sdk.get_traceparent()}, # Propagate trace context - stream=True, - hooks={"response": progress_callback} - ) - - # Set final data after completion - span.set_data("upload.success", response.ok) - if response.ok: - result = response.json() - span.set_data("upload.server_file_id", result["file_id"]) - - return response - - except Exception as error: - # Record failure information - span.set_data("upload.success", False) - span.set_data("upload.error_type", error.__class__.__name__) - span.set_data("upload.error_message", str(error)) - span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) - raise - -upload_file() -``` - -```python {tabTitle:Stream Mode} +```python # File upload request handling import sentry_sdk import time @@ -95,71 +27,63 @@ from pathlib import Path import requests from contextlib import contextmanager -def upload_file(): - with sentry_sdk.traces.start_span( - name="Upload File", - attributes={"sentry.op": "upload"}, - ) as span: - try: - # Begin upload process with the requests library - file_path = Path("/path/to/uploads/user-profile.jpg") - - # Track progress with a context manager - @contextmanager - def track_progress(total_size): - start = time.time() - bytes_sent = 0 - - class ProgressTracker: - def __call__(self, monitor): - nonlocal bytes_sent - bytes_sent = monitor.bytes_read - progress_percent = (bytes_sent / total_size) * 100 - span.set_attribute("upload.percent_complete", progress_percent) - span.set_attribute("upload.bytes_transferred", bytes_sent) - - yield ProgressTracker() - - # Record total time after context exits - span.set_attribute("upload.total_time_ms", (time.time() - start) * 1000) - - # Use the progress tracker with requests - with track_progress(file_path.stat().st_size) as progress_callback: - with open(file_path, "rb") as f: - response = requests.post( - "https://api.example.com/upload", - files={"file": f}, - headers={"X-Sentry-Trace": sentry_sdk.get_traceparent()}, # Propagate trace context - stream=True, - hooks={"response": progress_callback} - ) - - # Set final data after completion - span.set_attribute("upload.success", response.ok) - if response.ok: - result = response.json() - span.set_attribute("upload.server_file_id", result["file_id"]) - - return response - - except Exception as error: - # Record failure information - span.set_attribute("upload.success", False) - span.set_attribute("upload.error_type", error.__class__.__name__) - span.set_attribute("upload.error_message", str(error)) - span.status = "error" - raise - -upload_file() +with sentry_sdk.start_span(op="upload", name="Upload File") as span: + try: + # Begin upload process with the requests library + file_path = Path("/path/to/uploads/user-profile.jpg") + + # Track progress with a context manager + @contextmanager + def track_progress(total_size): + start = time.time() + bytes_sent = 0 + + class ProgressTracker: + def __call__(self, monitor): + nonlocal bytes_sent + bytes_sent = monitor.bytes_read + progress_percent = (bytes_sent / total_size) * 100 + span.set_data("upload.percent_complete", progress_percent) + span.set_data("upload.bytes_transferred", bytes_sent) + + yield ProgressTracker() + + # Record total time after context exits + span.set_data("upload.total_time_ms", (time.time() - start) * 1000) + + # Use the progress tracker with requests + with track_progress(file_path.stat().st_size) as progress_callback: + with open(file_path, "rb") as f: + response = requests.post( + "https://api.example.com/upload", + files={"file": f}, + headers={"X-Sentry-Trace": sentry_sdk.get_traceparent()}, # Propagate trace context + stream=True, + hooks={"response": progress_callback} + ) + + # Set final data after completion + span.set_data("upload.success", response.ok) + if response.ok: + result = response.json() + span.set_data("upload.server_file_id", result["file_id"]) + + return response + + except Exception as error: + # Record failure information + span.set_data("upload.success", False) + span.set_data("upload.error_type", error.__class__.__name__) + span.set_data("upload.error_message", str(error)) + span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) + raise ``` **Server Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} +```python # File processing service import sentry_sdk from pathlib import Path -import time import boto3 with sentry_sdk.start_span( @@ -168,7 +92,7 @@ with sentry_sdk.start_span( ) as span: # File processing implementation file_path = Path("/tmp/uploads/user-profile.jpg") - + # Process the file try: # Track individual processing steps @@ -176,7 +100,7 @@ with sentry_sdk.start_span( # Virus scan implementation scan_span.set_data("scan.engine", "clamav") scan_span.set_data("scan.result", "clean") - + # Upload to S3 s3_client = boto3.client('s3', region_name='us-west-2') upload_start = time.time() @@ -185,63 +109,16 @@ with sentry_sdk.start_span( 'my-bucket', 'uploads/user-profile.jpg' ) - - span.set_data( - "storage.actual_upload_time_ms", - (time.time() - upload_start) * 1000 - ) - + + span.set_data("storage.actual_upload_time_ms", + (time.time() - upload_start) * 1000) + except Exception as e: span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) span.set_data("error.message", str(e)) raise ``` -```python {tabTitle:Stream Mode} -# File processing service -import sentry_sdk -from pathlib import Path -import time -import boto3 - -with sentry_sdk.traces.start_span( - name="File Processing Service", - attributes={"sentry.op": "file.process.service"}, -) as span: - # File processing implementation - file_path = Path("/tmp/uploads/user-profile.jpg") - - # Process the file - try: - # Track individual processing steps - with sentry_sdk.traces.start_span( - name="Virus Scan", - attributes={"sentry.op": "scan"}, - ) as scan_span: - # Virus scan implementation - scan_span.set_attribute("scan.engine", "clamav") - scan_span.set_attribute("scan.result", "clean") - - # Upload to S3 - s3_client = boto3.client('s3', region_name='us-west-2') - upload_start = time.time() - s3_client.upload_file( - str(file_path), - 'my-bucket', - 'uploads/user-profile.jpg' - ) - - span.set_attribute( - "storage.actual_upload_time_ms", - (time.time() - upload_start) * 1000 - ) - - except Exception as e: - span.status = "error" - span.set_attribute("error.message", str(e)) - raise -``` - **How the Trace Works Together:** The client application span initiates the trace and handles the file upload. It propagates the trace context to the server through the request headers. The server span continues the trace, processing the file and storing it. This creates a complete picture of the file's journey, allowing you to: @@ -257,26 +134,25 @@ The client application span initiates the trace and handles the file upload. It **Solution:** Tracking of the entire LLM interaction flow, from initial request through response processing. **Client Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} +```python # LLM request handling in a Flask application import sentry_sdk import time import openai -from flask import jsonify, request +from flask import jsonify @app.route("/ask", methods=["POST"]) def handle_llm_request(): with sentry_sdk.start_span(op="gen_ai.chat", name="Generate Text") as span: start_time = time.time() * 1000 # Convert to milliseconds - + # Begin streaming response from LLM API user_input = request.json["question"] - + response_chunks = [] first_token_received = False tokens_received = 0 - + # Using OpenAI's streaming API try: for chunk in openai.ChatCompletion.create( @@ -287,25 +163,25 @@ def handle_llm_request(): tokens_received += 1 content = chunk.get("choices", [{}])[0].get("delta", {}).get("content", "") response_chunks.append(content) - + # Record time to first token if not first_token_received and content: first_token_received = True time_to_first_token = (time.time() * 1000) - start_time span.set_data("response.time_to_first_token_ms", time_to_first_token) - + # Record final metrics after stream completes total_request_time = (time.time() * 1000) - start_time - + span.set_data("response.total_time_ms", total_request_time) span.set_data("response.format", "text") span.set_data("response.tokens_received", tokens_received) - + return jsonify({ "response": "".join(response_chunks), "tokens": tokens_received }) - + except Exception as error: span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) span.set_data("error.type", error.__class__.__name__) @@ -313,69 +189,8 @@ def handle_llm_request(): return jsonify({"error": str(error)}), 500 ``` -```python {tabTitle:Stream Mode} -# LLM request handling in a Flask application -import sentry_sdk -import time -import openai -from flask import jsonify, request - -@app.route("/ask", methods=["POST"]) -def handle_llm_request(): - with sentry_sdk.traces.start_span( - name="Generate Text", - attributes={"sentry.op": "gen_ai.chat"}, - ) as span: - start_time = time.time() * 1000 # Convert to milliseconds - - # Begin streaming response from LLM API - user_input = request.json["question"] - - response_chunks = [] - first_token_received = False - tokens_received = 0 - - # Using OpenAI's streaming API - try: - for chunk in openai.ChatCompletion.create( - model="gpt-4", - messages=[{"role": "user", "content": user_input}], - stream=True - ): - tokens_received += 1 - content = chunk.get("choices", [{}])[0].get("delta", {}).get("content", "") - response_chunks.append(content) - - # Record time to first token - if not first_token_received and content: - first_token_received = True - time_to_first_token = (time.time() * 1000) - start_time - span.set_attribute("response.time_to_first_token_ms", time_to_first_token) - - # Record final metrics after stream completes - total_request_time = (time.time() * 1000) - start_time - - span.set_attributes({ - "response.total_time_ms": total_request_time, - "response.format": "text", - "response.tokens_received": tokens_received - }) - - return jsonify({ - "response": "".join(response_chunks), - "tokens": tokens_received - }) - - except Exception as error: - span.status = "error" - span.set_attribute("error.type", error.__class__.__name__) - span.set_attribute("error.message", str(error)) - return jsonify({"error": str(error)}), 500 -``` - **Server Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} +```python # LLM processing service (e.g., in a separate microservice) import sentry_sdk import time @@ -387,15 +202,15 @@ def process_llm_request(request_data): name="Generate Text" ) as span: start_time = int(time.time() * 1000) # Current time in milliseconds - + try: # Check rate limits before processing rate_limits = check_rate_limits() span.set_data("llm.rate_limit_remaining", rate_limits["remaining"]) - + # Prepare the prompt with additional context prepared_prompt = enhance_prompt(request_data["question"]) - + # Make the actual API call to the LLM provider response = openai.ChatCompletion.create( model="gpt-4", @@ -404,13 +219,13 @@ def process_llm_request(request_data): temperature=0.7, max_tokens=4096 ) - + # Track token usage and performance metrics span.set_data("llm.prompt_tokens", response.usage.prompt_tokens) span.set_data("llm.completion_tokens", response.usage.completion_tokens) span.set_data("llm.total_tokens", response.usage.total_tokens) span.set_data("llm.api_latency_ms", int(time.time() * 1000) - start_time) - + # Calculate and record cost based on token usage cost = calculate_cost( response.usage.prompt_tokens, @@ -418,88 +233,23 @@ def process_llm_request(request_data): "gpt-4" ) span.set_data("llm.cost_usd", cost) - + return { "response": response.choices[0].message.content, "usage": response.usage } - + except Exception as error: # Track error information span.set_data("error", True) span.set_data("error.type", error.__class__.__name__) span.set_data("error.message", str(error)) - + # Check if it's a rate limit error is_rate_limit = "rate_limit" in str(error).lower() span.set_data("error.is_rate_limit", is_rate_limit) span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) - - raise -``` - -```python {tabTitle:Stream Mode} -# LLM processing service (e.g., in a separate microservice) -import sentry_sdk -import time -import openai - -def process_llm_request(request_data): - with sentry_sdk.traces.start_span( - name="Generate Text", - attributes={"sentry.op": "gen_ai.chat"}, - ) as span: - start_time = int(time.time() * 1000) # Current time in milliseconds - - try: - # Check rate limits before processing - rate_limits = check_rate_limits() - span.set_attribute("llm.rate_limit_remaining", rate_limits["remaining"]) - - # Prepare the prompt with additional context - prepared_prompt = enhance_prompt(request_data["question"]) - - # Make the actual API call to the LLM provider - response = openai.ChatCompletion.create( - model="gpt-4", - messages=[{"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": prepared_prompt}], - temperature=0.7, - max_tokens=4096 - ) - - # Track token usage and performance metrics - span.set_attributes({ - "llm.prompt_tokens": response.usage.prompt_tokens, - "llm.completion_tokens": response.usage.completion_tokens, - "llm.total_tokens": response.usage.total_tokens, - "llm.api_latency_ms": int(time.time() * 1000) - start_time - }) - - # Calculate and record cost based on token usage - cost = calculate_cost( - response.usage.prompt_tokens, - response.usage.completion_tokens, - "gpt-4" - ) - span.set_attribute("llm.cost_usd", cost) - - return { - "response": response.choices[0].message.content, - "usage": response.usage - } - - except Exception as error: - # Track error information - span.set_attribute("error", True) - span.set_attribute("error.type", error.__class__.__name__) - span.set_attribute("error.message", str(error)) - - # Check if it's a rate limit error - is_rate_limit = "rate_limit" in str(error).lower() - span.set_attribute("error.is_rate_limit", is_rate_limit) - span.status = "error" - + raise ``` @@ -519,8 +269,7 @@ The client application span captures the initial request handling, while the ser **Solution:** Track the full transaction process from API request to order fulfillment. **Client Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} +```python # Django view handling checkout request import sentry_sdk import time @@ -533,28 +282,28 @@ class CheckoutView(View): # Validate the checkout request validation_start = time.time() validation_result = self.validate_checkout_data(request.POST) - span.set_data("request.validation_time_ms", + span.set_data("request.validation_time_ms", (time.time() - validation_start) * 1000) - + if not validation_result["valid"]: span.set_data("request.validation_success", False) span.set_data("request.validation_errors", validation_result["errors"]) return JsonResponse({"errors": validation_result["errors"]}, status=400) - + # Process the order try: order_result = self.process_order(request) - + # Update span with order results span.set_data("order.id", order_result["order_id"]) span.set_data("order.success", True) - + # Clear the cart and return success request.session["cart"] = [] request.session["cart_total"] = 0 - + return JsonResponse({"order_id": order_result["order_id"]}) - + except Exception as e: span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) span.set_data("order.success", False) @@ -562,58 +311,11 @@ class CheckoutView(View): return JsonResponse({"error": str(e)}, status=500) ``` -```python {tabTitle:Stream Mode} -# Django view handling checkout request -import sentry_sdk -import time -from django.views import View -from django.http import JsonResponse - -class CheckoutView(View): - def post(self, request): - with sentry_sdk.traces.start_span( - name="Process Order", - attributes={"sentry.op": "order"}, - ) as span: - # Validate the checkout request - validation_start = time.time() - validation_result = self.validate_checkout_data(request.POST) - span.set_attribute("request.validation_time_ms", - (time.time() - validation_start) * 1000) - - if not validation_result["valid"]: - span.set_attribute("request.validation_success", False) - span.set_attribute("request.validation_errors", validation_result["errors"]) - return JsonResponse({"errors": validation_result["errors"]}, status=400) - - # Process the order - try: - order_result = self.process_order(request) - - # Update span with order results - span.set_attribute("order.id", order_result["order_id"]) - span.set_attribute("order.success", True) - - # Clear the cart and return success - request.session["cart"] = [] - request.session["cart_total"] = 0 - - return JsonResponse({"order_id": order_result["order_id"]}) - - except Exception as e: - span.status = "error" - span.set_attribute("order.success", False) - span.set_attribute("error.message", str(e)) - return JsonResponse({"error": str(e)}, status=500) -``` - **Server Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} +```python # Order processing service import sentry_sdk import stripe -import time from decimal import Decimal def process_order(order_data): @@ -625,15 +327,15 @@ def process_order(order_data): # Check inventory availability inventory_start = time.time() inventory_result = check_inventory(order_data["items"]) - span.set_data("inventory.check_time_ms", + span.set_data("inventory.check_time_ms", (time.time() - inventory_start) * 1000) span.set_data("inventory.all_available", inventory_result["all_available"]) - + if not inventory_result["all_available"]: - span.set_data("inventory.unavailable_items", + span.set_data("inventory.unavailable_items", inventory_result["unavailable_items"]) raise ValueError("Some items are out of stock") - + # Process payment via Stripe payment_start = time.time() stripe.api_key = "sk_test_..." @@ -643,27 +345,27 @@ def process_order(order_data): payment_method=order_data["payment_method_id"], confirm=True ) - - span.set_data("payment.processing_time_ms", + + span.set_data("payment.processing_time_ms", (time.time() - payment_start) * 1000) span.set_data("payment.transaction_id", payment_intent.id) span.set_data("payment.success", payment_intent.status == "succeeded") - + # Create fulfillment record fulfillment = create_fulfillment(order_data["order_id"]) span.set_data("fulfillment.id", fulfillment["id"]) span.set_data("fulfillment.warehouse", fulfillment["warehouse"]) span.set_data("fulfillment.shipping_method", fulfillment["shipping_method"]) - span.set_data("fulfillment.estimated_delivery", + span.set_data("fulfillment.estimated_delivery", fulfillment["estimated_delivery"].isoformat()) - + return { "success": True, "order_id": order_data["order_id"], "payment_id": payment_intent.id, "fulfillment_id": fulfillment["id"] } - + except Exception as e: span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) span.set_data("error.message", str(e)) @@ -671,70 +373,6 @@ def process_order(order_data): raise ``` -```python {tabTitle:Stream Mode} -# Order processing service -import sentry_sdk -import stripe -import time -from decimal import Decimal - -def process_order(order_data): - with sentry_sdk.traces.start_span( - name="Check Inventory", - attributes={"sentry.op": "inventory"}, - ) as span: - try: - # Check inventory availability - inventory_start = time.time() - inventory_result = check_inventory(order_data["items"]) - span.set_attribute("inventory.check_time_ms", - (time.time() - inventory_start) * 1000) - span.set_attribute("inventory.all_available", inventory_result["all_available"]) - - if not inventory_result["all_available"]: - span.set_attribute("inventory.unavailable_items", - inventory_result["unavailable_items"]) - raise ValueError("Some items are out of stock") - - # Process payment via Stripe - payment_start = time.time() - stripe.api_key = "sk_test_..." - payment_intent = stripe.PaymentIntent.create( - amount=int(Decimal(order_data["total"]) * 100), # Convert to cents - currency="usd", - payment_method=order_data["payment_method_id"], - confirm=True - ) - - span.set_attributes({ - "payment.processing_time_ms": (time.time() - payment_start) * 1000, - "payment.transaction_id": payment_intent.id, - "payment.success": payment_intent.status == "succeeded" - }) - - # Create fulfillment record - fulfillment = create_fulfillment(order_data["order_id"]) - span.set_attributes({ - "fulfillment.id": fulfillment["id"], - "fulfillment.warehouse": fulfillment["warehouse"], - "fulfillment.shipping_method": fulfillment["shipping_method"], - "fulfillment.estimated_delivery": fulfillment["estimated_delivery"].isoformat() - }) - - return { - "success": True, - "order_id": order_data["order_id"], - "payment_id": payment_intent.id, - "fulfillment_id": fulfillment["id"] - } - - except Exception as e: - span.status = "error" - span.set_attribute("error.message", str(e)) - span.set_attribute("order.success", False) - raise -``` - **How the Trace Works Together:** The client application span tracks the initial order request, while the server span handles order processing and fulfillment. The distributed trace provides visibility into the entire purchase flow, allowing you to: @@ -751,14 +389,11 @@ The client application span tracks the initial order request, while the server s **Solution:** Comprehensive tracking of job lifecycle across queue management, processing stages, and worker performance. **Client Application Instrumentation:** - -```python {tabTitle:Transaction Mode (Default)} +```python # Celery task submission import sentry_sdk from celery import shared_task import time -import psutil -import os from datetime import datetime, timedelta def submit_processing_job(data_file_path, priority="medium"): @@ -771,20 +406,20 @@ def submit_processing_job(data_file_path, priority="medium"): kwargs={"priority": priority}, queue="data_processing" ) - + span.set_data("job.id", task.id) span.set_data("job.submission_success", True) - + # Start monitoring task progress monitoring_result = setup_task_monitoring(task.id) span.set_data("monitor.callback_url", monitoring_result["callback_url"]) - + return { "job_id": task.id, "status": "submitted", "monitoring_url": monitoring_result["status_url"] } - + except Exception as e: span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) span.set_data("job.submission_success", False) @@ -801,36 +436,34 @@ def process_data_file(file_path, priority="medium"): # Processing implementation with stage tracking span.set_data("processing.current_stage", "parse") data = parse_data_file(file_path) - + span.set_data("processing.current_stage", "transform") transformed_data = transform_data(data) - + span.set_data("processing.current_stage", "validate") validation_result = validate_data(transformed_data) span.set_data("validation.errors_count", len(validation_result["errors"])) - + span.set_data("processing.current_stage", "export") export_result = export_processed_data(transformed_data) - + # Record resource utilization span.set_data("resource.cpu_percent", psutil.cpu_percent()) - span.set_data( - "resource.memory_used_mb", - psutil.Process().memory_info().rss / (1024 * 1024) - ) - + span.set_data("resource.memory_used_mb", + psutil.Process().memory_info().rss / (1024 * 1024)) + # Update final job outcome span.set_data("outcome.status", "completed") span.set_data("outcome.records_processed", len(data)) - span.set_data("outcome.output_size_bytes", + span.set_data("outcome.output_size_bytes", os.path.getsize(export_result["output_path"])) - + return { "success": True, "records_processed": len(data), "output_path": export_result["output_path"] } - + except Exception as e: span.set_status(sentry_sdk.consts.SPANSTATUS.INTERNAL_ERROR) span.set_data("outcome.status", "failed") @@ -839,96 +472,6 @@ def process_data_file(file_path, priority="medium"): raise ``` -```python {tabTitle:Stream Mode} -# Celery task submission -import sentry_sdk -from celery import shared_task -import time -import psutil -import os -from datetime import datetime, timedelta - -def submit_processing_job(data_file_path, priority="medium"): - with sentry_sdk.traces.start_span( - name="Process Job", - attributes={"sentry.op": "job"}, - ) as span: - # Submit job to Celery - try: - # Configure task and submit - task = process_data_file.apply_async( - args=[str(data_file_path)], - kwargs={"priority": priority}, - queue="data_processing" - ) - - span.set_attribute("job.id", task.id) - span.set_attribute("job.submission_success", True) - - # Start monitoring task progress - monitoring_result = setup_task_monitoring(task.id) - span.set_attribute("monitor.callback_url", monitoring_result["callback_url"]) - - return { - "job_id": task.id, - "status": "submitted", - "monitoring_url": monitoring_result["status_url"] - } - - except Exception as e: - span.status = "error" - span.set_attribute("job.submission_success", False) - span.set_attribute("error.message", str(e)) - raise - -@shared_task(name="tasks.process_data_file") -def process_data_file(file_path, priority="medium"): - with sentry_sdk.traces.start_span( - name="Process Data", - attributes={"sentry.op": "process"}, - ) as span: - try: - # Processing implementation with stage tracking - span.set_attribute("processing.current_stage", "parse") - data = parse_data_file(file_path) - - span.set_attribute("processing.current_stage", "transform") - transformed_data = transform_data(data) - - span.set_attribute("processing.current_stage", "validate") - validation_result = validate_data(transformed_data) - span.set_attribute("validation.errors_count", len(validation_result["errors"])) - - span.set_attribute("processing.current_stage", "export") - export_result = export_processed_data(transformed_data) - - # Record resource utilization - span.set_attribute("resource.cpu_percent", psutil.cpu_percent()) - span.set_attribute( - "resource.memory_used_mb", - psutil.Process().memory_info().rss / (1024 * 1024) - ) - - # Update final job outcome - span.set_attributes({ - "outcome.status": "completed", - "outcome.records_processed": len(data), - "outcome.output_size_bytes": os.path.getsize(export_result["output_path"]) - }) - - return { - "success": True, - "records_processed": len(data), - "output_path": export_result["output_path"] - } - - except Exception as e: - span.status = "error" - span.set_attribute("outcome.status", "failed") - span.set_attribute("error.message", str(e)) - raise -``` - **How the Trace Works Together:** This example shows both the job submission and processing as a single trace, which is common in Celery/distributed task patterns. The spans track the entire job lifecycle, enabling you to: diff --git a/docs/platforms/python/tracing/span-metrics/index.mdx b/docs/platforms/python/tracing/span-metrics/index.mdx index 9f574462973240..4483c5c3be8344 100644 --- a/docs/platforms/python/tracing/span-metrics/index.mdx +++ b/docs/platforms/python/tracing/span-metrics/index.mdx @@ -17,17 +17,11 @@ Span metrics allow you to extend the default metrics that are collected by traci 1. [Adding metrics to existing spans](#adding-metrics-to-existing-spans) 2. [Creating dedicated spans with custom metrics](#creating-dedicated-metric-spans) - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## Adding Metrics to Existing Spans You can enhance existing spans with custom metrics by adding data. This is useful when you want to augment automatic instrumentation or add contextual data to spans you've already created. -```python {tabTitle:Transaction Mode (Default)} +```python span = sentry_sdk.get_current_span() if span: # Add individual metrics @@ -38,17 +32,6 @@ if span: span.set_data("processing.duration_ms", 127) ``` -```python {tabTitle:Stream Mode} -span = sentry_sdk.traces.get_current_span() -if span: - # Add individual metrics - span.set_attribute("database.rows_affected", 42) - span.set_attribute("cache.hit_rate", 0.85) - span.set_attribute("memory.heap_used", 1024000) - span.set_attribute("queue.length", 15) - span.set_attribute("processing.duration_ms", 127) -``` - ### Best Practices for Span Data When adding metrics as span data: @@ -61,7 +44,7 @@ When adding metrics as span data: For more detailed operations, tasks, or process tracking, you can create custom dedicated spans that focus on specific metrics or attributes that you want to track. This approach provides better discoverability and more precise span configurations, however it can also create more noise in your trace waterfall. -```python {tabTitle:Transaction Mode (Default)} +```python with sentry_sdk.start_span( op="db.metrics", name="Database Query Metrics" @@ -76,27 +59,43 @@ with sentry_sdk.start_span( pass ``` -```python {tabTitle:Stream Mode} -with sentry_sdk.traces.start_span( - name="Database Query Metrics", - attributes={"sentry.op": "db.metrics"}, -) as span: - # Set metrics after creating the span - span.set_attributes({ - "db.query_type": "SELECT", - "db.table": "users", - "db.execution_time_ms": 45, - "db.rows_returned": 100, - "db.connection_pool_size": 5, - }) - # Your database operation here - pass -``` - For detailed examples of how to implement span metrics in common scenarios, see our Span Metrics Examples guide. ## Adding Metrics to All Spans - +To consistently add metrics across all spans in your application, you can use the `before_send_transaction` callback: + +```python +import sentry_sdk +from sentry_sdk.types import Event, Hint + +def before_send_transaction(event: Event, hint: Hint) -> Event | None: + # Add metrics to the root span + if "trace" in event.get("contexts", {}): + if "data" not in event["contexts"]["trace"]: + event["contexts"]["trace"]["data"] = {} + + event["contexts"]["trace"]["data"].update({ + "app.version": "1.2.3", + "environment.region": "us-west-2" + }) + + # Add metrics to all child spans + for span in event.get("spans", []): + if "data" not in span: + span["data"] = {} + + span["data"].update({ + "app.component_version": "2.0.0", + "app.deployment_stage": "production" + }) + + return event + +sentry_sdk.init( + # ... + before_send_transaction=before_send_transaction +) +``` For detailed examples of how to implement span metrics in common scenarios, see our Span Metrics Examples guide. diff --git a/docs/platforms/python/tracing/streamed-spans/index.mdx b/docs/platforms/python/tracing/streamed-spans/index.mdx deleted file mode 100644 index b0744bf695e29c..00000000000000 --- a/docs/platforms/python/tracing/streamed-spans/index.mdx +++ /dev/null @@ -1,415 +0,0 @@ ---- -title: Streamed Spans -description: "Learn how to use stream mode to send spans to Sentry as they finish, removing the 1,000-span limit and making trace data visible sooner." -sidebar_order: 45 -new: true ---- - -By default, the Sentry Python SDK collects all spans in memory and sends them to Sentry as a single transaction once the root span ends. This is called transaction mode. -Stream mode changes this by sending spans to Sentry in batches as they finish. Service spans, which represent a service's entry point, replace transactions as the main grouping for each service. - - - -- **No 1,000-span limit.** In transaction mode, transactions are capped at 1,000 spans. Stream mode has no upper limit since spans are sent in batches. This is especially beneficial for complex generative AI pipelines that easily exceed standard span limits. -- **Lower memory usage.** Spans are flushed periodically and don't need to be held in memory until the root span ends. This is especially useful for long-running processes like queue consumers or cron jobs. -- **Faster visibility.** Span data arrives in Sentry as your application runs, instead of only after the entire operation completes. -- **Fewer spans lost to crashes.** If your process terminates unexpectedly, spans that were already flushed are emitted normally. In transaction mode, a crash before the transaction ends means all span data is lost. Stream mode emits spans incrementally as they finish, so more of them survive a crash. - - - -You can find the following span types mentioned throughout this page: - -- **Root span**: The topmost span in a trace. It has no parent span and is always a service span. -- **Service span**: A parent-level span at the entry of a service. In transaction mode, this is called a transaction. -- **Child span**: Any span nested under a parent span within the same trace. - -This graph shows how these span types relate to each other within a trace: - -``` -Trace -│ -└── Root span [service A] - ├── Child span - │ └── Child span - └── Service span [service B] - ├── Child span - └── Child span -``` - - - -Stream mode requires the streamed Span API, so migrating to stream mode and migrating to the streamed Span API are the same step. If you have existing custom instrumentation, see the Migration Guide for a full list of changes. - - - -## Prerequisites - -You need: - -- Tracing configured in - your app -- Sentry SDK `>=2.62.0` - -## Enable Stream Mode - - - - - -Opt in by adding `trace_lifecycle` to the `_experiments` option when initializing the SDK: - - - - -```python -import sentry_sdk - -sentry_sdk.init( - dsn="___PUBLIC_DSN___", - traces_sample_rate=1.0, - _experiments={ - # enables stream mode - "trace_lifecycle": "stream", - }, -) - -``` - - - - - -To revert to transaction mode, remove the `_experiments` option or set `trace_lifecycle` to `"static"` (the default). - - - -When stream mode is enabled, the SDK maintains an internal buffer that groups spans by trace ID. - -Spans are flushed: - -- On a regular interval (every 5 seconds by default). -- When a trace's buffer reaches 1,000 spans. -- When the SDK shuts down. - -Each flush sends only the spans accumulated since the last flush, grouped into envelopes by trace ID. - - - -## Manual Instrumentation (Optional) - -### Start a Span - - - - - -Use `sentry_sdk.traces.start_span()` to create a span that ends automatically when the `with` block exits: - - - - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span(name="my-operation") as span: - # Your code here - do_work() -``` - - - - - - -Child spans created inside an active span are automatically associated with the parent: - - - - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span(name="parent-operation"): - with sentry_sdk.traces.start_span(name="child-step-1"): - step_one() - - with sentry_sdk.traces.start_span(name="child-step-2"): - step_two() -``` - - - - - - -A span is automatically promoted to a service span (the equivalent of a transaction) if no other parent is currently active. -If you want to force a new service span, regardless of whether it has a parent span, set `parent_span=None`: - - - - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span(name="task-name", parent_span=None) as span: - do_work() -``` - - - - - - -You can also use the `@trace` decorator to instrument a function. It accepts optional `name`, `attributes`, and `active` arguments: - - - - -```python -from sentry_sdk.traces import trace - -@trace(name="checkout", attributes={"flow.pipeline": "legacy"}) -def checkout(): - ... -``` - - - - - -For more details on span creation, see Custom Instrumentation. - -### Add Span Attributes - -Attach structured metadata to spans using `attributes`, which can be `str`, `int`, `float`, or `bool`, as well as arrays of these types. - - - -Sentry automatically sets several standard attributes on spans. To avoid accidentally overwriting these, refer to our Sentry Attribute Conventions. - - - - - - - -You can set attributes when starting a span: - - - - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span( - name="process-order", - attributes={ - "sentry.op": "queue.process", - "order.id": "abc-123", - "order.item_count": 5, - "order.priority": True, - }, -): - process_order() -``` - - - - - - - -Or add them to an already running span: - - - - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span(name="handle-request") as span: - # Set a single attribute - span.set_attribute("http.response.status_code", 200) - - # Set multiple attributes at once - span.set_attributes({ - "http.route": "/api/users", - "user.id": "user-42", - }) -``` - - - - - -Find more examples in our Sending Span Metrics documentation. - -## Distributed Tracing (Optional) - -### Continue a Trace - - - - - -When your service receives a request from an upstream service that includes Sentry trace headers, use `sentry_sdk.traces.continue_trace()` to connect your spans to the existing distributed trace. -Unlike the legacy `sentry_sdk.continue_trace()`, the new version is not a context manager. Instead, it sets the propagation context and the next span picks it up automatically. - - - - -```python -import sentry_sdk - -headers = { - "sentry-trace": "4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0be902b7-1", - "baggage": "sentry-trace_id=...", -} - -sentry_sdk.traces.continue_trace(headers) -with sentry_sdk.traces.start_span(name="handle request"): - ... -``` - - - - - -### Start a New Trace - - - - - -If you need to start a completely new trace unconnected to the current one, use `sentry_sdk.traces.new_trace()`. This is useful for background jobs or scheduled tasks where you want a clean trace boundary: - - - - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span(name="span in trace 1"): - ... - -sentry_sdk.traces.new_trace() - -with sentry_sdk.traces.start_span(name="span in trace 2"): - # This span is the root of a new, separate trace - ... -``` - - - - - -See Custom Trace Propagation for more information on distributed tracing. - -## Extended Configuration (Optional) - -### Filter Spans - - - - - -To modify or redact span data before it's sent, use `before_send_span` in the `_experiments` option: - - - - -```python -import sentry_sdk - -def postprocess_span(span, hint): - attributes_to_sanitize = [ - "http.request.header.custom-auth", - "http.request.header.custom-user-id", - ] - for attribute in attributes_to_sanitize: - if span["attributes"].get(attribute): - span["attributes"][attribute] = "[Sanitized]" - return span - -sentry_sdk.init( - dsn="___PUBLIC_DSN___", - traces_sample_rate=1.0, - _experiments={ - "trace_lifecycle": "stream", - "before_send_span": postprocess_span, - }, -) -``` - - - - - - - -`before_send_span` can only modify span data — you cannot use it to drop spans (use [`ignore_spans`](#drop-spans) instead). - - - -### Drop Spans - - - - - -To prevent specific spans from being created, use `ignore_spans` in the `_experiments` option. Rules are evaluated at span start, so only the span name and attributes set at creation time are taken into account. Rules can be strings, compiled regexes, or dictionaries with name and/or attributes conditions: - - - - -```python -import re -import sentry_sdk - -sentry_sdk.init( - dsn="___PUBLIC_DSN___", - traces_sample_rate=1.0, - _experiments={ - "trace_lifecycle": "stream", - "ignore_spans": [ - # String match against span name - "/health", - # Regex match against span name - re.compile(r"/flow/.*"), - # Match by attributes (all must match) - { - "attributes": { - "service.id": "15def9a", - "flow.pipeline": "legacy", - } - }, - # Match by name and attributes - { - "name": re.compile(r"/flow/.*"), - "attributes": { - "service.id": re.compile(r".*\.facade"), - }, - }, - ], - }, -) -``` - - - - - -If a matching span is a service span, all of its child spans are dropped as well. If a child span matches, only that span is dropped and its children are reparented to the nearest ancestor. - -## Sampling (Optional) - -If you use `traces_sample_rate`, no changes are needed — it works the same way in stream mode. - -If you use a custom `traces_sampler`, the shape of the sampling context is different in stream mode. See the Streamed Span API migration guide for details. - -## Verify Your Setup - -To make sure you've enabled stream mode successfully: - -- **Check the Sentry dashboard**: Spans should appear in the Traces view shortly after they complete. Traces look similar to transaction mode, but contain only spans and no transactions. -- **Check your logs**: If the SDK logs warnings about unsupported span operations, you may still be using the legacy Span API somewhere in your code. diff --git a/docs/platforms/python/tracing/streamed-spans/migration-guide.mdx b/docs/platforms/python/tracing/streamed-spans/migration-guide.mdx deleted file mode 100644 index 3db1510ac8a36c..00000000000000 --- a/docs/platforms/python/tracing/streamed-spans/migration-guide.mdx +++ /dev/null @@ -1,244 +0,0 @@ ---- -title: Migrate to Stream Mode -sidebar_order: 10 -description: "Learn how to migrate your custom instrumentation from transaction mode to stream mode." ---- - -Stream mode requires the streamed Span API. If you use custom instrumentation (creating spans manually, setting span data, or filtering spans) you'll need to update that code before you can switch to stream mode. This guide walks through the changes. - -For an introduction to stream mode itself, see Streamed Spans. - -## Enable Stream Mode - -Add `trace_lifecycle` to the `_experiments` option when initializing the SDK: - -```python diff -import sentry_sdk - -sentry_sdk.init( - dsn="___PUBLIC_DSN___", - traces_sample_rate=1.0, -+ _experiments={ -+ "trace_lifecycle": "stream", -+ }, -) -``` - -## Span Creation - -Replace `start_span`, `start_transaction`, and `start_child` with `sentry_sdk.traces.start_span()`. Whether the resulting span is a service span, a child span, or a sibling depends on the `parent_span` argument and what's currently active. - -```python diff -import sentry_sdk - -# Starting a span -- with sentry_sdk.start_span(op="http.client", description="GET /api/users") as span: -+ with sentry_sdk.traces.start_span( -+ name="GET /api/users", -+ attributes={"sentry.op": "http.client"}, -+ ) as span: - ... - -# Starting what used to be a transaction: pass parent_span=None to force a service span -- with sentry_sdk.start_transaction(name="flow.checkout") as transaction: -+ with sentry_sdk.traces.start_span(name="flow.checkout", parent_span=None) as span: - ... - -# Starting a child span: just start a span while the parent is active -- with parent.start_child(op="db", description="SELECT") as child: -+ with sentry_sdk.traces.start_span(name="SELECT", attributes={"sentry.op": "db"}): - ... - -# Starting a child span: if the span's parent should be a span that's currently not active, you can provide it explicitly -- with parent.start_child(op="db", description="SELECT") as child: -+ with sentry_sdk.traces.start_span(name="SELECT", attributes={"sentry.op": "db"}, parent_span=parent): - ... -``` - -A few argument changes come along with this: - -- `description` no longer exists — use `name` instead. -- `op` is no longer a dedicated argument — set it as the `sentry.op` attribute instead. - -If you use the `@trace` decorator, only the import changes: - -```python diff -- from sentry_sdk import trace -+ from sentry_sdk.traces import trace - -@trace -def checkout(): - ... -``` - -If your code imports `Span` or `Transaction` directly, for example for type annotations, replace both with `StreamedSpan`: - -```python diff -- from sentry_sdk.tracing import Span, Transaction -+ from sentry_sdk.traces import StreamedSpan - -- def process(span: Span) -> None: -+ def process(span: StreamedSpan) -> None: - ... -``` - -## Span Data - -In stream mode, spans have no contexts, data, or tags. Instead, everything is an attribute. -Replace `set_data()`, `set_tag()`, and `set_context()` with `set_attribute()` or `set_attributes()`: - -```python diff -- span.set_data("flow.step", "submit_payment") -- span.set_tag("http.status_code", 201) -+ span.set_attributes({ -+ "flow.step": "submit_payment", -+ "http.response.status_code": 201, -+ }) -``` - -Unlike the old methods, `set_attribute` only accepts primitive types (`str`, `int`, `float`, `bool`, or arrays of these). `None` isn't supported either. Flatten dictionaries into separate attributes, and stringify anything that can't be flattened: - -```python diff -- span.set_data("request", {"method": "POST", "path": "/api/checkout"}) -+ span.set_attributes({ -+ "request.method": "POST", -+ "request.path": "/api/checkout", -+ }) -``` - -Tags set on the scope with `sentry_sdk.set_tag()` aren't applied to spans in stream mode. Use `sentry_sdk.set_attribute()` to apply data to spans: - -```python -import sentry_sdk - -sentry_sdk.set_tag("region", "Europe") # applied to errors and other tag-supporting telemetry -sentry_sdk.set_attribute("region", "Europe") # applied to spans, logs, metrics -``` - -## Accessing the Current Span - -A few ways of referencing the current span or transaction change in stream mode: - -```python diff -import sentry_sdk - -# Getting the current span -- span = sentry_sdk.get_current_span() -+ span = sentry_sdk.traces.get_current_span() - -# Getting the current span via the scope -- scope = sentry_sdk.get_current_scope() -- current_span = scope.span -+ current_span = sentry_sdk.traces.get_current_span() - -``` - -If your code reads specific fields off the trace context, access them as direct properties instead of calling `get_trace_context()`, which no longer exists on streaming spans: - -```python diff -- ctx = span.get_trace_context() -- trace_id = ctx["trace_id"] -- span_id = ctx["span_id"] -+ trace_id = span.trace_id -+ span_id = span.span_id -``` - -## Trace Propagation - -`sentry_sdk.traces.continue_trace()` replaces the legacy `continue_trace()`. It's no longer a context manager — it sets the propagation context, and the next span you start picks it up automatically: - -```python diff -import sentry_sdk - -headers = { - "sentry-trace": "...", - "baggage": "...", -} - -- with sentry_sdk.continue_trace(headers) as transaction: -- ... -+ sentry_sdk.traces.continue_trace(headers) -+ with sentry_sdk.traces.start_span(name="handle request"): -+ ... -``` - -## Span Status - -Status can only be `ok` (default) or `error` in stream mode: - -```python -from sentry_sdk.traces import start_span - -with start_span(name="process") as span: - try: - ... - except Exception: - span.status = "error" -``` - -## Sampling - -If you use `traces_sample_rate`, no changes are needed. - -If you use a custom `traces_sampler`, the sampling context has a different structure in stream mode. Span details are available under `sampling_context["span_context"]`, which includes `name`, `trace_id`, `parent_span_id`, `parent_sampled`, and `attributes`: - -```python -import sentry_sdk - -def traces_sampler(sampling_context): - if sampling_context["span_context"]["name"] in IGNORED_SPAN_NAMES: - return 0.0 - return 1.0 - -sentry_sdk.init( - traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, -) -``` - -`custom_sampling_context` is no longer an argument to `start_span`. Set it on the scope instead, after `continue_trace` (which resets the propagation context) and before `start_span` (which is when sampling happens): - -```python diff -import sentry_sdk - -- with sentry_sdk.start_span( -- name="handle request", -- custom_sampling_context={"asgi_scope": asgi_scope}, -- ): -- ... -+ sentry_sdk.get_current_scope().set_custom_sampling_context({"asgi_scope": asgi_scope}) -+ with sentry_sdk.traces.start_span(name="handle request"): -+ ... -``` - -## Filtering and Dropping Spans - -`before_send_transaction` has no effect in stream mode, since spans are sent individually rather than batched into a transaction. Replace it with `ignore_spans` (to drop spans) and `before_send_span` (to modify them), both configured under `_experiments`: - -```python diff -import re -import sentry_sdk - -+ def my_span_processor(span, hint): -+ if span["attributes"].get("sentry.op") == "db.query": -+ span["name"] = "[filtered]" -+ return span - -sentry_sdk.init( - dsn="___PUBLIC_DSN___", - traces_sample_rate=1.0, -- before_send_transaction=my_filter, -+ _experiments={ -+ "trace_lifecycle": "stream", -+ "ignore_spans": [ -+ "/health", -+ re.compile(r"^GET /api/v1/internal"), -+ {"attributes": {"service.id": "15def9a"}}, -+ ], -+ "before_send_span": my_span_processor, -+ }, -) -``` - -`ignore_spans` only has access to the span name and attributes set at creation time — not attributes added later in the span's lifetime, like an HTTP status code set after the request completes. If your `before_send_transaction` logic was used to drop spans based on late-set data, this can't be replicated in stream mode. Consider server-side filtering with Sentry [inbound data filters](/concepts/data-management/filtering/) or [Relay](/product/relay/) rules instead. -`before_send_span` runs after the span ends and has access to all attributes set during the span's lifetime. diff --git a/docs/platforms/python/tracing/troubleshooting/index.mdx b/docs/platforms/python/tracing/troubleshooting/index.mdx index 9ac0bf712cc7a8..b6c136095d57e9 100644 --- a/docs/platforms/python/tracing/troubleshooting/index.mdx +++ b/docs/platforms/python/tracing/troubleshooting/index.mdx @@ -91,9 +91,3 @@ with sentry_sdk.start_span(op="request", name="setup form") as span: # ... ``` - -## Traces Miss Spans, High Memory Usage, or Data Loss After Crashes - -If you're hitting the 1,000-span limit, experiencing high memory usage from long-running processes, or losing span data when your process crashes, consider enabling stream mode. Stream mode sends spans to Sentry in batches as they finish rather than holding them in memory until the transaction ends. - -See Streamed Spans for more information. diff --git a/includes/tracing/ai-agents-module/ai-client-span.mdx b/includes/tracing/ai-agents-module/ai-client-span.mdx index 7433cb368c69a2..091f98404c0da7 100644 --- a/includes/tracing/ai-agents-module/ai-client-span.mdx +++ b/includes/tracing/ai-agents-module/ai-client-span.mdx @@ -1,10 +1,8 @@ - -In transaction mode, the [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span using its `template` parameter. The stream-mode decorator doesn't have a `template` option. - +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span. -- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"gen_ai.{gen_ai.operation.name}"`. (e.g. `"gen_ai.chat"`) +- The span `op` MUST be `"gen_ai.{gen_ai.operation.name}"`. (e.g. `"gen_ai.chat"`) - The span `name` SHOULD be `"{gen_ai.operation.name} {gen_ai.request.model}"`. (e.g. `"chat o3-mini"`) - The `gen_ai.operation.name` attribute MUST be `"chat"`, `"embeddings"`, `"generate_content"` or `"text_completion"`. - The `gen_ai.provider.name` attribute MUST be the Generative AI product as identified by the client or server instrumentation. (e.g. `"openai"`) @@ -15,9 +13,9 @@ In transaction mode, the [@sentry_sdk.trace()](/platforms/python/tracing/instrum ### Request Attributes -| Data Attribute | Type | Requirement Level | Description | Example | -| :--------------------------------- | :----- | :---------------- | :------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------- | -| `gen_ai.input.messages` | string | optional | List of message objects sent to the LLM. **[0]**, **[1]** | `'[{"role": "user", "parts": [{"type": "text", "content": "..."}]}]'` | +| Data Attribute | Type | Requirement Level | Description | Example | +| :--------------------------------- | :----- | :---------------- | :------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------- | +| `gen_ai.input.messages` | string | optional | List of message objects sent to the LLM. **[0]**, **[1]** | `'[{"role": "user", "parts": [{"type": "text", "content": "..."}]}]'` | | `gen_ai.tool.definitions` | string | optional | List of objects describing the available tools. **[0]** | `'[{"name": "random_number", "description": "..."}]'` | | `gen_ai.system_instructions` | string | optional | The system instructions passed to the model. | `"You are a helpful assistant."` | | `gen_ai.request.frequency_penalty` | float | optional | Model configuration parameter. | `0.5` | @@ -33,16 +31,16 @@ In transaction mode, the [@sentry_sdk.trace()](/platforms/python/tracing/instrum ### Response Attributes -| Data Attribute | Type | Requirement Level | Description | Example | -| :------------------------------------ | :------ | :---------------- | :------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------- | -| `gen_ai.response.model` | string | required | The concrete model that responded (may differ from `gen_ai.request.model`). | `"gpt-4o-2024-08-06"` | -| `gen_ai.output.messages` | string | optional | Stringified array of message objects representing the model's output. **[0]**, **[1]** | `'[{"role": "assistant", "parts": [{"type": "text", "content": "..."}]}]'` | -| `gen_ai.response.finish_reasons` | string | optional | Stringified array of reasons the model stopped generating. **[0]** | `'["stop"]'` | -| `gen_ai.response.id` | string | optional | Unique identifier for the completion. | `"chatcmpl-abc123"` | -| `gen_ai.response.streaming` | boolean | optional | Whether the response was streamed. | `true` | -| `gen_ai.response.time_to_first_token` | double | optional | Seconds until first response chunk in streaming. | `0.5` | -| `gen_ai.response.text` | string | optional | **Deprecated.** Use `gen_ai.output.messages` instead. The text representation of the model's responses. | `"The weather in Paris is rainy"` | -| `gen_ai.response.tool_calls` | string | optional | **Deprecated.** Use `gen_ai.output.messages` instead. The tool calls in the model's response. **[0]** | `'[{"name": "random_number", "type": "function_call", "arguments": "..."}]'` | +| Data Attribute | Type | Requirement Level | Description | Example | +| :-------------------------------- | :------ | :---------------- | :------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------- | +| `gen_ai.response.model` | string | required | The concrete model that responded (may differ from `gen_ai.request.model`). | `"gpt-4o-2024-08-06"` | +| `gen_ai.output.messages` | string | optional | Stringified array of message objects representing the model's output. **[0]**, **[1]** | `'[{"role": "assistant", "parts": [{"type": "text", "content": "..."}]}]'` | +| `gen_ai.response.finish_reasons` | string | optional | Stringified array of reasons the model stopped generating. **[0]** | `'["stop"]'` | +| `gen_ai.response.id` | string | optional | Unique identifier for the completion. | `"chatcmpl-abc123"` | +| `gen_ai.response.streaming` | boolean | optional | Whether the response was streamed. | `true` | +| `gen_ai.response.time_to_first_token` | double | optional | Seconds until first response chunk in streaming. | `0.5` | +| `gen_ai.response.text` | string | optional | **Deprecated.** Use `gen_ai.output.messages` instead. The text representation of the model's responses. | `"The weather in Paris is rainy"` | +| `gen_ai.response.tool_calls` | string | optional | **Deprecated.** Use `gen_ai.output.messages` instead. The tool calls in the model's response. **[0]** | `'[{"name": "random_number", "type": "function_call", "arguments": "..."}]'` | ### Token Usage diff --git a/includes/tracing/ai-agents-module/execute-tool-span.mdx b/includes/tracing/ai-agents-module/execute-tool-span.mdx index 03605c777388db..feddc9bbcf70ce 100644 --- a/includes/tracing/ai-agents-module/execute-tool-span.mdx +++ b/includes/tracing/ai-agents-module/execute-tool-span.mdx @@ -1,12 +1,10 @@ Describes a tool execution. - -In transaction mode, the [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span using its `template` parameter. The stream-mode decorator doesn't have a `template` option. - +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span. -- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"gen_ai.execute_tool"`. +- The span `op` MUST be `"gen_ai.execute_tool"`. - The span `name` SHOULD be `"execute_tool {gen_ai.tool.name}"`. (e.g. `"execute_tool query_database"`) - The `gen_ai.operation.name` attribute MUST be `"execute_tool"`. - The `gen_ai.tool.name` attribute SHOULD be set to the name of the tool. (e.g. `"query_database"`) diff --git a/includes/tracing/ai-agents-module/invoke-agent-span.mdx b/includes/tracing/ai-agents-module/invoke-agent-span.mdx index 4fcbd5bab2dad8..b42eda94b40a90 100644 --- a/includes/tracing/ai-agents-module/invoke-agent-span.mdx +++ b/includes/tracing/ai-agents-module/invoke-agent-span.mdx @@ -1,12 +1,10 @@ Describes AI agent invocation. - -In transaction mode, the [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span using its `template` parameter. The stream-mode decorator doesn't have a `template` option. - +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span. -- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"gen_ai.invoke_agent"`. +- The span `op` MUST be `"gen_ai.invoke_agent"`. - The span `name` SHOULD be `"invoke_agent {gen_ai.agent.name}"`. - The `gen_ai.operation.name` attribute MUST be `"invoke_agent"`. - The `gen_ai.agent.name` attribute SHOULD be set to the agent's name. (e.g. `"Weather Agent"`) @@ -21,7 +19,7 @@ Additional attributes on the span: | `gen_ai.input.messages` | string | optional | List of message objects given to the agent. **[0]**, **[1]** | `'[{"role": "user", "parts": [{"type": "text", "content": "..."}]}]'` | | `gen_ai.tool.definitions` | string | optional | List of objects describing the available tools. **[0]** | `'[{"name": "random_number", "description": "..."}]'` | | `gen_ai.system_instructions` | string | optional | The system instructions passed to the model. | `"You are a helpful assistant."` | -| `gen_ai.pipeline.name` | string | optional | The name of the AI workflow or pipeline the agent belongs to. | `"weather-pipeline"` | +| `gen_ai.pipeline.name` | string | optional | The name of the AI workflow or pipeline the agent belongs to. | `"weather-pipeline"` | | `gen_ai.request.messages` | string | optional | **Deprecated.** Use `gen_ai.input.messages` instead. List of message objects given to the agent. **[0]** | `'[{"role": "system", "content": "..."}]'` | | `gen_ai.request.available_tools` | string | optional | **Deprecated.** Use `gen_ai.tool.definitions` instead. List of objects describing the available tools. **[0]** | `'[{"name": "random_number", "description": "..."}]'` | diff --git a/includes/tracing/mcp-module/prompt-retrieval-span.mdx b/includes/tracing/mcp-module/prompt-retrieval-span.mdx index faccf4775c574c..5c68cec65f6973 100644 --- a/includes/tracing/mcp-module/prompt-retrieval-span.mdx +++ b/includes/tracing/mcp-module/prompt-retrieval-span.mdx @@ -1,6 +1,8 @@ Describes MCP prompt retrieval. -- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"mcp.server"`. +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span. + +- The span's `op` MUST be `"mcp.server"`. - The span `name` SHOULD be `"prompts/get {mcp.prompt.name}"`. - The `mcp.prompt.name` attribute MUST be set to the prompt's name. (e.g. `"code_review"`) - The `mcp.method.name` attribute SHOULD be set to `"prompts/get"`. diff --git a/includes/tracing/mcp-module/resource-read-span.mdx b/includes/tracing/mcp-module/resource-read-span.mdx index f670977d0635de..83d25e859345ad 100644 --- a/includes/tracing/mcp-module/resource-read-span.mdx +++ b/includes/tracing/mcp-module/resource-read-span.mdx @@ -1,6 +1,8 @@ Describes MCP resource access. -- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"mcp.server"`. +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span. + +- The span's `op` MUST be `"mcp.server"`. - The span `name` SHOULD be `"resources/read {mcp.resource.uri}"`. - The `mcp.resource.uri` attribute MUST be set to the resource's URI. (e.g. `"file:///path/to/resource"`) - The `mcp.method.name` attribute SHOULD be set to `"resources/read"`. diff --git a/includes/tracing/mcp-module/tool-execution-span.mdx b/includes/tracing/mcp-module/tool-execution-span.mdx index de435384e6a3c4..6af1a75028215a 100644 --- a/includes/tracing/mcp-module/tool-execution-span.mdx +++ b/includes/tracing/mcp-module/tool-execution-span.mdx @@ -1,6 +1,8 @@ Describes MCP tool execution. -- The span `op` (transaction mode) or the span's `sentry.op` attribute (stream mode) MUST be `"mcp.server"`. +The [@sentry_sdk.trace()](/platforms/python/tracing/instrumentation/custom-instrumentation/#span-templates) decorator can also be used to create this span. + +- The span's `op` MUST be `"mcp.server"`. - The span `name` SHOULD be `"tools/call {mcp.tool.name}"`. - The `mcp.tool.name` attribute MUST be set to the tool's name. (e.g. `"get_weather"`) - The `mcp.method.name` attribute SHOULD be set to `"tools/call"`. diff --git a/platform-includes/configuration/before-send-span/python.mdx b/platform-includes/configuration/before-send-span/python.mdx deleted file mode 100644 index a0e048df54aa36..00000000000000 --- a/platform-includes/configuration/before-send-span/python.mdx +++ /dev/null @@ -1,20 +0,0 @@ -```python -import sentry_sdk - -def postprocess_span(span, hint): - attributes_to_sanitize = [ - "http.request.header.custom-auth", - "http.request.header.custom-user-id", - ] - for attribute in attributes_to_sanitize: - if span["attributes"].get(attribute): - span["attributes"][attribute] = "[Sanitized]" - return span - -sentry_sdk.init( - _experiments={ - "trace_lifecycle": "stream", - "before_send_span": postprocess_span, - }, -) -``` diff --git a/platform-includes/distributed-tracing/custom-instrumentation/python.mdx b/platform-includes/distributed-tracing/custom-instrumentation/python.mdx index 21ff75fe96ad5b..8ac6dcfdea6e6f 100644 --- a/platform-includes/distributed-tracing/custom-instrumentation/python.mdx +++ b/platform-includes/distributed-tracing/custom-instrumentation/python.mdx @@ -2,12 +2,6 @@ Distributed tracing works out of the box for [supported frameworks](/platforms/p This page describes how to manually propagate trace information into and out of your Python application. All you have to do is to make sure your application extracts incoming headers and to set those headers again when making an outgoing request within your application. - - -This page covers both transaction mode (default) and stream mode. See Streamed Spans to learn more. - - - ## 1. Extract Incoming Tracing Information Incoming tracing information has to be extracted and stored in memory for later use. Sentry provides the `continue_trace()` function to help you with this. Incoming tracing information can come from different places: @@ -16,16 +10,9 @@ Incoming tracing information has to be extracted and stored in memory for later - In a job queue, like Celery, it can be retrieved from meta or header variables. - You also can pick up tracing information from environment variables. - -In **transaction mode**, `sentry_sdk.continue_trace()` returns a transaction, but does not start it. To start the transaction, use `start_transaction()`. - -In **stream mode**, `sentry_sdk.traces.continue_trace()` replaces `sentry_sdk.continue_trace()` and is not a context manager. The next span created with `sentry_sdk.traces.start_span()` picks it up automatically. - - - Here's an example of how to extract and store incoming tracing information using `continue_trace()`: -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk from my_project import get_incoming_headers_as_dict @@ -36,43 +23,14 @@ with sentry_sdk.start_transaction(transaction): ... ``` -```python {tabTitle:Stream Mode} -import sentry_sdk -from my_project import get_incoming_headers_as_dict - -headers = get_incoming_headers_as_dict() +In this example, `get_incoming_headers_as_dict()` returns a dictionary that contains tracing information from HTTP headers, environment variables, or any other mechanism your project uses to communicate with the outside world. -sentry_sdk.traces.continue_trace(headers) +Sentry's `continue_trace()` function will extract the given headers, try to find the `sentry-trace` and `baggage` headers, and store them in memory for later use. -with sentry_sdk.traces.start_span(name="handle request"): - ... -``` +`continue_trace()` returns a transaction, but does not start it. To start the transaction, use `start_transaction()`. -In these examples, `get_incoming_headers_as_dict()` returns a dictionary that contains tracing information from HTTP headers, environment variables, or any other mechanism your project uses to communicate with the outside world. - -## 2. Start a New Trace - - - This step only applies to stream mode and is not available in transaction - mode. - - -In stream mode, if you need to start a completely new trace unconnected to the current one, use `sentry_sdk.traces.new_trace()`. This is useful for background jobs or scheduled tasks where you want a clean trace boundary: - -```python -import sentry_sdk - -with sentry_sdk.traces.start_span(name="span in trace 1"): - ... - -sentry_sdk.traces.new_trace() - -with sentry_sdk.traces.start_span(name="span in trace 2"): - # This span is the root of a new, separate trace - ... -``` -## 3. Inject Tracing Information to Outgoing Requests +## 2. Inject Tracing Information to Outgoing Requests For distributed tracing to work, the two headers `sentry-trace` and `baggage`, must be added to outgoing requests. If you pregenerate HTML on the server-side, you might want to take a look at [Inject Tracing Information into Rendered HTML](#inject-tracing-information-into-rendered-html), which describes how to pass on tracing information through HTML meta tags. diff --git a/platform-includes/performance/dropping-spans/python.mdx b/platform-includes/performance/dropping-spans/python.mdx deleted file mode 100644 index 71dea6d207cdca..00000000000000 --- a/platform-includes/performance/dropping-spans/python.mdx +++ /dev/null @@ -1,37 +0,0 @@ -`ignore_span` is only available in stream mode. - -In stream mode, you can prevent specific spans from being created using `ignore_spans`, set under `_experiments`. It evaluates rules at span start, which can be strings, compiled regexes, or dictionaries with `name` and/or `attributes` conditions. If the dropped span is a service span, its children are dropped too. If it's a child span, only that span is dropped and its children are reparented to the nearest ancestor. - -```python -import re -import sentry_sdk - -sentry_sdk.init( - _experiments={ - "trace_lifecycle": "stream", - "ignore_spans": [ - # String match against span name - "/health", - - # Regex match against span name - re.compile(r"/flow/.*"), - - # Match by attributes (all must match) - { - "attributes": { - "service.id": "15def9a", - "flow.pipeline": "legacy", - } - }, - - # Match by name and attributes - { - "name": re.compile(r"/flow/.*"), - "attributes": { - "service.id": re.compile(r".*\.facade"), - }, - }, - ], - }, -) -``` diff --git a/platform-includes/performance/traces-sampler-as-sampler/python.mdx b/platform-includes/performance/traces-sampler-as-sampler/python.mdx index 40f49bb0d7fbe2..a8c2a6f44cca20 100644 --- a/platform-includes/performance/traces-sampler-as-sampler/python.mdx +++ b/platform-includes/performance/traces-sampler-as-sampler/python.mdx @@ -1,4 +1,4 @@ -```python {tabTitle:Transaction Mode (Default)} +```python import sentry_sdk from sentry_sdk.types import SamplingContext @@ -30,37 +30,3 @@ sentry_sdk.init( traces_sampler=traces_sampler, ) ``` - -```python {tabTitle:Stream Mode} -import sentry_sdk -from sentry_sdk.types import SamplingContext - -def traces_sampler(sampling_context: SamplingContext) -> float: - # Use the parent sampling decision if we have an incoming trace. - # Note: we strongly recommend respecting the parent sampling decision, - # as this ensures your traces will be complete! - parent_sampling_decision = sampling_context["span_context"]["parent_sampled"] - if parent_sampling_decision is not None: - return float(parent_sampling_decision) - - # Examine provided sampling context along with anything in the - # global namespace to compute the sample rate for this span - if "...": - # These are important - take a big sample - return 0.5 - elif "...": - # These are less important - only take 1% - return 0.01 - elif "...": - # These aren't worth tracking - drop these spans - return 0 - - # Default sample rate - return 0.1 - -sentry_sdk.init( - # ... - traces_sampler=traces_sampler, - _experiments={"trace_lifecycle": "stream"}, -) -``` diff --git a/platform-includes/performance/traces-sampler-config-option/python.mdx b/platform-includes/performance/traces-sampler-config-option/python.mdx index 3f9a0f5f22e5d4..6521295fb60de8 100644 --- a/platform-includes/performance/traces-sampler-config-option/python.mdx +++ b/platform-includes/performance/traces-sampler-config-option/python.mdx @@ -1,6 +1,6 @@ -A function responsible for determining the percentage chance a given transaction (or service span in stream mode) will be sent to Sentry. This configuration option accepts a function, which takes one parameter (the `sampling_context`). The given `sampling_context` contains information about the transaction and the context in which it's being created. The function must return a number between `0` (0% chance of being sent) and `1` (100% chance of being sent). Can also be used for filtering transactions, by returning `0` for those that are unwanted. +A function responsible for determining the percentage chance a given transaction will be sent to Sentry. This configuration option accepts a function, which takes one parameter (the `sampling_context`). The given `sampling_context` contains information about the transaction and the context in which it's being created. The function must return a number between `0` (0% chance of being sent) and `1` (100% chance of being sent). Can also be used for filtering transactions, by returning `0` for those that are unwanted. Either this or `traces_sample_rate` must be defined to enable tracing. diff --git a/platform-includes/sensitive-data/set-tag/python.mdx b/platform-includes/sensitive-data/set-tag/python.mdx index c9c1b015dfa9d6..49996745df222d 100644 --- a/platform-includes/sensitive-data/set-tag/python.mdx +++ b/platform-includes/sensitive-data/set-tag/python.mdx @@ -1,7 +1,3 @@ -```python {tabTitle:Transaction Mode(Default)} +```python sentry_sdk.set_tag("birthday", checksum_or_hash("08/12/1990")) ``` - -```python {tabTitle:Stream Mode} -sentry_sdk.set_attribute("birthday", checksum_or_hash("08/12/1990")) -``` diff --git a/platform-includes/sensitive-data/set-user/python.mdx b/platform-includes/sensitive-data/set-user/python.mdx index b54259c21be67d..94f12fed48c5b9 100644 --- a/platform-includes/sensitive-data/set-user/python.mdx +++ b/platform-includes/sensitive-data/set-user/python.mdx @@ -1,15 +1,7 @@ -```python {tabTitle:Transaction Mode(Default)} +```python sentry_sdk.set_user({"id": user.id}) # or sentry_sdk.set_user({"username": user.username}) ``` - -```python {tabTitle:Stream Mode} -sentry_sdk.set_attribute("user.id", user.id) - -# or - -sentry_sdk.set_attribute("user.username", user.username) -``` diff --git a/platform-includes/tracing/span-metrics/add-all-spans/python.mdx b/platform-includes/tracing/span-metrics/add-all-spans/python.mdx deleted file mode 100644 index b2b21348285fcf..00000000000000 --- a/platform-includes/tracing/span-metrics/add-all-spans/python.mdx +++ /dev/null @@ -1,56 +0,0 @@ -How you add data to every span depends on your tracing mode: - -```python {tabTitle:Transaction Mode (Default)} -import sentry_sdk -from sentry_sdk.types import Event, Hint - -def before_send_transaction(event: Event, hint: Hint) -> Event | None: - # Add attributes to the root span (transaction) - if "trace" in event.get("contexts", {}): - if "data" not in event["contexts"]["trace"]: - event["contexts"]["trace"]["data"] = {} - - event["contexts"]["trace"]["data"].update({ - "app_version": "1.2.3", - "environment_region": "us-west-2" - }) - - # Add attributes to all child spans - for span in event.get("spans", []): - if "data" not in span: - span["data"] = {} - - span["data"].update({ - "component_version": "2.0.0", - "deployment_stage": "production" - }) - - return event - -sentry_sdk.init( - # ... - before_send_transaction=before_send_transaction -) -``` - -```python {tabTitle:Stream Mode} -import sentry_sdk - -def before_send_span(span, hint): - # Runs once per span, as it's flushed. - if "attributes" not in span: - span["attributes"] = {} - span["attributes"].update({ - "component_version": "2.0.0", - "deployment_stage": "production", - }) - return span - -sentry_sdk.init( - # ... - _experiments={ - "trace_lifecycle": "stream", - "before_send_span": before_send_span, - }, -) -```