-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Python tracing docs refresh #13125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Python tracing docs refresh #13125
Changes from 33 commits
426c39e
302e288
2f2680d
dd4ad91
0397fcf
3c601a2
2cdca6b
6d4e3bf
d6e2490
4a3d732
084a25e
a192aec
9770d5a
f69c5ea
d808dd9
c430801
2f791b2
03f5d6a
137dbb8
5117a30
44289dd
c8dba3b
92a9a3d
407c560
0a89965
b131bbf
fef0ae1
61dd75e
1a9da90
dd5d44a
8b00344
ab9ebf7
6de450f
e9bb6af
e39d413
d8e046c
7e5c6e4
d09e2b4
1519f54
8f17aa2
a2e0e4d
75033d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,285 @@ | ||
| --- | ||
| title: Configure Sampling | ||
| description: "Learn how to configure sampling in your app." | ||
| sidebar_order: 40 | ||
| --- | ||
|
|
||
| If you find that Sentry's tracing functionality is generating too much data, for example, if you notice your spans quota is quickly being exhausted, you can choose to sample your traces. | ||
|
|
||
| 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](#trace-sampler-examples) below. | ||
|
|
||
| ## 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 will be sampled: | ||
|
|
||
| <PlatformContent includePath="/performance/traces-sample-rate" /> | ||
|
|
||
| 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 | ||
| - 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 | ||
|
|
||
| <Alert> | ||
|
|
||
| It is strongly recommended when using a custom `traces_sampler` that you respect the parent sampling decision. This ensures your traces will be complete. | ||
|
|
||
| </Alert> | ||
|
|
||
| In distributed systems, implement inheritance logic when trace information is propagated between services. This ensures consistent sampling decisions across your entire distributed trace | ||
|
|
||
| <PlatformContent includePath="/performance/traces-sampler-as-sampler" /> | ||
|
|
||
| <details> | ||
| <summary className="text-xl font-semibold">Trace Sampler Examples</summary> | ||
|
|
||
| #### Trace Sampler Examples | ||
|
|
||
| 1. Prioritizing Critical User Flows | ||
|
|
||
| ```python | ||
| def traces_sampler(sampling_context): | ||
sfanahata marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # 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.get("parent_sampled") | ||
| if parent_sampling_decision is not None: | ||
| return float(parent_sampling_decision) | ||
|
|
||
| ctx = sampling_context.get("transaction_context", {}) | ||
sfanahata marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| name = ctx.get("name") | ||
|
|
||
| # Sample all checkout transactions | ||
| if name and ('/checkout' in name or | ||
| ctx.get("op") == 'checkout'): | ||
| return 1.0 | ||
|
|
||
| # Sample 50% of login transactions | ||
| if name and ('/login' in name or | ||
| ctx.get("op") == 'login'): | ||
| return 0.5 | ||
|
|
||
| # Sample 10% of everything else | ||
| return 0.1 | ||
|
|
||
| sentry_sdk.init( | ||
| dsn="your-dsn", | ||
| traces_sampler=traces_sampler, | ||
| ) | ||
| ``` | ||
|
|
||
| 2. Handling Different Environments and Error Rates | ||
|
|
||
| ```python | ||
| def traces_sampler(sampling_context): | ||
| # 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.get("parent_sampled") | ||
| if parent_sampling_decision is not None: | ||
| return float(parent_sampling_decision) | ||
|
|
||
| ctx = sampling_context.get("transaction_context", {}) | ||
| environment = os.environ.get("ENVIRONMENT", "development") | ||
|
|
||
| # Sample all transactions in development | ||
| if environment == "development": | ||
| return 1.0 | ||
|
|
||
| # Sample more transactions if there are recent errors by using custom attributes | ||
| if ctx.get("data", {}).get("hasRecentErrors"): | ||
| 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 | ||
|
|
||
| sentry_sdk.init( | ||
| dsn="your-dsn", | ||
| traces_sampler=traces_sampler, | ||
| ) | ||
| ``` | ||
|
|
||
| 3. Controlling Sampling Based on User and Transaction Properties | ||
|
|
||
| ```python | ||
| def traces_sampler(sampling_context): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this example it's also not 100% clear what's coming from the SDK and what the user needs to have set for it to appear in the sampling context (
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the feedback! I'll add more details in the sample comments that makes it clearer.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also added a bullet at the top of the section to call out using custom attributes as a part of useing
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey, have the changes already been applied? As is the examples are still mixing prepopulated keys with custom ones and it's still not clear which is which. If I was a new user I'd be confused as to why there's a I feel like the current page https://docs.sentry.io/platforms/python/configuration/sampling/#sampling-context-data does a great job of making the distinction as well as making it clear how you can get custom data into the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for that example. I see what you mean now! The
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I changed the examples to more explicitly use |
||
| # 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.get("parent_sampled") | ||
| if parent_sampling_decision is not None: | ||
| return float(parent_sampling_decision) | ||
|
|
||
| ctx = sampling_context.get("transaction_context", {}) | ||
| data = ctx.get("data", {}) | ||
|
|
||
| # Always sample for premium users | ||
| if data.get("user", {}).get("tier") == "premium": | ||
| return 1.0 | ||
|
|
||
| # Sample more transactions for users experiencing errors | ||
| if data.get("hasRecentErrors"): | ||
| return 0.8 | ||
|
|
||
| # Sample less for high-volume, low-value paths | ||
| if (ctx.get("name") or "").startswith("/api/metrics"): | ||
| return 0.01 | ||
|
|
||
| # Sample more for slow transactions | ||
| if data.get("duration_ms", 0) > 1000: # Transactions over 1 second | ||
| return 0.5 | ||
|
|
||
| # Default sampling rate | ||
| return 0.2 | ||
|
|
||
| sentry_sdk.init( | ||
| dsn="your-dsn", | ||
| traces_sampler=traces_sampler, | ||
| ) | ||
| ``` | ||
|
|
||
| 4. Complex Business Logic Sampling | ||
|
|
||
| ```python | ||
| def traces_sampler(sampling_context): | ||
| # 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.get("parent_sampled") | ||
| if parent_sampling_decision is not None: | ||
| return float(parent_sampling_decision) | ||
|
|
||
| ctx = sampling_context.get("transaction_context", {}) | ||
| data = ctx.get("data", {}) | ||
|
|
||
| # Always sample critical business operations | ||
| if ctx.get("op") in ["payment.process", "order.create", "user.verify"]: | ||
| return 1.0 | ||
|
|
||
| # Sample based on user segment | ||
| user_segment = data.get("user", {}).get("segment") | ||
| if user_segment == "enterprise": | ||
| return 0.8 | ||
| elif user_segment == "premium": | ||
| return 0.5 | ||
|
|
||
| # Sample based on transaction value | ||
| transaction_value = data.get("transaction", {}).get("value", 0) | ||
| if transaction_value > 1000: # High-value transactions | ||
| return 0.7 | ||
|
|
||
| # Sample based on error rate in the service | ||
| error_rate = data.get("service", {}).get("error_rate", 0) | ||
| if error_rate > 0.05: # Error rate above 5% | ||
| return 0.9 | ||
|
|
||
| # Default sampling rate | ||
| return 0.1 | ||
|
|
||
| sentry_sdk.init( | ||
| dsn="your-dsn", | ||
| traces_sampler=traces_sampler, | ||
| ) | ||
| ``` | ||
|
|
||
| 5. Performance-Based Sampling | ||
|
|
||
| ```python | ||
| def traces_sampler(sampling_context): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd also make clear the attributes here have to be added and are not there by default (
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I only picked
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a line as a part of the section's intro that this outlined how to use custom attributes, but happy to add notes in the examples too.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| # 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.get("parent_sampled") | ||
| if parent_sampling_decision is not None: | ||
| return float(parent_sampling_decision) | ||
|
|
||
| ctx = sampling_context.get("transaction_context", {}) | ||
| data = ctx.get("data", {}) | ||
|
|
||
| # Sample all slow transactions | ||
| if data.get("duration_ms", 0) > 2000: # Over 2 seconds | ||
| return 1.0 | ||
|
|
||
| # Sample more transactions with high memory usage | ||
| if data.get("memory_usage_mb", 0) > 500: # Over 500MB | ||
| return 0.8 | ||
|
|
||
| # Sample more transactions with high CPU usage | ||
| if data.get("cpu_percent", 0) > 80: # Over 80% CPU | ||
| return 0.8 | ||
|
|
||
| # Sample more transactions with high database load using custom attributes | ||
| if data.get("db_connections", 0) > 100: # Over 100 connections | ||
| return 0.7 | ||
|
|
||
| # Default sampling rate | ||
| return 0.1 | ||
|
|
||
| sentry_sdk.init( | ||
| dsn="your-dsn", | ||
| traces_sampler=traces_sampler, | ||
| ) | ||
| ``` | ||
| </details> | ||
|
|
||
| ## The Sampling Context Object | ||
sfanahata marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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: | ||
|
|
||
| ```python | ||
| { | ||
| "transaction_context": { | ||
| "name": str, # transaction title at creation time | ||
| "op": str, # short description of transaction type, like "http.request" | ||
| "data": Optional[Dict[str, Any]] # other transaction data | ||
| }, | ||
| "parent_sampled ": Optional[bool], # whether the parent transaction was sampled, `None` if no parent or if the parent has no sampling decision | ||
| "parent_sample_rate": Optional[float], # the sample rate used by the parent (if any) | ||
| "transaction_context": Optional[Dict[str, Any]], # custom context data | ||
| "custom_sampling_context": Optional[Dict[str, Any]] # additional custom data for sampling | ||
| } | ||
| ``` | ||
|
|
||
| <b>Additional common types used in</b> `sampling_context`<b>:</b> | ||
| - str: for text values (names, operations, etc.) | ||
| - bool: for true/false values | ||
| - float: for decimal numbers (like sample rates) | ||
| - Dict[str, Any]: for dictionaries with string keys and any type of values | ||
| - Optional[Type]: for values that might be None | ||
sfanahata marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Sampling Decision Precedence | ||
|
|
||
| When multiple sampling mechanisms could apply, Sentry follows this order of precedence: | ||
|
|
||
| 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 | ||
|
|
||
| Sentry uses a "head-based" sampling approach: | ||
|
|
||
| - A sampling decision is made in the originating service (the "head") | ||
| - 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/) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| --- | ||
| title: Custom Instrumentation | ||
| sidebar_order: 40 | ||
| title: Custom Trace Propagation | ||
| sidebar_order: 10 | ||
| --- | ||
|
|
||
| <PlatformContent includePath="distributed-tracing/custom-instrumentation/" /> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,21 @@ | ||
| --- | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure I agree with the changes to this page. This page is meant as an introduction to instrumenting traces. Since most instrumentation is done automatically by the Sentry SDK, any custom instrumentation steps, which are being described on this page, are advanced topics. I think the changes to this page should be reverted. We can describe custom instrumentation on a "custom instrumentation" subpage.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I partially agree. When i made these changes for the JavaScipt docs, I wanted to name the section custom instrumentaiton instead, but there was a few pages that tied closely to instrumentation that seemed to make sense nesting underneath, so i went with "instrumentation". I didn't feel like it really landed as an introduction to instrumenting traces; since most of the configurations were custom - or things that someone instrumenting would be in making custom adjustments to. Ill bring this up when @mydea and I sync on the JS changes this week and see their thought. But i'd love your opinion more here too (knowing the logic i was tracing).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can simplify this page to be more of an intro to the section, and link down to custom instrumentation for more details. @codyde - let me know what comes from your convo this week, too.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I turned this version of the instrumentation index file into a separate span lifecycle file. If we agree on this format, I'll make the change in JS too.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, this looks quite a bit better! |
||
| title: Instrumentation | ||
| description: "Learn how to instrument tracing in your app." | ||
| sidebar_order: 20 | ||
| description: "Learn what Sentry instruments automatically, and how to configure spans to capture tracing data on any action in your app." | ||
| sidebar_order: 50 | ||
| --- | ||
|
|
||
| <PageGrid /> | ||
| <Alert> | ||
|
|
||
| To capture transactions and spans customized to your organization's needs, you must first <PlatformLink to="/tracing/">set up tracing</PlatformLink>. | ||
|
|
||
| </Alert> | ||
|
|
||
| There are two ways that instrumentation is applied to your application: | ||
|
|
||
| ## 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/). | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.