From 995a0a86f537b2fe6919e8545f6b1729e6705aea Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 25 May 2022 15:18:34 +0100 Subject: [PATCH] feat: Basic attachment support (#466) --- sentry-core/src/client.rs | 7 +++++ sentry-core/src/scope/real.rs | 13 +++++++++- sentry-types/src/protocol/attachment.rs | 13 +++++++--- sentry-types/src/protocol/envelope.rs | 34 +++++++++++++++++++++++++ sentry/tests/test_basic.rs | 28 ++++++++++++++++++++ 5 files changed, 91 insertions(+), 4 deletions(-) diff --git a/sentry-core/src/client.rs b/sentry-core/src/client.rs index 96343684..a04851b7 100644 --- a/sentry-core/src/client.rs +++ b/sentry-core/src/client.rs @@ -278,6 +278,13 @@ impl Client { envelope.add_item(session_item); } } + + if let Some(scope) = scope { + for attachment in scope.attachments.iter().cloned() { + envelope.add_item(attachment); + } + } + transport.send_envelope(envelope); return event_id; } diff --git a/sentry-core/src/scope/real.rs b/sentry-core/src/scope/real.rs index fbdc6df8..e29651b0 100644 --- a/sentry-core/src/scope/real.rs +++ b/sentry-core/src/scope/real.rs @@ -4,7 +4,7 @@ use std::fmt; use std::sync::{Arc, Mutex, PoisonError, RwLock}; use crate::performance::TransactionOrSpan; -use crate::protocol::{Breadcrumb, Context, Event, Level, User, Value}; +use crate::protocol::{Attachment, Breadcrumb, Context, Event, Level, User, Value}; use crate::session::Session; use crate::Client; @@ -46,6 +46,7 @@ pub struct Scope { pub(crate) event_processors: Arc>, pub(crate) session: Arc>>, pub(crate) span: Arc>, + pub(crate) attachments: Arc>, } impl fmt::Debug for Scope { @@ -218,6 +219,16 @@ impl Scope { Arc::make_mut(&mut self.event_processors).push(Arc::new(f)); } + /// Adds an attachment to the scope + pub fn add_attachment(&mut self, attachment: Attachment) { + Arc::make_mut(&mut self.attachments).push(attachment); + } + + /// Clears attachments from the scope + pub fn clear_attachments(&mut self) { + Arc::make_mut(&mut self.attachments).clear(); + } + /// Applies the contained scoped data to fill an event. pub fn apply_to_event(&self, mut event: Event<'static>) -> Option> { // TODO: event really should have an optional level diff --git a/sentry-types/src/protocol/attachment.rs b/sentry-types/src/protocol/attachment.rs index 7fe067c1..f00b5b05 100644 --- a/sentry-types/src/protocol/attachment.rs +++ b/sentry-types/src/protocol/attachment.rs @@ -37,13 +37,15 @@ impl AttachmentType { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Default)] /// Represents an attachment item. pub struct Attachment { /// The actual attachment data. pub buffer: Vec, /// The filename of the attachment. pub filename: String, + /// The Content Type of the attachment + pub content_type: Option, /// The special type of this attachment. pub ty: Option, } @@ -56,10 +58,14 @@ impl Attachment { { writeln!( writer, - r#"{{"type":"attachment","length":{length},"filename":"{filename}","attachment_type":"{at}"}}"#, + r#"{{"type":"attachment","length":{length},"filename":"{filename}","attachment_type":"{at}","content_type":"{ct}"}}"#, filename = self.filename, length = self.buffer.len(), - at = self.ty.unwrap_or_default().as_str() + at = self.ty.unwrap_or_default().as_str(), + ct = self + .content_type + .as_ref() + .unwrap_or(&"application/octet-stream".to_string()) )?; writer.write_all(&self.buffer)?; @@ -74,6 +80,7 @@ impl fmt::Debug for Attachment { f.debug_struct("Attachment") .field("buffer", &self.buffer.len()) .field("filename", &self.filename) + .field("content_type", &self.content_type) .field("type", &self.ty) .finish() } diff --git a/sentry-types/src/protocol/envelope.rs b/sentry-types/src/protocol/envelope.rs index 60ca78b7..7c07c5ea 100644 --- a/sentry-types/src/protocol/envelope.rs +++ b/sentry-types/src/protocol/envelope.rs @@ -65,6 +65,12 @@ impl From> for EnvelopeItem { } } +impl From for EnvelopeItem { + fn from(attachment: Attachment) -> Self { + EnvelopeItem::Attachment(attachment) + } +} + /// An Iterator over the items of an Envelope. #[derive(Clone)] pub struct EnvelopeItemIter<'s> { @@ -352,6 +358,34 @@ mod test { r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"} {"type":"transaction","length":200} {"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5c","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]} +"# + ) + } + + #[test] + fn test_event_with_attachment() { + let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap(); + let timestamp = timestamp("2020-07-20T14:51:14.296Z"); + let event = Event { + event_id, + timestamp, + ..Default::default() + }; + let mut envelope: Envelope = event.into(); + + envelope.add_item(Attachment { + buffer: "some content".as_bytes().to_vec(), + filename: "file.txt".to_string(), + ..Default::default() + }); + + assert_eq!( + to_str(envelope), + r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"} +{"type":"event","length":74} +{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296} +{"type":"attachment","length":12,"filename":"file.txt","attachment_type":"event.attachment","content_type":"application/octet-stream"} +some content "# ) } diff --git a/sentry/tests/test_basic.rs b/sentry/tests/test_basic.rs index ae9a64de..6d239bf3 100644 --- a/sentry/tests/test_basic.rs +++ b/sentry/tests/test_basic.rs @@ -3,6 +3,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use sentry::protocol::{Attachment, EnvelopeItem}; use sentry::types::Uuid; #[test] @@ -173,3 +174,30 @@ fn test_attached_stacktrace() { .flat_map(|ev| ev.threads.into_iter().filter_map(|thrd| thrd.stacktrace)); assert_eq!(stacktraces.count(), 3); } + +#[test] +fn test_attachment_sent_from_scope() { + let envelopes = sentry::test::with_captured_envelopes(|| { + sentry::with_scope( + |scope| { + scope.add_attachment(Attachment { + buffer: vec![1, 2, 3, 4, 5, 6, 7, 8, 9], + filename: "test-file.bin".to_string(), + ..Default::default() + }) + }, + || sentry::capture_message("test", sentry::Level::Error), + ); + }); + + assert_eq!(envelopes.len(), 1); + + let items = envelopes[0].items().collect::>(); + + assert_eq!(items.len(), 2); + assert!(matches!(items[1], + EnvelopeItem::Attachment(attachment) + if attachment.filename == *"test-file.bin" + && attachment.buffer == vec![1, 2, 3, 4, 5, 6, 7, 8, 9] + )); +}