|
| 1 | +--- |
| 2 | +title: Configure Sampling |
| 3 | +description: "Learn how to configure sampling in your app." |
| 4 | +sidebar_order: 40 |
| 5 | +--- |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +## Sampling Configuration Options |
| 12 | + |
| 13 | +### 1. Uniform Sample Rate (`traces_sample_rate`) |
| 14 | + |
| 15 | +`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: |
| 16 | + |
| 17 | +<PlatformContent includePath="/performance/traces-sample-rate" /> |
| 18 | + |
| 19 | +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. |
| 20 | + |
| 21 | +### 2. Sampling Function (`traces_sampler`) |
| 22 | + |
| 23 | +For more granular control, you can provide a `traces_sampler` function. This approach allows you to: |
| 24 | + |
| 25 | +- Apply different sampling rates to different types of transactions |
| 26 | +- Filter out specific transactions entirely |
| 27 | +- Make sampling decisions based on transaction data |
| 28 | +- Control the inheritance of sampling decisions in distributed traces |
| 29 | +- Use custom attributes to modify sampling |
| 30 | + |
| 31 | +<Alert> |
| 32 | + |
| 33 | +It is strongly recommended when using a custom `traces_sampler` that you respect the parent sampling decision. This ensures your traces will be complete. |
| 34 | + |
| 35 | +</Alert> |
| 36 | + |
| 37 | +In distributed systems, implementing inheritance logic when trace information is propagated between services will ensure consistent sampling decisions across your entire distributed trace. |
| 38 | + |
| 39 | +<PlatformContent includePath="/performance/traces-sampler-as-sampler" /> |
| 40 | + |
| 41 | +<details> |
| 42 | +<summary className="text-xl font-semibold">Trace Sampler Examples</summary> |
| 43 | + |
| 44 | +#### Trace Sampler Examples |
| 45 | + |
| 46 | +1. Prioritizing Critical User Flows |
| 47 | + |
| 48 | +```python |
| 49 | +def traces_sampler(sampling_context): |
| 50 | + # Use the parent sampling decision if we have an incoming trace. |
| 51 | + # Note: we strongly recommend respecting the parent sampling decision, |
| 52 | + # as this ensures your traces will be complete! |
| 53 | + parent_sampling_decision = sampling_context.get("parent_sampled") |
| 54 | + if parent_sampling_decision is not None: |
| 55 | + return float(parent_sampling_decision) |
| 56 | + |
| 57 | + ctx = sampling_context.get("transaction_context", {}) |
| 58 | + name = ctx.get("name") |
| 59 | + |
| 60 | + # Sample all checkout transactions |
| 61 | + if name and ('/checkout' in name or |
| 62 | + ctx.get("op") == 'checkout'): |
| 63 | + return 1.0 |
| 64 | + |
| 65 | + # Sample 50% of login transactions |
| 66 | + if name and ('/login' in name or |
| 67 | + ctx.get("op") == 'login'): |
| 68 | + return 0.5 |
| 69 | + |
| 70 | + # Sample 10% of everything else |
| 71 | + return 0.1 |
| 72 | + |
| 73 | +sentry_sdk.init( |
| 74 | + dsn="your-dsn", |
| 75 | + traces_sampler=traces_sampler, |
| 76 | +) |
| 77 | +``` |
| 78 | + |
| 79 | +2. Handling Different Environments and Error Rates |
| 80 | + |
| 81 | +```python |
| 82 | +def traces_sampler(sampling_context): |
| 83 | + # Use the parent sampling decision if we have an incoming trace. |
| 84 | + # Note: we strongly recommend respecting the parent sampling decision, |
| 85 | + # as this ensures your traces will be complete! |
| 86 | + parent_sampling_decision = sampling_context.get("parent_sampled") |
| 87 | + if parent_sampling_decision is not None: |
| 88 | + return float(parent_sampling_decision) |
| 89 | + |
| 90 | + ctx = sampling_context.get("transaction_context", {}) |
| 91 | + environment = os.environ.get("ENVIRONMENT", "development") |
| 92 | + |
| 93 | + # Sample all transactions in development |
| 94 | + if environment == "development": |
| 95 | + return 1.0 |
| 96 | + |
| 97 | + # Sample more transactions if there are recent errors |
| 98 | + # Note: hasRecentErrors is a custom attribute that needs to be set |
| 99 | + if ctx.get("data", {}).get("hasRecentErrors"): |
| 100 | + return 0.8 |
| 101 | + |
| 102 | + # Sample based on environment |
| 103 | + if environment == "production": |
| 104 | + return 0.05 # 5% in production |
| 105 | + elif environment == "staging": |
| 106 | + return 0.2 # 20% in staging |
| 107 | + |
| 108 | + # Default sampling rate |
| 109 | + return 0.1 |
| 110 | + |
| 111 | +# Initialize the SDK with the sampling function |
| 112 | +sentry_sdk.init( |
| 113 | + dsn="your-dsn", |
| 114 | + traces_sampler=traces_sampler, |
| 115 | +) |
| 116 | + |
| 117 | +# You can use the sampling function by setting custom attributes: |
| 118 | +# Option 1: When creating the transaction |
| 119 | +with sentry_sdk.start_transaction(name="GET /api/users", op="http.request") as transaction: |
| 120 | + # Set custom attribute |
| 121 | + transaction.set_data("hasRecentErrors", True) |
| 122 | + # Your code here |
| 123 | + |
| 124 | +# Option 2: During the transaction's lifecycle |
| 125 | +with sentry_sdk.start_transaction(name="GET /api/users", op="http.request") as transaction: |
| 126 | + # Your code here |
| 127 | + transaction.set_data("hasRecentErrors", True) # Set custom attribute |
| 128 | +``` |
| 129 | + |
| 130 | +3. Controlling Sampling Based on User and Transaction Properties |
| 131 | + |
| 132 | +```python |
| 133 | +def traces_sampler(sampling_context): |
| 134 | + # Use the parent sampling decision if we have an incoming trace. |
| 135 | + # Note: we strongly recommend respecting the parent sampling decision, |
| 136 | + # as this ensures your traces will be complete! |
| 137 | + parent_sampling_decision = sampling_context.get("parent_sampled") |
| 138 | + if parent_sampling_decision is not None: |
| 139 | + return float(parent_sampling_decision) |
| 140 | + |
| 141 | + ctx = sampling_context.get("transaction_context", {}) |
| 142 | + data = ctx.get("data", {}) |
| 143 | + |
| 144 | + # Always sample for premium users |
| 145 | + # Note: user.tier is a custom attribute that needs to be set |
| 146 | + if data.get("user", {}).get("tier") == "premium": |
| 147 | + return 1.0 |
| 148 | + |
| 149 | + # Sample more transactions for users experiencing errors |
| 150 | + # Note: hasRecentErrors is a custom attribute |
| 151 | + if data.get("hasRecentErrors"): |
| 152 | + return 0.8 |
| 153 | + |
| 154 | + # Sample less for high-volume, low-value paths |
| 155 | + # Note: name is an SDK-provided attribute |
| 156 | + if (ctx.get("name") or "").startswith("/api/metrics"): |
| 157 | + return 0.01 |
| 158 | + |
| 159 | + # Sample more for slow transactions |
| 160 | + # Note: duration_ms is a custom attribute |
| 161 | + if data.get("duration_ms", 0) > 1000: # Transactions over 1 second |
| 162 | + return 0.5 |
| 163 | + |
| 164 | + # Default sampling rate |
| 165 | + return 0.2 |
| 166 | + |
| 167 | +# Initialize the SDK with the sampling function |
| 168 | +sentry_sdk.init( |
| 169 | + dsn="your-dsn", |
| 170 | + traces_sampler=traces_sampler, |
| 171 | +) |
| 172 | + |
| 173 | +# To set custom attributes for this example: |
| 174 | +with sentry_sdk.start_transaction(name="GET /api/users", op="http.request") as transaction: |
| 175 | + # Set custom attributes |
| 176 | + transaction.set_data("user", {"tier": "premium"}) # Custom user data |
| 177 | + transaction.set_data("hasRecentErrors", True) # Custom error flag |
| 178 | + transaction.set_data("duration_ms", 1500) # Custom timing data |
| 179 | + # Your code here |
| 180 | + |
| 181 | +``` |
| 182 | + |
| 183 | +4. Complex Business Logic Sampling |
| 184 | + |
| 185 | +```python |
| 186 | +def traces_sampler(sampling_context): |
| 187 | + # Use the parent sampling decision if we have an incoming trace. |
| 188 | + # Note: we strongly recommend respecting the parent sampling decision, |
| 189 | + # as this ensures your traces will be complete! |
| 190 | + parent_sampling_decision = sampling_context.get("parent_sampled") |
| 191 | + if parent_sampling_decision is not None: |
| 192 | + return float(parent_sampling_decision) |
| 193 | + |
| 194 | + ctx = sampling_context.get("transaction_context", {}) |
| 195 | + data = ctx.get("data", {}) |
| 196 | + |
| 197 | + # Always sample critical business operations |
| 198 | + # Note: op is an SDK-provided attribute |
| 199 | + if ctx.get("op") in ["payment.process", "order.create", "user.verify"]: |
| 200 | + return 1.0 |
| 201 | + |
| 202 | + # Sample based on user segment |
| 203 | + # Note: user.segment is a custom attribute |
| 204 | + user_segment = data.get("user", {}).get("segment") |
| 205 | + if user_segment == "enterprise": |
| 206 | + return 0.8 |
| 207 | + elif user_segment == "premium": |
| 208 | + return 0.5 |
| 209 | + |
| 210 | + # Sample based on transaction value |
| 211 | + # Note: transaction.value is a custom attribute |
| 212 | + transaction_value = data.get("transaction", {}).get("value", 0) |
| 213 | + if transaction_value > 1000: # High-value transactions |
| 214 | + return 0.7 |
| 215 | + |
| 216 | + # Sample based on error rate in the service |
| 217 | + # Note: service.error_rate is a custom attribute |
| 218 | + error_rate = data.get("service", {}).get("error_rate", 0) |
| 219 | + if error_rate > 0.05: # Error rate above 5% |
| 220 | + return 0.9 |
| 221 | + |
| 222 | + # Default sampling rate |
| 223 | + return 0.1 |
| 224 | + |
| 225 | +# Initialize the SDK with the sampling function |
| 226 | +sentry_sdk.init( |
| 227 | + dsn="your-dsn", |
| 228 | + traces_sampler=traces_sampler, |
| 229 | +) |
| 230 | + |
| 231 | +s# To set custom attributes for this example: |
| 232 | +with sentry_sdk.start_transaction(name="Process Payment", op="payment.process") as transaction: |
| 233 | + # Set custom attributes |
| 234 | + transaction.set_data("user", {"segment": "enterprise"}) # Custom user data |
| 235 | + transaction.set_data("transaction", {"value": 1500}) # Custom transaction data |
| 236 | + transaction.set_data("service", {"error_rate": 0.03}) # Custom service data |
| 237 | + # Your code here |
| 238 | + |
| 239 | +``` |
| 240 | + |
| 241 | +5. Performance-Based Sampling |
| 242 | + |
| 243 | +```python |
| 244 | +def traces_sampler(sampling_context): |
| 245 | + # Use the parent sampling decision if we have an incoming trace. |
| 246 | + # Note: we strongly recommend respecting the parent sampling decision, |
| 247 | + # as this ensures your traces will be complete! |
| 248 | + parent_sampling_decision = sampling_context.get("parent_sampled") |
| 249 | + if parent_sampling_decision is not None: |
| 250 | + return float(parent_sampling_decision) |
| 251 | + |
| 252 | + ctx = sampling_context.get("transaction_context", {}) |
| 253 | + data = ctx.get("data", {}) |
| 254 | + |
| 255 | + # Sample all slow transactions |
| 256 | + # Note: duration_ms is a custom attribute |
| 257 | + if data.get("duration_ms", 0) > 2000: # Over 2 seconds |
| 258 | + return 1.0 |
| 259 | + |
| 260 | + # Sample more transactions with high memory usage |
| 261 | + # Note: memory_usage_mb is a custom attribute |
| 262 | + if data.get("memory_usage_mb", 0) > 500: # Over 500MB |
| 263 | + return 0.8 |
| 264 | + |
| 265 | + # Sample more transactions with high CPU usage |
| 266 | + # Note: cpu_percent is a custom attribute |
| 267 | + if data.get("cpu_percent", 0) > 80: # Over 80% CPU |
| 268 | + return 0.8 |
| 269 | + |
| 270 | + # Sample more transactions with high database load |
| 271 | + # Note: db_connections is a custom attribute |
| 272 | + if data.get("db_connections", 0) > 100: # Over 100 connections |
| 273 | + return 0.7 |
| 274 | + |
| 275 | + # Default sampling rate |
| 276 | + return 0.1 |
| 277 | + |
| 278 | +# Initialize the SDK with the sampling function |
| 279 | + sentry_sdk.init( |
| 280 | + dsn="your-dsn", |
| 281 | + traces_sampler=traces_sampler, |
| 282 | +) |
| 283 | + |
| 284 | +# To set custom attributes for this example: |
| 285 | +with sentry_sdk.start_transaction(name="Process Data", op="data.process") as transaction: |
| 286 | + # Set custom attributes |
| 287 | + transaction.set_data("duration_ms", 2500) # Custom timing data |
| 288 | + transaction.set_data("memory_usage_mb", 600) # Custom memory data |
| 289 | + transaction.set_data("cpu_percent", 85) # Custom CPU data |
| 290 | + transaction.set_data("db_connections", 120) # Custom database data |
| 291 | + # Your code here |
| 292 | + |
| 293 | +``` |
| 294 | +</details> |
| 295 | + |
| 296 | +## The Sampling Context Object |
| 297 | + |
| 298 | +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: |
| 299 | + |
| 300 | +```python |
| 301 | +{ |
| 302 | + "transaction_context": { |
| 303 | + "name": str, # transaction title at creation time (SDK-provided) |
| 304 | + "op": str, # short description of transaction type (SDK-provided) |
| 305 | + "data": Optional[Dict[str, Any]] # custom data you've added to the transaction |
| 306 | + }, |
| 307 | + "parent_sampled": Optional[bool], # whether the parent transaction was sampled (SDK-provided) |
| 308 | + "parent_sample_rate": Optional[float], # the sample rate used by the parent (SDK-provided) |
| 309 | + "custom_sampling_context": Optional[Dict[str, Any]] # additional custom data for sampling |
| 310 | +} |
| 311 | +``` |
| 312 | + |
| 313 | +### SDK-Provided vs. Custom Attributes |
| 314 | + |
| 315 | +The sampling context contains both SDK-provided attributes and custom attributes: |
| 316 | + |
| 317 | +**SDK-Provided Attributes:** |
| 318 | +- `transaction_context.name`: The name of the transaction |
| 319 | +- `transaction_context.op`: The operation type |
| 320 | +- `parent_sampled`: Whether the parent transaction was sampled |
| 321 | +- `parent_sample_rate`: The sample rate used by the parent |
| 322 | + |
| 323 | +**Custom Attributes:** |
| 324 | +- Any data you add to the `set_data` method on the transaction object. Use this for data that you want to include in the transaction data that gets sent to Sentry. |
| 325 | +- 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/configuration/sampling/#sampling-context). |
| 326 | + |
| 327 | +## Sampling Decision Precedence |
| 328 | + |
| 329 | +When multiple sampling mechanisms could apply, Sentry follows this order of precedence: |
| 330 | + |
| 331 | +1. If a sampling decision is passed to `start_transaction`, that decision is used |
| 332 | +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 |
| 333 | +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 |
| 334 | +4. If neither of the above, `traces_sample_rate` is used |
| 335 | +5. If none of the above are set, no transactions are sampled. This is equivalent to setting `traces_sample_rate=0.0` |
| 336 | + |
| 337 | +## How Sampling Propagates in Distributed Traces |
| 338 | + |
| 339 | +Sentry uses a "head-based" sampling approach: |
| 340 | + |
| 341 | +- A sampling decision is made in the originating service (the "head") |
| 342 | +- This decision is propagated to all downstream services |
| 343 | + |
| 344 | +The two key headers are: |
| 345 | +- `sentry-trace`: Contains trace ID, span ID, and sampling decision |
| 346 | +- `baggage`: Contains additional trace metadata including sample rate |
| 347 | + |
| 348 | +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/) |
0 commit comments