@@ -686,17 +686,19 @@ impl StoreService {
686686 ) -> Result < Option < ChunkedAttachment > , StoreError > {
687687 let id = Uuid :: new_v4 ( ) . to_string ( ) ;
688688
689- let mut chunk_index = 0 ;
690689 let payload = item. payload ( ) ;
691690 let size = item. len ( ) ;
692691 let max_chunk_size = self . config . attachment_chunk_size ( ) ;
693692
694693 // When sending individual attachments, and we have a single chunk, we want to send the
695694 // `data` inline in the `attachment` message.
696695 // This avoids a needless roundtrip through the attachments cache on the Sentry side.
697- let data = if send_individual_attachments && size < max_chunk_size {
698- ( size > 0 ) . then_some ( payload)
696+ let payload = if size == 0 {
697+ AttachmentPayload :: Chunked ( 0 )
698+ } else if send_individual_attachments && size < max_chunk_size {
699+ AttachmentPayload :: Inline ( payload)
699700 } else {
701+ let mut chunk_index = 0 ;
700702 let mut offset = 0 ;
701703 // This skips chunks for empty attachments. The consumer does not require chunks for
702704 // empty attachments. `chunks` will be `0` in this case.
@@ -717,26 +719,25 @@ impl StoreService {
717719 offset += chunk_size;
718720 chunk_index += 1 ;
719721 }
720- None
721- } ;
722722
723- // The chunk_index is incremented after every loop iteration. After we exit the loop, it
724- // is one larger than the last chunk, so it is equal to the number of chunks.
723+ // The chunk_index is incremented after every loop iteration. After we exit the loop, it
724+ // is one larger than the last chunk, so it is equal to the number of chunks.
725+ AttachmentPayload :: Chunked ( chunk_index)
726+ } ;
725727
726728 let attachment = ChunkedAttachment {
727729 id,
728730 name : match item. filename ( ) {
729731 Some ( name) => name. to_owned ( ) ,
730732 None => UNNAMED_ATTACHMENT . to_owned ( ) ,
731733 } ,
734+ rate_limited : item. rate_limited ( ) ,
732735 content_type : item
733736 . content_type ( )
734737 . map ( |content_type| content_type. as_str ( ) . to_owned ( ) ) ,
735738 attachment_type : item. attachment_type ( ) . cloned ( ) . unwrap_or_default ( ) ,
736- chunks : chunk_index,
737- data,
738- size : Some ( size) ,
739- rate_limited : Some ( item. rate_limited ( ) ) ,
739+ size,
740+ payload,
740741 } ;
741742
742743 if send_individual_attachments {
@@ -1341,6 +1342,26 @@ impl Service for StoreService {
13411342 }
13421343}
13431344
1345+ /// This signifies how the attachment payload is being transfered.
1346+ #[ derive( Debug , Serialize ) ]
1347+ enum AttachmentPayload {
1348+ /// The payload has been split into multiple chunks.
1349+ ///
1350+ /// The individual chunks are being sent as separate [`AttachmentChunkKafkaMessage`] messages.
1351+ /// If the payload `size == 0`, the number of chunks will also be `0`.
1352+ #[ serde( rename = "chunks" ) ]
1353+ Chunked ( usize ) ,
1354+
1355+ /// The payload is inlined here directly, and thus into the [`ChunkedAttachment`].
1356+ #[ serde( rename = "data" ) ]
1357+ Inline ( Bytes ) ,
1358+
1359+ /// The attachment has already been stored into the objectstore, with the given Id.
1360+ #[ serde( rename = "stored_id" ) ]
1361+ #[ allow( unused) ] // TODO: actually storing it in objectstore first is still WIP
1362+ Stored ( String ) ,
1363+ }
1364+
13441365/// Common attributes for both standalone attachments and processing-relevant attachments.
13451366#[ derive( Debug , Serialize ) ]
13461367struct ChunkedAttachment {
@@ -1352,6 +1373,14 @@ struct ChunkedAttachment {
13521373 /// File name of the attachment file.
13531374 name : String ,
13541375
1376+ /// Whether this attachment was rate limited and should be removed after processing.
1377+ ///
1378+ /// By default, rate limited attachments are immediately removed from Envelopes. For processing,
1379+ /// native crash reports still need to be retained. These attachments are marked with the
1380+ /// `rate_limited` header, which signals to the processing pipeline that the attachment should
1381+ /// not be persisted after processing.
1382+ rate_limited : bool ,
1383+
13551384 /// Content type of the attachment payload.
13561385 #[ serde( skip_serializing_if = "Option::is_none" ) ]
13571386 content_type : Option < String > ,
@@ -1360,27 +1389,12 @@ struct ChunkedAttachment {
13601389 #[ serde( serialize_with = "serialize_attachment_type" ) ]
13611390 attachment_type : AttachmentType ,
13621391
1363- /// Number of outlined chunks.
1364- /// Zero if the attachment has `size: 0`, or there was only a single chunk which has been inlined into `data`.
1365- chunks : usize ,
1366-
1367- /// The content of the attachment,
1368- /// if they are smaller than the configured `attachment_chunk_size`.
1369- #[ serde( skip_serializing_if = "Option::is_none" ) ]
1370- data : Option < Bytes > ,
1371-
13721392 /// The size of the attachment in bytes.
1373- #[ serde( skip_serializing_if = "Option::is_none" ) ]
1374- size : Option < usize > ,
1393+ size : usize ,
13751394
1376- /// Whether this attachment was rate limited and should be removed after processing.
1377- ///
1378- /// By default, rate limited attachments are immediately removed from Envelopes. For processing,
1379- /// native crash reports still need to be retained. These attachments are marked with the
1380- /// `rate_limited` header, which signals to the processing pipeline that the attachment should
1381- /// not be persisted after processing.
1382- #[ serde( skip_serializing_if = "Option::is_none" ) ]
1383- rate_limited : Option < bool > ,
1395+ /// The attachment payload, chunked, inlined, or already stored.
1396+ #[ serde( flatten) ]
1397+ payload : AttachmentPayload ,
13841398}
13851399
13861400/// A hack to make rmp-serde behave more like serde-json when serializing enums.
@@ -1823,18 +1837,13 @@ struct ProfileChunkKafkaMessage {
18231837#[ allow( clippy:: large_enum_variant) ]
18241838enum KafkaMessage < ' a > {
18251839 Event ( EventKafkaMessage ) ,
1826- Attachment ( AttachmentKafkaMessage ) ,
1827- AttachmentChunk ( AttachmentChunkKafkaMessage ) ,
18281840 UserReport ( UserReportKafkaMessage ) ,
18291841 Metric {
18301842 #[ serde( skip) ]
18311843 headers : BTreeMap < String , String > ,
18321844 #[ serde( flatten) ]
18331845 message : MetricKafkaMessage < ' a > ,
18341846 } ,
1835- Profile ( ProfileKafkaMessage ) ,
1836- ReplayEvent ( ReplayEventKafkaMessage < ' a > ) ,
1837- ReplayRecordingNotChunked ( ReplayRecordingNotChunkedKafkaMessage < ' a > ) ,
18381847 CheckIn ( CheckInKafkaMessage ) ,
18391848 Item {
18401849 #[ serde( skip) ]
@@ -1850,15 +1859,21 @@ enum KafkaMessage<'a> {
18501859 #[ serde( flatten) ]
18511860 message : SpanKafkaMessage < ' a > ,
18521861 } ,
1862+
1863+ Attachment ( AttachmentKafkaMessage ) ,
1864+ AttachmentChunk ( AttachmentChunkKafkaMessage ) ,
1865+
1866+ Profile ( ProfileKafkaMessage ) ,
18531867 ProfileChunk ( ProfileChunkKafkaMessage ) ,
1868+
1869+ ReplayEvent ( ReplayEventKafkaMessage < ' a > ) ,
1870+ ReplayRecordingNotChunked ( ReplayRecordingNotChunkedKafkaMessage < ' a > ) ,
18541871}
18551872
18561873impl Message for KafkaMessage < ' _ > {
18571874 fn variant ( & self ) -> & ' static str {
18581875 match self {
18591876 KafkaMessage :: Event ( _) => "event" ,
1860- KafkaMessage :: Attachment ( _) => "attachment" ,
1861- KafkaMessage :: AttachmentChunk ( _) => "attachment_chunk" ,
18621877 KafkaMessage :: UserReport ( _) => "user_report" ,
18631878 KafkaMessage :: Metric { message, .. } => match message. name . namespace ( ) {
18641879 MetricNamespace :: Sessions => "metric_sessions" ,
@@ -1868,24 +1883,26 @@ impl Message for KafkaMessage<'_> {
18681883 MetricNamespace :: Stats => "metric_metric_stats" ,
18691884 MetricNamespace :: Unsupported => "metric_unsupported" ,
18701885 } ,
1871- KafkaMessage :: Profile ( _) => "profile" ,
1872- KafkaMessage :: ReplayEvent ( _) => "replay_event" ,
1873- KafkaMessage :: ReplayRecordingNotChunked ( _) => "replay_recording_not_chunked" ,
18741886 KafkaMessage :: CheckIn ( _) => "check_in" ,
18751887 KafkaMessage :: Span { .. } => "span" ,
1876- KafkaMessage :: ProfileChunk ( _) => "profile_chunk" ,
18771888 KafkaMessage :: Item { item_type, .. } => item_type. as_str_name ( ) ,
1889+
1890+ KafkaMessage :: Attachment ( _) => "attachment" ,
1891+ KafkaMessage :: AttachmentChunk ( _) => "attachment_chunk" ,
1892+
1893+ KafkaMessage :: Profile ( _) => "profile" ,
1894+ KafkaMessage :: ProfileChunk ( _) => "profile_chunk" ,
1895+
1896+ KafkaMessage :: ReplayEvent ( _) => "replay_event" ,
1897+ KafkaMessage :: ReplayRecordingNotChunked ( _) => "replay_recording_not_chunked" ,
18781898 }
18791899 }
18801900
18811901 /// Returns the partitioning key for this Kafka message determining.
18821902 fn key ( & self ) -> Option < relay_kafka:: Key > {
18831903 match self {
18841904 Self :: Event ( message) => Some ( message. event_id . 0 ) ,
1885- Self :: Attachment ( message) => Some ( message. event_id . 0 ) ,
1886- Self :: AttachmentChunk ( message) => Some ( message. event_id . 0 ) ,
18871905 Self :: UserReport ( message) => Some ( message. event_id . 0 ) ,
1888- Self :: ReplayEvent ( message) => Some ( message. replay_id . 0 ) ,
18891906 Self :: Span { message, .. } => Some ( message. trace_id . 0 ) ,
18901907
18911908 // Monitor check-ins use the hinted UUID passed through from the Envelope.
@@ -1894,6 +1911,10 @@ impl Message for KafkaMessage<'_> {
18941911 // recieve the routing_key_hint form their envelopes.
18951912 Self :: CheckIn ( message) => message. routing_key_hint ,
18961913
1914+ Self :: Attachment ( message) => Some ( message. event_id . 0 ) ,
1915+ Self :: AttachmentChunk ( message) => Some ( message. event_id . 0 ) ,
1916+ Self :: ReplayEvent ( message) => Some ( message. replay_id . 0 ) ,
1917+
18971918 // Random partitioning
18981919 _ => None ,
18991920 }
0 commit comments