Skip to content

feat(attachments): Migrate standalone logic#5703

Open
tobias-wilfert wants to merge 15 commits intomasterfrom
tobias-wilfert/feat/migrate-standalone-attachments
Open

feat(attachments): Migrate standalone logic#5703
tobias-wilfert wants to merge 15 commits intomasterfrom
tobias-wilfert/feat/migrate-standalone-attachments

Conversation

@tobias-wilfert
Copy link
Member

@tobias-wilfert tobias-wilfert commented Mar 6, 2026

Moves the standalone attachment logic out of process_standalone, migrating the logic to the new processor architecture.

Ref: https://linear.app/getsentry/issue/INGEST-796/extract-attachment-logic-from-process-standalone

@tobias-wilfert tobias-wilfert self-assigned this Mar 6, 2026
@linear-code
Copy link

linear-code bot commented Mar 6, 2026

Comment on lines +67 to +71
) -> Option<Managed<Self::UnitOfWork>> {
// For now only extract the standalone attachments.
if envelope.envelope().items().any(Item::creates_event) {
return None;
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we can just grab attachments here, since I would assume that this break in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to remove the check, but instead maybe explicitly turn it into a debug_assert!

Comment on lines -146 to +148
attachments = [
("att_1", "foo.txt", chunked_contents),
("att_2", "foobar.txt", b""),
]
relay.send_attachments(project_id, event_id, attachments)
relay.send_attachments(
project_id, event_id, [("att_1", "foo.txt", chunked_contents)]
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split this into 2 tests since the 'wrong' attachment arrived first now, causing the test to fail.
Not sure how this never flaked before.

Comment on lines -93 to +104
pub struct StoreAttachment {
pub struct StoreTraceAttachment {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed this to avoid naming collisions.

Comment on lines +321 to +325
/// Uploads the attachment.
///
/// This mutates the attachment item in-place, setting the `stored_key` field to the key in the
/// objectstore.
async fn handle_attachment(&self, mut attachment: Managed<StoreAttachment>) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic taken over from handle_envelope with minor changes.

@tobias-wilfert
Copy link
Member Author

Current plan is to deploy this without a feature flag, if there are objections to it I can still add one.

@tobias-wilfert tobias-wilfert marked this pull request as ready for review March 9, 2026 16:13
@tobias-wilfert tobias-wilfert requested a review from a team as a code owner March 9, 2026 16:13
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Standalone attachments missing PII scrubbing for untyped items
    • Changed filter to use unwrap_or_default() so attachments without explicit AttachmentType headers are correctly routed to StandaloneAttachments group for PII scrubbing.

Create PR

Or push these changes by commenting:

@cursor push fc2208ed3e
Preview (fc2208ed3e)
diff --git a/relay-server/src/services/processor.rs b/relay-server/src/services/processor.rs
--- a/relay-server/src/services/processor.rs
+++ b/relay-server/src/services/processor.rs
@@ -380,7 +380,7 @@
         if !envelope.items().any(Item::creates_event) {
             let standalone_attachments = envelope.take_items_by(|i| {
                 i.requires_event()
-                    && matches!(i.attachment_type(), Some(AttachmentType::Attachment))
+                    && i.attachment_type().unwrap_or_default() == AttachmentType::Attachment
             });
             if !standalone_attachments.is_empty() {
                 grouped_envelopes.push((
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Some(Outcome::RateLimited(reason_code))
}
#[cfg(feature = "processing")]
Self::NoEventId => Some(Outcome::Invalid(DiscardReason::Internal)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Relay somehow already ensures there is always an event id?

What happens if the there is a standalone attachment sent in an envelope without event id? Does it make sense to ingest it? And does that actually mean it's Internal?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old logic checks it in the store here, so this check "just moved into the processor".

event_id.ok_or(StoreError::NoEventId)?,

The outcome is the same as that of the store error (but we can ofcourse improve it):

pub enum StoreError {
#[error("failed to send the message to kafka: {0}")]
SendFailed(#[from] ClientError),
#[error("failed to encode data: {0}")]
EncodingFailed(std::io::Error),
#[error("failed to store event because event id was missing")]
NoEventId,
}
impl OutcomeError for StoreError {
type Error = Self;
fn consume(self) -> (Option<Outcome>, Self::Error) {
(Some(Outcome::Invalid(DiscardReason::Internal)), self)
}
}

Comment on lines +67 to +71
) -> Option<Managed<Self::UnitOfWork>> {
// For now only extract the standalone attachments.
if envelope.envelope().items().any(Item::creates_event) {
return None;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to remove the check, but instead maybe explicitly turn it into a debug_assert!

Comment on lines +333 to +340
Err(error) => {
relay_log::error!(error = &error as &dyn std::error::Error, "session error");
relay_statsd::metric!(
counter(RelayCounters::AttachmentUpload) += 1,
result = error.to_string().as_str(),
type = "attachment",
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This match is pretty large, maybe an early return makes it more readable.

session.inspect_err(|_| <analytics here>)?;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is a bit sneaky in the sense that at the end we still do self.store.send(attachment) no matter what match arm we end up in.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@tobias-wilfert tobias-wilfert requested a review from jjbayer March 10, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants