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:

Copied
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
Copied
    # 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)
Copied
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

  1. Prioritizing Critical User Flows
Copied
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,
)
  1. Handling Different Environments and Error Rates
Copied
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,
)
  1. Controlling Sampling Based on User and Transaction Properties
Copied
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,
)
  1. Complex Business Logic Sampling
Copied
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,
)
  1. Performance-Based Sampling
Copied
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:

Copied
{
    "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:

Copied
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:

  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

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.

Was this helpful?
Help improve this content
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").