-
Notifications
You must be signed in to change notification settings - Fork 568
Open
Description
How do you use Sentry?
Sentry Saas (sentry.io)
Version
2.40.0
Steps to Reproduce
Start a transaction and call add_feature_flag, then finish the transaction and have it sent to Sentry.
Example traces:
- transaction and child spans: https://sentry-sdks.sentry.io/explore/traces/trace/4d9cce0eacc546b59ced37861c7d0030/?node=span-b3d2669fd4029a77&project=4510243434266624&source=traces&statsPeriod=7d&targetId=b485e4838313dbda×tamp=1761293303
- only transaction: https://sentry-sdks.sentry.io/explore/traces/trace/68c91c57f22a466892fdf6d2997aaf4d/?node=span-8237a3737ec162eb&project=4510243434266624&source=traces&statsPeriod=7d&targetId=8237a3737ec162eb×tamp=1761293303
Here's a repro script:
"""
Demo script showing how feature flags work with transactions in Sentry Python SDK.
"""
import sentry_sdk
from sentry_sdk import start_transaction, start_span
from sentry_sdk.feature_flags import add_feature_flag
import time
def main():
# Initialize Sentry SDK with 100% transaction sampling
sentry_sdk.init(
dsn="https://a2e6d6ad22e4fabbbcb47b62a920c820@o447951.ingest.us.sentry.io/4510243434266624", # Replace with your DSN
traces_sample_rate=1.0, # Capture 100% of transactions
debug=True, # Enable debug mode to see what's being sent
)
print("=" * 60)
print("Testing Feature Flags with Transactions")
print("=" * 60)
# Scenario 1: Feature flags added directly to transaction
print("\n[Scenario 1] Adding flags directly to transaction...")
with start_transaction(name="transaction_with_flags", op="test"):
add_feature_flag("feature_on_transaction", True)
add_feature_flag("another_flag", False)
time.sleep(0.1)
# Scenario 2: Feature flags added to child spans
print("\n[Scenario 2] Adding flags to child spans...")
with start_transaction(name="transaction_with_child_spans", op="test"):
add_feature_flag("flag_before_span", True)
with start_span(op="child_operation", name="first_child_span"):
add_feature_flag("flag_in_first_child", True)
add_feature_flag("flag_in_first_child_2", False)
time.sleep(0.05)
with start_span(op="another_operation", name="second_child_span"):
add_feature_flag("flag_in_second_child", True)
time.sleep(0.05)
# Scenario 3: Many flags to test the 10-flag limit on spans
print("\n[Scenario 3] Testing span flag limit (10 max)...")
with start_transaction(name="transaction_with_many_flags", op="test"):
with start_span(op="heavy_flagging", name="span_with_many_flags"):
for i in range(15): # Try to add 15 flags (only 10 will be stored)
add_feature_flag(f"flag_{i}", i % 2 == 0)
time.sleep(0.05)
# Scenario 4: Feature flags + error event
print("\n[Scenario 4] Adding flags with error capture...")
with start_transaction(name="transaction_with_error", op="test"):
add_feature_flag("flag_before_error", True)
with start_span(op="problematic_operation", name="span_before_error"):
add_feature_flag("flag_in_span_before_error", False)
time.sleep(0.05)
# Capture an error to see flags in error context
try:
result = 1 / 0
except ZeroDivisionError as e:
sentry_sdk.capture_exception(e)
# Scenario 5: Using lower-level APIs
print("\n[Scenario 5] Using lower-level APIs (scope and span direct access)...")
with start_transaction(name="transaction_low_level_api", op="test"):
# Get isolation scope and add flags directly
isolation_scope = sentry_sdk.get_isolation_scope()
isolation_scope.flags.set("scope_only_flag", True)
isolation_scope.flags.set("another_scope_flag", False)
with start_span(op="custom_operation", name="span_with_direct_flags") as span:
# Add flag directly to span only (won't be in scope)
span.set_flag("flag.evaluation.span_only_flag", True)
span.set_flag("flag.evaluation.another_span_flag", False)
# Compare with high-level API (adds to both scope and span)
add_feature_flag("both_scope_and_span_flag", True)
time.sleep(0.05)
print("\n" + "=" * 60)
print("All scenarios completed!")
print("=" * 60)
print("\nCheck your Sentry dashboard to see:")
print("- Transaction events with flags in spans[].data")
print("- Error events with flags in contexts.flags.values")
print("- Note: Flags on transaction itself (not child spans) are NOT sent")
print("\nAPI Options:")
print(" 1. add_feature_flag(name, value) - Recommended")
print(" Adds to BOTH isolation_scope.flags AND current span")
print(" 2. isolation_scope.flags.set(name, value)")
print(" Adds ONLY to scope (appears in error events)")
print(" 3. span.set_flag(key, value)")
print(" Adds ONLY to span (appears in transaction events)")
print("=" * 60)
# Give time for events to be sent
sentry_sdk.flush(timeout=2.0)
if __name__ == "__main__":
main()
Expected Result
Feature flag is visible in Sentry UI, similar to how it shows up for spans.
In this screenshot you can see what this looks like for spans:

Actual Result
Feature flag is missing.
In this screenshot you can see there's no feature flags on the transaction itself:
