From 9589c17ca6cdafe742c16c69f5936f247b3bff02 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 11:01:48 +0200 Subject: [PATCH 01/16] synthesize IDs only for "creates event" items --- relay-server/src/envelope/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/envelope/mod.rs b/relay-server/src/envelope/mod.rs index c8946dc9047..4f16509d9e2 100644 --- a/relay-server/src/envelope/mod.rs +++ b/relay-server/src/envelope/mod.rs @@ -330,7 +330,7 @@ impl Envelope { // Event-related envelopes *must* contain an event id. let items = Self::parse_items(&bytes, offset)?; - if items.iter().any(Item::requires_event) { + if items.iter().any(Item::creates_event) { headers.event_id.get_or_insert_with(EventId::new); } From 238e9eb91c75dc914c4d6b09ed2f22beb75ab35e Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 11:03:02 +0200 Subject: [PATCH 02/16] Improve discard reason --- relay-server/src/processing/attachments/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/processing/attachments/mod.rs b/relay-server/src/processing/attachments/mod.rs index c38cd659efa..f86c4c42940 100644 --- a/relay-server/src/processing/attachments/mod.rs +++ b/relay-server/src/processing/attachments/mod.rs @@ -36,7 +36,7 @@ impl OutcomeError for Error { Some(Outcome::RateLimited(reason_code)) } #[cfg(feature = "processing")] - Self::NoEventId => Some(Outcome::Invalid(DiscardReason::Internal)), + Self::NoEventId => Some(Outcome::Invalid(DiscardReason::InvalidEventId)), }; (outcome, self) } From e583a936d4914c521fd2c3fe5c0dbd340eb8c977 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 11:04:51 +0200 Subject: [PATCH 03/16] Also check event ID in serialize_envelope --- relay-server/src/processing/attachments/forward.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/relay-server/src/processing/attachments/forward.rs b/relay-server/src/processing/attachments/forward.rs index f1dc43e55ac..e79fdffcecc 100644 --- a/relay-server/src/processing/attachments/forward.rs +++ b/relay-server/src/processing/attachments/forward.rs @@ -1,6 +1,6 @@ use crate::Envelope; use crate::managed::{Managed, Rejected}; -use crate::processing::attachments::AttachmentsOutput; +use crate::processing::attachments::{AttachmentsOutput, Error}; use crate::processing::{self, Forward}; impl Forward for AttachmentsOutput { @@ -9,6 +9,11 @@ impl Forward for AttachmentsOutput { _: processing::ForwardContext<'_>, ) -> Result>, Rejected<()>> { let Self(attachments) = self; + + if attachments.headers.event_id().is_none() { + return Err(attachments.reject_err(Error::NoEventId).map(drop)); + } + Ok(attachments.map(|attachments, _| { Envelope::from_parts(attachments.headers, attachments.attachments) })) @@ -20,8 +25,6 @@ impl Forward for AttachmentsOutput { s: processing::StoreHandle<'_>, ctx: processing::ForwardContext<'_>, ) -> Result<(), Rejected<()>> { - use crate::processing::attachments::Error; - let Self(attachments) = self; let Some(event_id) = attachments.headers.event_id() else { From 93d34936848be64da42f548e3269ebbde5dc0632 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 11:08:59 +0200 Subject: [PATCH 04/16] Integration test --- tests/integration/test_attachments.py | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/integration/test_attachments.py b/tests/integration/test_attachments.py index 00a9dffd98a..184d59facf0 100644 --- a/tests/integration/test_attachments.py +++ b/tests/integration/test_attachments.py @@ -6,6 +6,7 @@ from requests.exceptions import HTTPError from sentry_sdk.envelope import Envelope, Item, PayloadRef +from sentry_relay.consts import DataCategory from .asserts import matches_any from .test_store import make_transaction @@ -736,6 +737,53 @@ def test_event_with_attachment( assert event["event_id"] == event_id +def test_attachment_without_event_id( + mini_sentry, + relay_with_processing, + outcomes_consumer, +): + project_id = 42 + + mini_sentry.add_full_project_config(project_id) + outcomes_consumer = outcomes_consumer() + + relay = relay_with_processing() + + envelope = Envelope(headers=[]) + envelope.add_item( + Item( + type="attachment", + payload=PayloadRef(bytes=b"event attachment"), + filename="event.txt", + content_type="text/plain", + ) + ) + + relay.send_envelope(project_id, envelope) + + outcomes = outcomes_consumer.get_aggregated_outcomes(n=2) + assert outcomes == [ + { + "category": DataCategory.ATTACHMENT.value, + "key_id": 123, + "org_id": 1, + "outcome": 3, # Invalid + "project_id": 42, + "quantity": 16, + "reason": "invalid_event_id", + }, + { + "category": DataCategory.ATTACHMENT_ITEM.value, + "key_id": 123, + "org_id": 1, + "outcome": 3, # Invalid + "project_id": 42, + "quantity": 1, + "reason": "invalid_event_id", + }, + ] + + def test_form_data_is_rejected( mini_sentry, relay_with_processing, attachments_consumer, outcomes_consumer ): From c5a7a255e7fd964ccb58497004ff83fbf1ef0334 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 11:28:55 +0200 Subject: [PATCH 05/16] Do check in process --- relay-server/src/processing/attachments/forward.rs | 9 +++------ relay-server/src/processing/attachments/mod.rs | 4 ++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/relay-server/src/processing/attachments/forward.rs b/relay-server/src/processing/attachments/forward.rs index e79fdffcecc..f1dc43e55ac 100644 --- a/relay-server/src/processing/attachments/forward.rs +++ b/relay-server/src/processing/attachments/forward.rs @@ -1,6 +1,6 @@ use crate::Envelope; use crate::managed::{Managed, Rejected}; -use crate::processing::attachments::{AttachmentsOutput, Error}; +use crate::processing::attachments::AttachmentsOutput; use crate::processing::{self, Forward}; impl Forward for AttachmentsOutput { @@ -9,11 +9,6 @@ impl Forward for AttachmentsOutput { _: processing::ForwardContext<'_>, ) -> Result>, Rejected<()>> { let Self(attachments) = self; - - if attachments.headers.event_id().is_none() { - return Err(attachments.reject_err(Error::NoEventId).map(drop)); - } - Ok(attachments.map(|attachments, _| { Envelope::from_parts(attachments.headers, attachments.attachments) })) @@ -25,6 +20,8 @@ impl Forward for AttachmentsOutput { s: processing::StoreHandle<'_>, ctx: processing::ForwardContext<'_>, ) -> Result<(), Rejected<()>> { + use crate::processing::attachments::Error; + let Self(attachments) = self; let Some(event_id) = attachments.headers.event_id() else { diff --git a/relay-server/src/processing/attachments/mod.rs b/relay-server/src/processing/attachments/mod.rs index f86c4c42940..90833eaa65f 100644 --- a/relay-server/src/processing/attachments/mod.rs +++ b/relay-server/src/processing/attachments/mod.rs @@ -96,6 +96,10 @@ impl processing::Processor for AttachmentProcessor { mut attachments: Managed, ctx: processing::Context<'_>, ) -> Result, Rejected> { + if attachments.headers.event_id().is_none() { + return Err(attachments.reject_err(Error::NoEventId)); + } + attachments::validate_attachments(&mut attachments, |a| &mut a.attachments, ctx); let mut attachments = self.limiter.enforce_quotas(attachments, ctx).await?; process::scrub(&mut attachments, ctx)?; From 003fb7c29f63673056d65853bad594955d59c705 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 12:40:28 +0200 Subject: [PATCH 06/16] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5df6dcb0dbb..d31e06f4767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Reject Nintendo Switch dying message attachments with an invalid magic number instead of panicking on a short payload. ([#6253](https://github.com/getsentry/relay/pull/6253)) - Downgrade Kafka to prevent producers from getting stuck. ([#6336](https://github.com/getsentry/relay/pull/6336)) - Prevent memory bomb in PII processor's `split_chunks`. ([#6343](https://github.com/getsentry/relay/pull/6343)) +- Fill in missing event IDs only if items would create events. ([#6350](https://github.com/getsentry/relay/pull/6350)) **Internal**: From 25f185eb3b4478e02075ea154ec2a38d32a55012 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 12:45:51 +0200 Subject: [PATCH 07/16] Also change discard reason for user reports and replays --- relay-server/src/processing/replays/mod.rs | 2 +- relay-server/src/processing/user_reports/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/relay-server/src/processing/replays/mod.rs b/relay-server/src/processing/replays/mod.rs index 0fbf18d91f8..4ea14d13463 100644 --- a/relay-server/src/processing/replays/mod.rs +++ b/relay-server/src/processing/replays/mod.rs @@ -118,7 +118,7 @@ impl OutcomeError for Error { crate::services::outcome::DiscardItemType::ReplayRecording, ))), #[cfg(feature = "processing")] - Self::NoEventId => Some(Outcome::Invalid(DiscardReason::Internal)), + Self::NoEventId => Some(Outcome::Invalid(DiscardReason::InvalidEventId)), }; (outcome, self) } diff --git a/relay-server/src/processing/user_reports/mod.rs b/relay-server/src/processing/user_reports/mod.rs index 5732c835afb..9139c59c8d4 100644 --- a/relay-server/src/processing/user_reports/mod.rs +++ b/relay-server/src/processing/user_reports/mod.rs @@ -36,7 +36,7 @@ impl OutcomeError for Error { } #[cfg(feature = "processing")] Self::NoEventId => Some(Outcome::Invalid( - crate::services::outcome::DiscardReason::Internal, + crate::services::outcome::DiscardReason::InvalidEventId, )), }; (outcome, self) From cb4e6ae964cd52af4d22ade7858d5c24a82a1f6f Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 12:57:52 +0200 Subject: [PATCH 08/16] Don't gate error variant behind processing --- relay-server/src/processing/attachments/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/relay-server/src/processing/attachments/mod.rs b/relay-server/src/processing/attachments/mod.rs index 9f64a0e6e4d..9b453ce570e 100644 --- a/relay-server/src/processing/attachments/mod.rs +++ b/relay-server/src/processing/attachments/mod.rs @@ -7,7 +7,6 @@ use crate::envelope::{EnvelopeHeaders, Item, ItemType, Items}; use crate::managed::{Counted, Managed, ManagedEnvelope, OutcomeError, Quantities, Rejected}; use crate::processing::utils::attachments; use crate::processing::{self, CountRateLimited, Output, QuotaRateLimiter}; -#[cfg(feature = "processing")] use crate::services::outcome::DiscardReason; use crate::services::outcome::Outcome; use crate::statsd::RelayCounters; @@ -23,7 +22,6 @@ pub enum Error { RateLimited(RateLimits), /// The envelope did not contain an event ID. - #[cfg(feature = "processing")] #[error("missing event ID")] NoEventId, } @@ -37,7 +35,6 @@ impl OutcomeError for Error { let reason_code = limits.longest().and_then(|limit| limit.reason_code.clone()); Some(Outcome::RateLimited(reason_code)) } - #[cfg(feature = "processing")] Self::NoEventId => Some(Outcome::Invalid(DiscardReason::InvalidEventId)), }; (outcome, self) From 61157e0674ad4e1cb1967ccf58a8d35e549d6eab Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 13:23:13 +0200 Subject: [PATCH 09/16] Fix user report test --- tests/integration/test_user_report.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_user_report.py b/tests/integration/test_user_report.py index 649eb1a0532..86b961caf58 100644 --- a/tests/integration/test_user_report.py +++ b/tests/integration/test_user_report.py @@ -13,17 +13,19 @@ def test_standalone_user_report( relay = relay_with_processing() mini_sentry.add_full_project_config(project_id) + event_id = "4cec9f3e1f214073b816e0f4de5f59b1" + report_payload = { "name": "Josh", "email": "", "comments": "I'm having fun", - "event_id": "4cec9f3e1f214073b816e0f4de5f59b1", + "event_id": event_id, } - relay.send_user_report( - project_id, - report_payload, - ) + envelope = Envelope(headers={"event_id": event_id}) + envelope.add_item(Item(PayloadRef(json=report_payload), type="user_report")) + + relay.send_envelope(project_id, envelope) report = attachments_consumer.get_user_report(timeout=5) assert json.loads(report["payload"]) == report_payload From e8d37945dc2fdcde32535789398b4a5cdf338a72 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 13:26:14 +0200 Subject: [PATCH 10/16] Fix unknown item test --- tests/integration/test_envelope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_envelope.py b/tests/integration/test_envelope.py index d733e8c180c..92d1bb97f8d 100644 --- a/tests/integration/test_envelope.py +++ b/tests/integration/test_envelope.py @@ -92,7 +92,7 @@ def test_unknown_item(mini_sentry, relay): PROJECT_ID = 42 mini_sentry.add_basic_project_config(PROJECT_ID) - envelope = Envelope() + envelope = Envelope(headers={"event_id": "d2132d31b39445f1938d7e21b6bf0ec4"}) envelope.add_item( Item(payload=PayloadRef(bytes=b"something"), type="invalid_unknown") ) From 47bee860a8834bd504edf6106f10231c91c3d993 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 13:28:14 +0200 Subject: [PATCH 11/16] Fix pii_logfile test --- tests/integration/test_attachments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_attachments.py b/tests/integration/test_attachments.py index 184d59facf0..84682503775 100644 --- a/tests/integration/test_attachments.py +++ b/tests/integration/test_attachments.py @@ -447,7 +447,7 @@ def test_attachments_pii_logfile(mini_sentry, relay): path=c:\Users\yan\mylogfile.txt password=mysupersecretpassword123""" - envelope = Envelope() + envelope = Envelope(headers={"event_id": "515539018c9b4260a6f999572f1661ee"}) item = Item( payload=attachment, type="attachment", headers={"filename": "logfile.txt"} ) From f6c5a94afc4b0ca41d101bad388b230c3d3a257b Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 13:29:30 +0200 Subject: [PATCH 12/16] Fix drop_unknown_item test --- tests/integration/test_envelope.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_envelope.py b/tests/integration/test_envelope.py index 92d1bb97f8d..0444ffd246d 100644 --- a/tests/integration/test_envelope.py +++ b/tests/integration/test_envelope.py @@ -120,7 +120,7 @@ def test_drop_unknown_item(mini_sentry, relay): PROJECT_ID = 42 mini_sentry.add_basic_project_config(PROJECT_ID) - envelope = Envelope() + envelope = Envelope(headers={"event_id": "d2132d31b39445f1938d7e21b6bf0ec4"}) envelope.add_item(Item(payload=PayloadRef(bytes=b"something"), type="attachment")) envelope.add_item( Item(payload=PayloadRef(bytes=b"something"), type="invalid_unknown") From c65257178c56518bcb1f79949e775fef9e7a69b2 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 13:39:50 +0200 Subject: [PATCH 13/16] Add metrics for replays & user reports --- relay-server/src/processing/replays/forward.rs | 17 ++++++++++++----- .../src/processing/user_reports/forward.rs | 9 +++++++++ relay-server/src/statsd.rs | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/relay-server/src/processing/replays/forward.rs b/relay-server/src/processing/replays/forward.rs index e8270b6a9b1..461bfb918b6 100644 --- a/relay-server/src/processing/replays/forward.rs +++ b/relay-server/src/processing/replays/forward.rs @@ -48,13 +48,20 @@ impl Forward for ReplaysOutput { s: processing::StoreHandle<'_>, ctx: processing::ForwardContext<'_>, ) -> Result<(), Rejected<()>> { + use crate::{statsd::RelayCounters, utils::client_name_tag}; + let Self(replay) = self; - let event_id = replay.headers.event_id().ok_or_else(|| { - replay - .reject_err(crate::processing::replays::Error::NoEventId) - .map(drop) - })?; + let Some(event_id) = replay.headers.event_id() else { + // Temporary counter to figure out which SDKs are sendingb replays without event IDs. + relay_statsd::metric!( + counter(RelayCounters::Replay) += 1, + sdk = client_name_tag(replay.headers.meta().client_name()), + has_event_id = "false", + ); + + return Err(replay.reject_err(super::Error::NoEventId).map(drop)); + }; let ctx = store::Context { event_id, diff --git a/relay-server/src/processing/user_reports/forward.rs b/relay-server/src/processing/user_reports/forward.rs index 311abea398c..a641087dd7d 100644 --- a/relay-server/src/processing/user_reports/forward.rs +++ b/relay-server/src/processing/user_reports/forward.rs @@ -22,9 +22,18 @@ impl Forward for UserReportsOutput { _: ForwardContext<'_>, ) -> Result<(), Rejected<()>> { use crate::services::store::StoreUserReport; + use crate::statsd::RelayCounters; + use crate::utils::client_name_tag; let Self(reports) = self; let Some(event_id) = reports.headers.event_id() else { + // Temporary counter to figure out which SDKs are sendingb user reports without event IDs. + relay_statsd::metric!( + counter(RelayCounters::UserReport) += 1, + sdk = client_name_tag(reports.headers.meta().client_name()), + has_event_id = "false", + ); + return Err(reports.reject_err(super::Error::NoEventId).map(drop)); }; diff --git a/relay-server/src/statsd.rs b/relay-server/src/statsd.rs index 4f2a741b90b..87630cc1259 100644 --- a/relay-server/src/statsd.rs +++ b/relay-server/src/statsd.rs @@ -1028,6 +1028,18 @@ pub enum RelayCounters { /// - `sdk`: low-cardinality client name /// - `has_event_id`: whether the envelope contained an event ID StandaloneAttachment, + /// Amount of user reports processed. + /// + /// This metric is tagged with: + /// - `sdk`: low-cardinality client name + /// - `has_event_id`: whether the envelope contained an event ID + UserReport, + /// Amount of replays processed. + /// + /// This metric is tagged with: + /// - `sdk`: low-cardinality client name + /// - `has_event_id`: whether the envelope contained an event ID + Replay, } impl CounterMetric for RelayCounters { @@ -1095,6 +1107,8 @@ impl CounterMetric for RelayCounters { RelayCounters::CompressedMinidump => "minidump.compressed.count", RelayCounters::TraceMetricNilTraceId => "trace_metric.nil_trace_id", RelayCounters::StandaloneAttachment => "processing.standalone_attachment", + RelayCounters::UserReport => "processing.user_report", + RelayCounters::Replay => "processing.replay", } } } From 25fe72a920bdd9b58726bcff642167f9c1504982 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 13:51:15 +0200 Subject: [PATCH 14/16] Pull checks into processing for user reports & replays --- .../src/processing/replays/forward.rs | 9 -------- relay-server/src/processing/replays/mod.rs | 2 -- .../src/processing/replays/validate.rs | 15 +++++++++++++ .../src/processing/user_reports/forward.rs | 9 -------- .../src/processing/user_reports/mod.rs | 8 ++++--- .../src/processing/user_reports/validate.rs | 21 +++++++++++++++++++ 6 files changed, 41 insertions(+), 23 deletions(-) create mode 100644 relay-server/src/processing/user_reports/validate.rs diff --git a/relay-server/src/processing/replays/forward.rs b/relay-server/src/processing/replays/forward.rs index 461bfb918b6..6199c4bcee6 100644 --- a/relay-server/src/processing/replays/forward.rs +++ b/relay-server/src/processing/replays/forward.rs @@ -48,18 +48,9 @@ impl Forward for ReplaysOutput { s: processing::StoreHandle<'_>, ctx: processing::ForwardContext<'_>, ) -> Result<(), Rejected<()>> { - use crate::{statsd::RelayCounters, utils::client_name_tag}; - let Self(replay) = self; let Some(event_id) = replay.headers.event_id() else { - // Temporary counter to figure out which SDKs are sendingb replays without event IDs. - relay_statsd::metric!( - counter(RelayCounters::Replay) += 1, - sdk = client_name_tag(replay.headers.meta().client_name()), - has_event_id = "false", - ); - return Err(replay.reject_err(super::Error::NoEventId).map(drop)); }; diff --git a/relay-server/src/processing/replays/mod.rs b/relay-server/src/processing/replays/mod.rs index 4ea14d13463..130edb1043f 100644 --- a/relay-server/src/processing/replays/mod.rs +++ b/relay-server/src/processing/replays/mod.rs @@ -76,7 +76,6 @@ pub enum Error { #[error("replay recording too large")] TooLarge, /// The envelope did not contain an event ID. - #[cfg(feature = "processing")] #[error("missing replay ID")] NoEventId, } @@ -117,7 +116,6 @@ impl OutcomeError for Error { Self::TooLarge => Some(Outcome::Invalid(DiscardReason::ItemTooLarge( crate::services::outcome::DiscardItemType::ReplayRecording, ))), - #[cfg(feature = "processing")] Self::NoEventId => Some(Outcome::Invalid(DiscardReason::InvalidEventId)), }; (outcome, self) diff --git a/relay-server/src/processing/replays/validate.rs b/relay-server/src/processing/replays/validate.rs index ccc3a0fbbea..334dacbd0e3 100644 --- a/relay-server/src/processing/replays/validate.rs +++ b/relay-server/src/processing/replays/validate.rs @@ -1,9 +1,24 @@ use relay_event_normalization::replay; use crate::processing::replays::{Error, ExpandedReplay, Result}; +use crate::statsd::RelayCounters; +use crate::utils::client_name_tag; /// Checks the structural validity of a replay, rejects it if invalid. pub fn validate(replay: &ExpandedReplay) -> Result<()> { + let has_event_id = replay.headers.event_id().is_some(); + + // Temporary counter to figure out which SDKs are sendingb replays without event IDs. + relay_statsd::metric!( + counter(RelayCounters::Replay) += 1, + sdk = client_name_tag(replay.headers.meta().client_name()), + has_event_id = has_event_id.to_string(), + ); + + if !has_event_id { + return Err(Error::NoEventId); + } + let Some(event) = replay.payload.event() else { return Ok(()); }; diff --git a/relay-server/src/processing/user_reports/forward.rs b/relay-server/src/processing/user_reports/forward.rs index a641087dd7d..311abea398c 100644 --- a/relay-server/src/processing/user_reports/forward.rs +++ b/relay-server/src/processing/user_reports/forward.rs @@ -22,18 +22,9 @@ impl Forward for UserReportsOutput { _: ForwardContext<'_>, ) -> Result<(), Rejected<()>> { use crate::services::store::StoreUserReport; - use crate::statsd::RelayCounters; - use crate::utils::client_name_tag; let Self(reports) = self; let Some(event_id) = reports.headers.event_id() else { - // Temporary counter to figure out which SDKs are sendingb user reports without event IDs. - relay_statsd::metric!( - counter(RelayCounters::UserReport) += 1, - sdk = client_name_tag(reports.headers.meta().client_name()), - has_event_id = "false", - ); - return Err(reports.reject_err(super::Error::NoEventId).map(drop)); }; diff --git a/relay-server/src/processing/user_reports/mod.rs b/relay-server/src/processing/user_reports/mod.rs index 9139c59c8d4..41fff578c0c 100644 --- a/relay-server/src/processing/user_reports/mod.rs +++ b/relay-server/src/processing/user_reports/mod.rs @@ -4,12 +4,15 @@ use relay_cogs::{AppFeature, FeatureWeights}; use relay_quotas::RateLimits; use crate::envelope::{EnvelopeHeaders, Item, ItemType}; -use crate::managed::{Counted, Managed, ManagedEnvelope, OutcomeError, Quantities, Rejected}; +use crate::managed::{ + Counted, Managed, ManagedEnvelope, ManagedResult, OutcomeError, Quantities, Rejected, +}; use crate::processing::{Context, CountRateLimited, Output, Processor, QuotaRateLimiter}; use crate::services::outcome::Outcome; mod forward; mod process; +mod validate; pub use process::process_user_reports; @@ -20,7 +23,6 @@ pub enum Error { RateLimited(RateLimits), /// The envelope did not contain an event ID. - #[cfg(feature = "processing")] #[error("missing event ID")] NoEventId, } @@ -34,7 +36,6 @@ impl OutcomeError for Error { let reason_code = limits.longest().and_then(|limit| limit.reason_code.clone()); Some(Outcome::RateLimited(reason_code)) } - #[cfg(feature = "processing")] Self::NoEventId => Some(Outcome::Invalid( crate::services::outcome::DiscardReason::InvalidEventId, )), @@ -91,6 +92,7 @@ impl Processor for UserReportsProcessor { mut reports: Managed, ctx: Context<'_>, ) -> Result, Rejected> { + validate::validate(&reports).reject(&reports)?; process::process(&mut reports); let reports = self.limiter.enforce_quotas(reports, ctx).await?; diff --git a/relay-server/src/processing/user_reports/validate.rs b/relay-server/src/processing/user_reports/validate.rs new file mode 100644 index 00000000000..bf8058ff729 --- /dev/null +++ b/relay-server/src/processing/user_reports/validate.rs @@ -0,0 +1,21 @@ +use crate::processing::user_reports::{Error, SerializedUserReports}; +use crate::statsd::RelayCounters; +use crate::utils::client_name_tag; + +/// Checks that the user reports contain an event ID. +pub fn validate(reports: &SerializedUserReports) -> Result<(), Error> { + let has_event_id = reports.headers.event_id().is_some(); + + // Temporary counter to figure out which SDKs are sending user reports without event IDs. + relay_statsd::metric!( + counter(RelayCounters::UserReport) += 1, + sdk = client_name_tag(reports.headers.meta().client_name()), + has_event_id = has_event_id.to_string(), + ); + + if !has_event_id { + return Err(Error::NoEventId); + } + + Ok(()) +} From 12ab51e6a0bacee71ce1654364155499a8c43785 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 14:56:27 +0200 Subject: [PATCH 15/16] Fix replay_event tests --- tests/integration/fixtures/__init__.py | 7 ++----- tests/integration/test_replay_events.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/integration/fixtures/__init__.py b/tests/integration/fixtures/__init__.py index 5db52dfc095..484272f35d1 100644 --- a/tests/integration/fixtures/__init__.py +++ b/tests/integration/fixtures/__init__.py @@ -335,15 +335,12 @@ def send_transaction( self.send_envelope(project_id, envelope) - def send_replay_event(self, project_id, payload, item_headers=None): - envelope = Envelope() + def send_replay_event(self, project_id, payload, envelope_headers=None): + envelope = Envelope(headers=envelope_headers) envelope.add_item(Item(payload=PayloadRef(json=payload), type="replay_event")) envelope.add_item( Item(payload=PayloadRef(bytes=b"{}\n[]"), type="replay_recording") ) - if envelope.headers is None: - envelope.headers = {} - self.send_envelope(project_id, envelope) def send_session_aggregates(self, project_id, payload, headers=None): diff --git a/tests/integration/test_replay_events.py b/tests/integration/test_replay_events.py index ff0cd1fdc47..e13ae982f40 100644 --- a/tests/integration/test_replay_events.py +++ b/tests/integration/test_replay_events.py @@ -122,7 +122,9 @@ def test_replay_events_without_processing(mini_sentry, relay_chain): replay_item = generate_replay_sdk_event() - relay.send_replay_event(42, replay_item) + relay.send_replay_event( + 42, replay_item, envelope_headers={"event_id": replay_item["event_id"]} + ) envelope = mini_sentry.get_captured_envelope(timeout=20) assert len(envelope.items) == 2 @@ -147,7 +149,9 @@ def test_replay_events_are_filtered( replay = generate_replay_sdk_event() replay["request"]["url"] = "http://localhost:1200" - relay.send_replay_event(42, replay) + relay.send_replay_event( + 42, replay, envelope_headers={"event_id": replay["event_id"]} + ) outcome = outcomes_consumer.get_outcome(timeout=10) assert outcome["org_id"] == 1 @@ -188,7 +192,9 @@ def test_time_corrections(mini_sentry, relay, delta, error): replay["timestamp"] = sdk_ts replay["replay_start_timestamp"] = sdk_start_ts - relay.send_replay_event(project_id, replay) + relay.send_replay_event( + 42, replay, envelope_headers={"event_id": replay["event_id"]} + ) if error == "past_timestamp": assert mini_sentry.get_aggregated_outcomes() == [ { From 61ca1f8771d3bf2308585e4a591e6aa065e34083 Mon Sep 17 00:00:00 2001 From: Sebastian Zivota Date: Thu, 3 Sep 2026 15:22:51 +0200 Subject: [PATCH 16/16] Apply suggestion from @jjbayer Co-authored-by: Joris Bayer --- relay-server/src/processing/replays/validate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-server/src/processing/replays/validate.rs b/relay-server/src/processing/replays/validate.rs index 334dacbd0e3..a94026892ce 100644 --- a/relay-server/src/processing/replays/validate.rs +++ b/relay-server/src/processing/replays/validate.rs @@ -8,7 +8,7 @@ use crate::utils::client_name_tag; pub fn validate(replay: &ExpandedReplay) -> Result<()> { let has_event_id = replay.headers.event_id().is_some(); - // Temporary counter to figure out which SDKs are sendingb replays without event IDs. + // Temporary counter to figure out which SDKs are sending replays without event IDs. relay_statsd::metric!( counter(RelayCounters::Replay) += 1, sdk = client_name_tag(replay.headers.meta().client_name()),