Configure Sampling
Learn how to configure sampling in your app.
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 below.
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:
sentry_sdk.init(
# ...
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for tracing.
# We recommend adjusting this value in production,
traces_sample_rate=1.0,
)
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.
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
It is strongly recommended when using a custom traces_sampler
that you respect the parent sampling decision. This ensures your traces will be complete.
# 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)
def traces_sampler(sampling_context):
# Examine provided context data (including parent decision, if any)
# along with anything in the global namespace to compute the sample rate
# or sampling decision for this transaction
if "...":
# These are important - take a big sample
return 0.5
elif "...":
# These are less important or happen much more frequently - only take 1%
return 0.01
elif "...":
# These aren't something worth tracking - drop all transactions like this
return 0
else:
# Default sample rate
return 0.1
sentry_sdk.init(
# ...
traces_sampler=traces_sampler,
)
Trace Sampler Examples
- Prioritizing Critical User Flows
def traces_sampler(sampling_context):
ctx = sampling_context.get("transaction_context", {})
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,
)
- Handling Different Environments and Error Rates
def traces_sampler(sampling_context):
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
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
return 0.1 # 10% default
sentry_sdk.init(
dsn="your-dsn",
traces_sampler=traces_sampler,
)
- Controlling Sampling Based on User and Transaction Properties
def traces_sampler(sampling_context):
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", "").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
# If there's a parent sampling decision, respect it
if sampling_context.get("parent_sampled") is not None:
return sampling_context["parent_sampled"]
# Default sampling rate
return 0.2
sentry_sdk.init(
dsn="your-dsn",
traces_sampler=traces_sampler,
)
- Complex Business Logic Sampling
def traces_sampler(sampling_context):
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
# Inherit parent sampling decision if available
if sampling_context.get("parent_sampled") is not None:
return sampling_context["parent_sampled"]
# Default sampling rate
return 0.1
sentry_sdk.init(
dsn="your-dsn",
traces_sampler=traces_sampler,
)
- Performance-Based Sampling
def traces_sampler(sampling_context):
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
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,
)
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_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] | None, # whether the parent transaction was sampled, `None` if no parent
"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
}
Additional common types used in sampling_context
:
- 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
In distributed systems, trace information is propagated between services. You can implement inheritance logic like this:
def traces_sampler(sampling_context):
# Examine provided context data
if "transaction_context" in sampling_context:
name = sampling_context["transaction_context"].get("name", "")
# Apply specific rules first
if "critical-path" in name:
return 1.0 # Always sample
# Inherit parent sampling decision if available
if sampling_context.get("parent_sampled") is not None:
return sampling_context["parent_sampled"]
# Otherwise use a default rate
return 0.1
This approach ensures consistent sampling decisions across your entire distributed trace. All transactions in a given trace will share the same sampling decision, preventing broken or incomplete traces.
When multiple sampling mechanisms could apply, Sentry follows this order of precedence:
- If a sampling decision is passed to
start_transaction
, that decision is used - If
traces_sampler
is defined, its decision is used. Although thetraces_sampler
can override the parent sampling decision, most users will want to ensure theirtraces_sampler
respects the parent sampling decision - If no
traces_sampler
is defined, but there is a parent sampling decision from an incoming distributed trace, we use the parent sampling decision - If neither of the above,
traces_sample_rate
is used - If none of the above are set, no transactions are sampled. This is equivalent to setting
traces_sample_rate=0.0
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 decisionbaggage
: 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.
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").