Skip to content

Commit ab46f30

Browse files
authored
fix(server): Forward-compatible AttachmentType (#1638)
As we did in #1246 for `ItemType`, add an `Unknown(String)` variant to `AttachmentType`. When `accept_unknown_items` is set to false, items with this attachment type will be dropped. This will allow outdated external Relays to forward new attachment types instead of dropping the entire envelope. This defect was discovered in getsentry/rfcs#33, which introduces a new attachment type.
1 parent dba7030 commit ab46f30

File tree

9 files changed

+105
-37
lines changed

9 files changed

+105
-37
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
- Add OpenTelemetry Context ([#1617](https://github.com/getsentry/relay/pull/1617))
99
- Add `app.in_foreground` and `thread.main` flag to protocol. ([#1578](https://github.com/getsentry/relay/pull/1578))
1010

11+
**Bug Fixes**:
12+
- Make `attachment_type` on envelope items forward compatible by adding fallback variant. ([#1638](https://github.com/getsentry/relay/pull/1638))
13+
1114
**Internal**:
1215

1316
- Emit a `service.back_pressure` metric that measures internal back pressure by service. ([#1583](https://github.com/getsentry/relay/pull/1583))

relay-server/src/actors/processor.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,11 +1471,11 @@ impl EnvelopeProcessorService {
14711471
let raw_security_item = envelope.take_item_by(|item| item.ty() == &ItemType::RawSecurity);
14721472
let form_item = envelope.take_item_by(|item| item.ty() == &ItemType::FormData);
14731473
let attachment_item = envelope
1474-
.take_item_by(|item| item.attachment_type() == Some(AttachmentType::EventPayload));
1474+
.take_item_by(|item| item.attachment_type() == Some(&AttachmentType::EventPayload));
14751475
let breadcrumbs1 = envelope
1476-
.take_item_by(|item| item.attachment_type() == Some(AttachmentType::Breadcrumbs));
1476+
.take_item_by(|item| item.attachment_type() == Some(&AttachmentType::Breadcrumbs));
14771477
let breadcrumbs2 = envelope
1478-
.take_item_by(|item| item.attachment_type() == Some(AttachmentType::Breadcrumbs));
1478+
.take_item_by(|item| item.attachment_type() == Some(&AttachmentType::Breadcrumbs));
14791479

14801480
// Event items can never occur twice in an envelope.
14811481
if let Some(duplicate) = envelope.get_item_by(|item| self.is_duplicate(item)) {
@@ -1550,9 +1550,9 @@ impl EnvelopeProcessorService {
15501550
let envelope = &mut state.envelope;
15511551

15521552
let minidump_attachment =
1553-
envelope.get_item_by(|item| item.attachment_type() == Some(AttachmentType::Minidump));
1553+
envelope.get_item_by(|item| item.attachment_type() == Some(&AttachmentType::Minidump));
15541554
let apple_crash_report_attachment = envelope
1555-
.get_item_by(|item| item.attachment_type() == Some(AttachmentType::AppleCrashReport));
1555+
.get_item_by(|item| item.attachment_type() == Some(&AttachmentType::AppleCrashReport));
15561556

15571557
if let Some(item) = minidump_attachment {
15581558
let event = state.event.get_or_insert_with(Event::default);
@@ -1608,7 +1608,7 @@ impl EnvelopeProcessorService {
16081608

16091609
let attachment_size = envelope
16101610
.items()
1611-
.filter(|item| item.attachment_type() == Some(AttachmentType::Attachment))
1611+
.filter(|item| item.attachment_type() == Some(&AttachmentType::Attachment))
16121612
.map(|item| item.len() as u64)
16131613
.sum::<u64>();
16141614

@@ -1896,7 +1896,7 @@ impl EnvelopeProcessorService {
18961896
let envelope = &mut state.envelope;
18971897
if let Some(ref config) = state.project_state.config.pii_config {
18981898
let minidump = envelope
1899-
.get_item_by_mut(|item| item.attachment_type() == Some(AttachmentType::Minidump));
1899+
.get_item_by_mut(|item| item.attachment_type() == Some(&AttachmentType::Minidump));
19001900

19011901
if let Some(item) = minidump {
19021902
let filename = item.filename().unwrap_or_default();

relay-server/src/actors/store.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ impl StoreService {
316316
content_type: item
317317
.content_type()
318318
.map(|content_type| content_type.as_str().to_owned()),
319-
attachment_type: item.attachment_type().unwrap_or_default(),
319+
attachment_type: item.attachment_type().cloned().unwrap_or_default(),
320320
chunks: chunk_index,
321321
size: Some(size),
322322
rate_limited: Some(item.rate_limited()),

relay-server/src/endpoints/common.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ pub fn event_id_from_items(items: &Items) -> Result<Option<EventId>, BadStoreReq
203203

204204
if let Some(item) = items
205205
.iter()
206-
.find(|item| item.attachment_type() == Some(AttachmentType::EventPayload))
206+
.find(|item| item.attachment_type() == Some(&AttachmentType::EventPayload))
207207
{
208208
if let Some(event_id) = event_id_from_msgpack(&item.payload())? {
209209
return Ok(Some(event_id));

relay-server/src/endpoints/minidump.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ fn extract_envelope(
126126
.and_then(move |mut items| {
127127
let minidump_index = items
128128
.iter()
129-
.position(|item| item.attachment_type() == Some(AttachmentType::Minidump));
129+
.position(|item| item.attachment_type() == Some(&AttachmentType::Minidump));
130130

131131
let mut minidump_item = match minidump_index {
132132
Some(index) => items.swap_remove(index),

relay-server/src/envelope.rs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -321,24 +321,20 @@ relay_common::impl_str_de!(ContentType, "a content type string");
321321
/// The type of an event attachment.
322322
///
323323
/// These item types must align with the Sentry processing pipeline.
324-
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)]
324+
#[derive(Clone, Debug, Eq, PartialEq)]
325325
pub enum AttachmentType {
326326
/// A regular attachment without special meaning.
327-
#[serde(rename = "event.attachment")]
328327
Attachment,
329328

330329
/// A minidump crash report (binary data).
331-
#[serde(rename = "event.minidump")]
332330
Minidump,
333331

334332
/// An apple crash report (text data).
335-
#[serde(rename = "event.applecrashreport")]
336333
AppleCrashReport,
337334

338335
/// A msgpack-encoded event payload submitted as part of multipart uploads.
339336
///
340337
/// This attachment is processed by Relay immediately and never forwarded or persisted.
341-
#[serde(rename = "event.payload")]
342338
EventPayload,
343339

344340
/// A msgpack-encoded list of payloads.
@@ -347,7 +343,6 @@ pub enum AttachmentType {
347343
/// will be merged and truncated to the maxmimum number of allowed attachments.
348344
///
349345
/// This attachment is processed by Relay immediately and never forwarded or persisted.
350-
#[serde(rename = "event.breadcrumbs")]
351346
Breadcrumbs,
352347

353348
/// This is a binary attachment present in Unreal 4 events containing event context information.
@@ -356,7 +351,6 @@ pub enum AttachmentType {
356351
/// [`symbolic_unreal::Unreal4Context`].
357352
///
358353
/// [`symbolic_unreal::Unreal4Context`]: https://docs.rs/symbolic/*/symbolic/unreal/struct.Unreal4Context.html
359-
#[serde(rename = "unreal.context")]
360354
UnrealContext,
361355

362356
/// This is a binary attachment present in Unreal 4 events containing event Logs.
@@ -365,8 +359,11 @@ pub enum AttachmentType {
365359
/// [`symbolic_unreal::Unreal4LogEntry`].
366360
///
367361
/// [`symbolic_unreal::Unreal4LogEntry`]: https://docs.rs/symbolic/*/symbolic/unreal/struct.Unreal4LogEntry.html
368-
#[serde(rename = "unreal.logs")]
369362
UnrealLogs,
363+
364+
/// Unknown attachment type, forwarded for compatibility.
365+
/// Attachments with this type will be dropped if `accept_unknown_items` is set to false.
366+
Unknown(String),
370367
}
371368

372369
impl Default for AttachmentType {
@@ -375,6 +372,43 @@ impl Default for AttachmentType {
375372
}
376373
}
377374

375+
impl fmt::Display for AttachmentType {
376+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377+
match self {
378+
AttachmentType::Attachment => write!(f, "event.attachment"),
379+
AttachmentType::Minidump => write!(f, "event.minidump"),
380+
AttachmentType::AppleCrashReport => write!(f, "event.applecrashreport"),
381+
AttachmentType::EventPayload => write!(f, "event.payload"),
382+
AttachmentType::Breadcrumbs => write!(f, "event.breadcrumbs"),
383+
AttachmentType::UnrealContext => write!(f, "unreal.context"),
384+
AttachmentType::UnrealLogs => write!(f, "unreal.logs"),
385+
AttachmentType::Unknown(s) => s.fmt(f),
386+
}
387+
}
388+
}
389+
390+
impl std::str::FromStr for AttachmentType {
391+
type Err = std::convert::Infallible;
392+
393+
fn from_str(s: &str) -> Result<Self, Self::Err> {
394+
Ok(match s {
395+
"event.attachment" => AttachmentType::Attachment,
396+
"event.minidump" => AttachmentType::Minidump,
397+
"event.applecrashreport" => AttachmentType::AppleCrashReport,
398+
"event.payload" => AttachmentType::EventPayload,
399+
"event.breadcrumbs" => AttachmentType::Breadcrumbs,
400+
"unreal.context" => AttachmentType::UnrealContext,
401+
"unreal.logs" => AttachmentType::UnrealLogs,
402+
other => AttachmentType::Unknown(other.to_owned()),
403+
})
404+
}
405+
}
406+
407+
relay_common::impl_str_serde!(
408+
AttachmentType,
409+
"an attachment type (see sentry develop docs)"
410+
);
411+
378412
fn is_false(val: &bool) -> bool {
379413
!*val
380414
}
@@ -491,9 +525,9 @@ impl Item {
491525
}
492526

493527
/// Returns the attachment type if this item is an attachment.
494-
pub fn attachment_type(&self) -> Option<AttachmentType> {
528+
pub fn attachment_type(&self) -> Option<&AttachmentType> {
495529
// TODO: consider to replace this with an ItemType?
496-
self.headers.attachment_type
530+
self.headers.attachment_type.as_ref()
497531
}
498532

499533
/// Sets the attachment type of this item.
@@ -607,15 +641,21 @@ impl Item {
607641

608642
// Attachments are only event items if they are crash reports or if they carry partial
609643
// event payloads. Plain attachments never create event payloads.
610-
ItemType::Attachment => match self.attachment_type().unwrap_or_default() {
611-
AttachmentType::AppleCrashReport
612-
| AttachmentType::Minidump
613-
| AttachmentType::EventPayload
614-
| AttachmentType::Breadcrumbs => true,
615-
AttachmentType::Attachment
616-
| AttachmentType::UnrealContext
617-
| AttachmentType::UnrealLogs => false,
618-
},
644+
ItemType::Attachment => {
645+
match self.attachment_type().unwrap_or(&AttachmentType::default()) {
646+
AttachmentType::AppleCrashReport
647+
| AttachmentType::Minidump
648+
| AttachmentType::EventPayload
649+
| AttachmentType::Breadcrumbs => true,
650+
AttachmentType::Attachment
651+
| AttachmentType::UnrealContext
652+
| AttachmentType::UnrealLogs => false,
653+
// When an outdated Relay instance forwards an unknown attachment type for compatibility,
654+
// we assume that the attachment does not create a new event. This will make it hard
655+
// to introduce new attachment types which _do_ create a new event.
656+
AttachmentType::Unknown(_) => false,
657+
}
658+
}
619659

620660
// Form data items may contain partial event payloads, but those are only ever valid if
621661
// they occur together with an explicit event item, such as a minidump or apple crash

relay-server/src/utils/sizes.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use relay_config::Config;
22

3-
use crate::envelope::{Envelope, ItemType};
3+
use crate::envelope::{AttachmentType, Envelope, ItemType};
44

55
/// Checks for size limits of items in this envelope.
66
///
@@ -67,7 +67,13 @@ pub fn remove_unknown_items(config: &Config, envelope: &mut Envelope) {
6767
relay_log::debug!("dropping unknown item of type '{}'", ty);
6868
false
6969
}
70-
_ => true,
70+
_ => match item.attachment_type() {
71+
Some(AttachmentType::Unknown(ty)) => {
72+
relay_log::debug!("dropping unknown attachment of type '{}'", ty);
73+
false
74+
}
75+
_ => true,
76+
},
7177
});
7278
}
7379
}

relay-server/src/utils/unreal.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,9 @@ pub fn process_unreal_envelope(
272272
.get_header(UNREAL_USER_HEADER)
273273
.and_then(Value::as_str);
274274
let context_item =
275-
envelope.get_item_by(|item| item.attachment_type() == Some(AttachmentType::UnrealContext));
275+
envelope.get_item_by(|item| item.attachment_type() == Some(&AttachmentType::UnrealContext));
276276
let logs_item =
277-
envelope.get_item_by(|item| item.attachment_type() == Some(AttachmentType::UnrealLogs));
277+
envelope.get_item_by(|item| item.attachment_type() == Some(&AttachmentType::UnrealLogs));
278278

279279
// Early exit if there is no information.
280280
if user_header.is_none() && context_item.is_none() && logs_item.is_none() {

tests/integration/test_envelope.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,23 @@ def test_unknown_item(mini_sentry, relay):
5656
envelope.add_item(
5757
Item(payload=PayloadRef(bytes=b"something"), type="invalid_unknown")
5858
)
59+
attachment = Item(payload=PayloadRef(bytes=b"something"), type="attachment")
60+
attachment.headers["attachment_type"] = "attachment_unknown"
61+
envelope.add_item(attachment)
5962
relay.send_envelope(PROJECT_ID, envelope)
6063

61-
envelope = mini_sentry.captured_events.get(timeout=1)
62-
assert len(envelope.items) == 1
63-
assert envelope.items[0].type == "invalid_unknown"
64+
envelopes = [ # non-event items are split into separate envelopes, so fetch 2x here
65+
mini_sentry.captured_events.get(timeout=1),
66+
mini_sentry.captured_events.get(timeout=1),
67+
]
68+
69+
types = {
70+
(item.type, item.headers.get("attachment_type"))
71+
for envelope in envelopes
72+
for item in envelope.items
73+
}
74+
75+
assert types == {("invalid_unknown", None), ("attachment", "attachment_unknown")}
6476

6577

6678
def test_drop_unknown_item(mini_sentry, relay):
@@ -69,12 +81,19 @@ def test_drop_unknown_item(mini_sentry, relay):
6981
mini_sentry.add_basic_project_config(PROJECT_ID)
7082

7183
envelope = Envelope()
84+
envelope.add_item(Item(payload=PayloadRef(bytes=b"something"), type="attachment"))
7285
envelope.add_item(
7386
Item(payload=PayloadRef(bytes=b"something"), type="invalid_unknown")
7487
)
88+
attachment = Item(payload=PayloadRef(bytes=b"something"), type="attachment")
89+
attachment.headers["attachment_type"] = "attachment_unknown"
90+
envelope.add_item(attachment)
7591
relay.send_envelope(PROJECT_ID, envelope)
7692

77-
# there is nothing sent to the upstream
93+
envelope = mini_sentry.captured_events.get(timeout=1)
94+
assert len(envelope.items) == 1
95+
assert envelope.items[0].type == "attachment"
96+
7897
with pytest.raises(queue.Empty):
7998
mini_sentry.captured_events.get(timeout=1)
8099

0 commit comments

Comments
 (0)