-
Notifications
You must be signed in to change notification settings - Fork 115
fix(multipart): Use correct discard reasons #5950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c055532
d4025f1
1a20b80
aa1370c
6f358f8
eeb45a7
6751950
0176978
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,13 +17,12 @@ use serde::Deserialize; | |
|
|
||
| use crate::envelope::{ | ||
| AttachmentPlaceholder, AttachmentType, ContentType, Envelope, EnvelopeError, Item, ItemType, | ||
| ManagedItems, | ||
| Items, | ||
| }; | ||
| use crate::extractors::RequestMeta; | ||
| use crate::managed::{Managed, Rejected}; | ||
| use crate::service::ServiceState; | ||
| use crate::services::buffer::{ProjectKeyPair, PushError}; | ||
| use crate::services::outcome::{DiscardItemType, DiscardReason, Outcome, TrackOutcome}; | ||
| use crate::services::outcome::{DiscardItemType, DiscardReason, Outcome}; | ||
| use crate::services::processor::{BucketSource, MetricData, ProcessMetrics}; | ||
| use crate::services::upload::{Create, Stream, Upload}; | ||
| use crate::statsd::{RelayCounters, RelayDistributions}; | ||
|
|
@@ -125,6 +124,29 @@ pub enum BadStoreRequest { | |
| ObjectstoreUploadFailed, | ||
| } | ||
|
|
||
| impl BadStoreRequest { | ||
| pub fn to_outcome(&self) -> Option<Outcome> { | ||
| let discard_reason = match self { | ||
| Self::InvalidCompressionContainer(_) => DiscardReason::InvalidCompression, | ||
| Self::InvalidMinidump => DiscardReason::InvalidMinidump, | ||
| #[cfg(sentry)] | ||
| Self::InvalidProsperodump => DiscardReason::InvalidProsperodump, | ||
| Self::MissingMinidump => DiscardReason::MissingMinidumpUpload, | ||
| #[cfg(sentry)] | ||
| Self::MissingProsperodump => DiscardReason::MissingProsperodumpUpload, | ||
| Self::Overflow(item_type) => DiscardReason::TooLarge(*item_type), | ||
| _ => DiscardReason::Internal, | ||
| }; | ||
| Some(Outcome::Invalid(discard_reason)) | ||
| } | ||
| } | ||
|
elramen marked this conversation as resolved.
elramen marked this conversation as resolved.
Comment on lines
+127
to
+142
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker, but converting BadStoreRequest to a DiscardReason seems backward to me. It would be better to make the functions that currently return BadStoreRequest return a DiscardReason instead, and make that to a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, and maybe we don't need BadStoreRequest at all. But changing all the functions that return BadStoreRequest seems out of scope for this PR.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally we have proper scoped errors for these cases. Also not really happy about: impl From<Rejected<BadStoreRequest>> for BadStoreRequest {
fn from(rejected: Rejected<BadStoreRequest>) -> Self {
rejected.into_inner()
}
}This also seems like a workaround rather than a solution and if it was explicit That being said, this shouldn't block an overall improvement. |
||
|
|
||
| impl From<Rejected<BadStoreRequest>> for BadStoreRequest { | ||
| fn from(rejected: Rejected<BadStoreRequest>) -> Self { | ||
| rejected.into_inner() | ||
| } | ||
| } | ||
|
|
||
| impl From<BytesRejection> for BadStoreRequest { | ||
| fn from(value: BytesRejection) -> Self { | ||
| match value { | ||
|
|
@@ -292,7 +314,7 @@ pub fn event_id_from_formdata(data: &[u8]) -> Result<Option<EventId>, BadStoreRe | |
| /// | ||
| /// Extracting the event id from chunked formdata fields on the Minidump endpoint (`sentry__1`, | ||
| /// `sentry__2`, ...) is not supported. In this case, `None` is returned. | ||
| pub fn event_id_from_items(items: &ManagedItems) -> Result<Option<EventId>, BadStoreRequest> { | ||
| pub fn event_id_from_items(items: &Items) -> Result<Option<EventId>, BadStoreRequest> { | ||
| if let Some(item) = items.iter().find(|item| item.ty() == &ItemType::Event) | ||
| && let Some(event_id) = event_id_from_json(&item.payload())? | ||
| { | ||
|
|
@@ -575,27 +597,6 @@ where | |
| Some(item) | ||
| } | ||
|
|
||
| pub fn managed_items_to_envelope( | ||
| items: ManagedItems, | ||
| meta: RequestMeta, | ||
| outcome_aggregator: &Addr<TrackOutcome>, | ||
| event_id: EventId, | ||
| ) -> Managed<Box<Envelope>> { | ||
| let envelope = Envelope::from_request(Some(event_id), meta); | ||
| let mut envelope = Managed::from_envelope(envelope, outcome_aggregator.clone()); | ||
| let mut has_event = false; | ||
| for item in items { | ||
| envelope.merge_with(item, |envelope, item, records| { | ||
| if !has_event && item.creates_event() { | ||
| records.modify_by(DataCategory::Error, 1); | ||
| has_event = true; | ||
| } | ||
| envelope.add_item(item); | ||
| }); | ||
| } | ||
| envelope | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub struct TextResponse(pub Option<EventId>); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -24,7 +24,7 @@ use crate::endpoints::common::{self, BadStoreRequest, TextResponse, upload_to_ob | |||||
| use crate::envelope::ContentType::Minidump; | ||||||
| use crate::envelope::{AttachmentType, Envelope, Item, ItemType}; | ||||||
| use crate::extractors::{RawContentType, RequestMeta}; | ||||||
| use crate::managed::Managed; | ||||||
| use crate::managed::{Managed, ManagedResult}; | ||||||
| use crate::middlewares; | ||||||
| use crate::service::ServiceState; | ||||||
| use crate::services::outcome::{DiscardAttachmentType, DiscardItemType, DiscardReason, Outcome}; | ||||||
|
|
@@ -123,9 +123,8 @@ fn decode_minidump(minidump_data: Bytes, max_size: usize) -> Result<Bytes, BadSt | |||||
| match run_decoder(decoder) { | ||||||
| Ok(decoded) => { | ||||||
| if decoded.len() > max_size { | ||||||
| return Err(BadStoreRequest::Overflow(DiscardItemType::Attachment( | ||||||
| DiscardAttachmentType::Minidump, | ||||||
| ))); | ||||||
| let item_type = DiscardItemType::Attachment(DiscardAttachmentType::Minidump); | ||||||
| return Err(BadStoreRequest::Overflow(item_type)); | ||||||
|
elramen marked this conversation as resolved.
|
||||||
| } | ||||||
| Ok(Bytes::from(decoded)) | ||||||
| } | ||||||
|
|
@@ -284,36 +283,37 @@ async fn multipart_to_envelope( | |||||
| ) | ||||||
| .await?; | ||||||
|
|
||||||
| let minidump_item = items | ||||||
| .iter_mut() | ||||||
| .find(|item| item.attachment_type() == Some(AttachmentType::Minidump)) | ||||||
| .ok_or(BadStoreRequest::MissingMinidump)?; | ||||||
| let minidump_idx = items | ||||||
| .iter() | ||||||
| .position(|item| item.attachment_type() == Some(AttachmentType::Minidump)) | ||||||
| .ok_or(BadStoreRequest::MissingMinidump) | ||||||
| .reject(&items)?; | ||||||
|
|
||||||
| // Doing these operations does not make sense if we already streamed the minidump to objectstore. | ||||||
| if !minidump_item.is_attachment_ref() { | ||||||
| let payload = minidump_item.payload(); | ||||||
| if !items[minidump_idx].is_attachment_ref() { | ||||||
|
elramen marked this conversation as resolved.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit nitpicky, but we do have a guideline to avoid slice syntax as it introduces a place where things can panic, see: https://develop.sentry.dev/engineering-practices/rust/#use-get-instead-of-slice-syntax This policy while at times may seem useless, it did serve us well so far. Something like this should do:
Suggested change
Or some variant of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will fix in another PR |
||||||
| let payload = items[minidump_idx].payload(); | ||||||
| let payload = extract_embedded_minidump(payload.clone()) | ||||||
| .await? | ||||||
| .unwrap_or(payload); | ||||||
| let payload = decode_minidump(payload, config.max_attachment_size())?; | ||||||
| let payload = decode_minidump(payload, config.max_attachment_size()).reject(&items)?; | ||||||
|
|
||||||
| minidump_item.modify(|inner, records| { | ||||||
| inner.set_payload(Minidump, payload); | ||||||
| records.lenient(DataCategory::Attachment); | ||||||
| }); | ||||||
|
|
||||||
| validate_minidump(&minidump_item.payload())?; | ||||||
|
|
||||||
| minidump_item.modify(|inner, _| { | ||||||
| if let Some(minidump_filename) = inner.filename() { | ||||||
| inner.set_filename(remove_container_extension(minidump_filename).to_owned()) | ||||||
| items.try_modify(|items, records| -> Result<(), BadStoreRequest> { | ||||||
| let minidump_item = &mut items[minidump_idx]; | ||||||
| minidump_item.set_payload(Minidump, payload); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not related to this PR, but I noticed it: we always qualify enum variants:
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused, is qualifying something we should or shouldn't do?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our convention is to always use the qualified path, that is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will fix in another PR |
||||||
| records.lenient(DataCategory::Attachment); // decoding the minidump changes its size | ||||||
| if let Some(minidump_filename) = minidump_item.filename() { | ||||||
| minidump_item.set_filename(remove_container_extension(minidump_filename).to_owned()) | ||||||
| } | ||||||
| }); | ||||||
| validate_minidump(&minidump_item.payload())?; | ||||||
| Ok(()) | ||||||
| })?; | ||||||
| } | ||||||
|
|
||||||
| let event_id = common::event_id_from_items(&items)?.unwrap_or_else(EventId::new); | ||||||
| let envelope = | ||||||
| common::managed_items_to_envelope(items, meta, state.outcome_aggregator(), event_id); | ||||||
| let envelope = items.map(|items, records| { | ||||||
| records.modify_by(DataCategory::Error, 1); | ||||||
| Box::new(Envelope::from_request(Some(event_id), meta).with_items(items)) | ||||||
| }); | ||||||
| Ok(envelope) | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -400,7 +400,6 @@ async fn raw_minidump_to_envelope( | |||||
| item.set_filename(MINIDUMP_FILE_NAME); | ||||||
| item.set_attachment_type(AttachmentType::Minidump); | ||||||
| let mut item = Managed::with_meta_from_request_meta(&meta, state.outcome_aggregator(), item); | ||||||
|
|
||||||
| if let Some(upload_context) = upload_context | ||||||
| && matches!(upload_context.upload_minidumps, UploadDecision::Upload) | ||||||
| { | ||||||
|
|
@@ -416,23 +415,20 @@ async fn raw_minidump_to_envelope( | |||||
| .await | ||||||
| .ok_or(BadStoreRequest::ObjectstoreUploadFailed)?; | ||||||
| } else { | ||||||
| let decoded_payload = decode_minidump( | ||||||
| request.extract().await?, | ||||||
| state.config().max_attachment_size(), | ||||||
| )?; | ||||||
| item.modify(|inner, records| { | ||||||
| inner.set_payload(Minidump, decoded_payload); | ||||||
| let minidump_data = request.extract().await?; | ||||||
| item.try_modify(|inner, records| -> Result<(), BadStoreRequest> { | ||||||
| let payload = decode_minidump(minidump_data, state.config().max_attachment_size())?; | ||||||
| inner.set_payload(Minidump, payload); | ||||||
| records.lenient(DataCategory::Attachment); // decoding the minidump changes its size | ||||||
| }); | ||||||
| validate_minidump(&item.payload())?; | ||||||
|
elramen marked this conversation as resolved.
|
||||||
| validate_minidump(&inner.payload())?; | ||||||
| Ok(()) | ||||||
| })?; | ||||||
| }; | ||||||
|
|
||||||
| // Create an envelope with a random event id. | ||||||
| let envelope = Envelope::from_request(Some(EventId::new()), meta); | ||||||
| let mut envelope = Managed::from_envelope(envelope, state.outcome_aggregator().clone()); | ||||||
| envelope.merge_with(item, |envelope, item, records| { | ||||||
| let envelope = item.map(|item, records| { | ||||||
| records.modify_by(DataCategory::Error, 1); | ||||||
| envelope.add_item(item); | ||||||
| Box::new(Envelope::from_request(Some(EventId::new()), meta).with_items(vec![item])) | ||||||
| }); | ||||||
| Ok(envelope) | ||||||
| } | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Steered away from a common envelope-creation function because of the complexity of keeping records correct in all cases. Opened #5949 to address this