diff --git a/CHANGELOG.md b/CHANGELOG.md index b234eb837dc..c2da0fcf070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Match patterns iteratively instead of recursively. ([#6188](https://github.com/getsentry/relay/pull/6188)) - Expand size of outcome quantities from `u32` to `u64`. ([#6133](https://github.com/getsentry/relay/pull/6133)) - Internally handle outcomes as metrics. ([#6107](https://github.com/getsentry/relay/pull/6107)) +- Re-introduce `projects:discard-transaction` feature flag. ([#6199](https://github.com/getsentry/relay/pull/6199)) - Have relay generate metric billing outcomes. ([#6066](https://github.com/getsentry/relay/pull/6066)) - Update sentry-conventions to 0.12.0. - Update sentry-conventions to 0.13.0. ([#6139](https://github.com/getsentry/relay/pull/6139)) diff --git a/relay-filter/src/common.rs b/relay-filter/src/common.rs index fd264546fd6..05f485383b4 100644 --- a/relay-filter/src/common.rs +++ b/relay-filter/src/common.rs @@ -42,6 +42,11 @@ pub enum FilterStatKey { /// Filtered due to the namespace being disabled. DisabledNamespace, + /// Filtered by Relay. + /// + /// This is currently only used for transactions, after spans have been extracted. + Discarded, + /// Filtered due to a generic filter. GenericFilter(String), } @@ -73,6 +78,7 @@ impl FilterStatKey { FilterStatKey::FilteredTransactions => "filtered-transaction", FilterStatKey::DeniedName => "denied-name", FilterStatKey::DisabledNamespace => "disabled-namespace", + FilterStatKey::Discarded => "discarded", FilterStatKey::GenericFilter(filter_identifier) => { return Cow::Owned(filter_identifier); } diff --git a/relay-server/src/processing/transactions/types/output.rs b/relay-server/src/processing/transactions/types/output.rs index 4180b681063..ae61ceb2bc1 100644 --- a/relay-server/src/processing/transactions/types/output.rs +++ b/relay-server/src/processing/transactions/types/output.rs @@ -53,6 +53,8 @@ impl Forward for TransactionOutput { ctx: ForwardContext<'_>, ) -> Result<(), Rejected<()>> { use crate::services::store::StoreEvent; + use relay_dynamic_config::Feature; + use relay_filter::FilterStatKey; let (spans, transaction) = match self { TransactionOutput::Full(managed) => { @@ -67,7 +69,7 @@ impl Forward for TransactionOutput { let performance_issues_spans = ctx .project_info - .has_feature(relay_dynamic_config::Feature::PerformanceIssuesSpans); + .has_feature(Feature::PerformanceIssuesSpans); if let Some(spans) = spans { let event_id = transaction.headers.event_id(); @@ -112,7 +114,11 @@ impl Forward for TransactionOutput { (profile, event) }); - s.send_event(event); + let discard_transactions = ctx.project_info.has_feature(Feature::DiscardTransaction); + match discard_transactions { + false => s.send_event(event), + true => drop(event.reject_err(Outcome::Filtered(FilterStatKey::Discarded))), + } if let Some(profile) = profile.transpose() { s.send_to_store(profile.map(|p, _| store::convert_profile(p, true, ctx))); diff --git a/tests/integration/test_spans.py b/tests/integration/test_spans.py index 12b5d4efbde..4d8a2a95860 100644 --- a/tests/integration/test_spans.py +++ b/tests/integration/test_spans.py @@ -14,8 +14,6 @@ TEST_CONFIG = { "aggregator": { - "bucket_interval": 1, - "initial_delay": 0, "shift_key": "none", } } @@ -1070,6 +1068,42 @@ def summarize_outcomes(): assert span_usage_metric() == 2 +def test_discard_transaction( + mini_sentry, + relay_with_processing, + transactions_consumer, + spans_consumer, + outcomes_consumer, +): + transactions_consumer = transactions_consumer() + spans_consumer = spans_consumer() + outcomes_consumer = outcomes_consumer() + + project_id = 42 + project_config = mini_sentry.add_full_project_config(project_id) + project_config["config"].setdefault("features", []).append( + "projects:discard-transaction" + ) + + relay = relay_with_processing(options=TEST_CONFIG) + + start = datetime.now(timezone.utc) + end = start + timedelta(seconds=1) + + relay.send_envelope(project_id, envelope_with_transaction_and_spans(start, end)) + + # We have one nested span and the transaction itself becomes a span + spans = spans_consumer.get_spans(n=2) + assert len(spans) == 2 + + outcomes = outcomes_consumer.get_outcomes() + assert [(o["category"], o["outcome"], o["reason"]) for o in outcomes] == [ + (9, 1, "discarded"), # TransactionIndexed, Filtered + ] + + transactions_consumer.assert_empty() + + def test_span_filtering_with_generic_inbound_filter( mini_sentry, relay_with_processing, spans_consumer, outcomes_consumer ):