From e538e778f50b12bda5ef4a1023a569194371d90d Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 24 May 2022 18:22:21 +0100 Subject: [PATCH 1/3] Add attachment support --- sentry-core/src/client.rs | 7 +++++++ sentry-core/src/scope/real.rs | 13 ++++++++++++- sentry-types/src/protocol/attachment.rs | 10 ++++++++-- sentry-types/src/protocol/envelope.rs | 6 ++++++ 4 files changed, 33 insertions(+), 3 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..0dd60f0b 100644 --- a/sentry-types/src/protocol/attachment.rs +++ b/sentry-types/src/protocol/attachment.rs @@ -44,6 +44,8 @@ pub struct Attachment { 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)?; diff --git a/sentry-types/src/protocol/envelope.rs b/sentry-types/src/protocol/envelope.rs index 60ca78b7..75d2fbea 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> { From 6f52c6e036e965c264bb23cfb865d618c6b6a34c Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 25 May 2022 12:32:21 +0100 Subject: [PATCH 2/3] Add tests and a couple of other minor fixes --- sentry-types/src/protocol/attachment.rs | 3 +- sentry-types/src/protocol/envelope.rs | 28 +++++++++++++ sentry/tests/test_basic.rs | 53 +++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/sentry-types/src/protocol/attachment.rs b/sentry-types/src/protocol/attachment.rs index 0dd60f0b..f00b5b05 100644 --- a/sentry-types/src/protocol/attachment.rs +++ b/sentry-types/src/protocol/attachment.rs @@ -37,7 +37,7 @@ impl AttachmentType { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Default)] /// Represents an attachment item. pub struct Attachment { /// The actual attachment data. @@ -80,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 75d2fbea..7c07c5ea 100644 --- a/sentry-types/src/protocol/envelope.rs +++ b/sentry-types/src/protocol/envelope.rs @@ -358,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..a79ec1a9 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,55 @@ 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() { + struct TestTransport(Arc); + + impl sentry::Transport for TestTransport { + fn send_envelope(&self, envelope: sentry::Envelope) { + for item in envelope.items() { + if let EnvelopeItem::Attachment(attachment) = item { + assert_eq!(attachment.filename, "test-file.txt"); + assert_eq!(attachment.buffer, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); + self.0.fetch_add(1, Ordering::SeqCst); + } + } + } + } + + let events = Arc::new(AtomicUsize::new(0)); + + let events_for_options = events.clone(); + let options = sentry::ClientOptions { + dsn: "http://foo@example.com/42".parse().ok(), + transport: Some(Arc::new( + move |opts: &sentry::ClientOptions| -> Arc { + assert_eq!(opts.dsn.as_ref().unwrap().host(), "example.com"); + Arc::new(TestTransport(events_for_options.clone())) + }, + )), + ..Default::default() + }; + + sentry::Hub::run( + Arc::new(sentry::Hub::new( + Some(Arc::new(options.into())), + Arc::new(Default::default()), + )), + || { + sentry::with_scope( + |scope| { + scope.add_attachment(Attachment { + buffer: vec![1, 2, 3, 4, 5, 6, 7, 8, 9], + filename: "test-file.txt".to_string(), + ..Default::default() + }) + }, + || sentry::capture_message("test", sentry::Level::Error), + ) + }, + ); + + assert_eq!(events.load(Ordering::SeqCst), 1); +} From 1363638d8e363e8ef7b28cdc6805ca80e74c196c Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 25 May 2022 13:50:54 +0100 Subject: [PATCH 3/3] simplify test with `with_captured_envelopes` --- sentry/tests/test_basic.rs | 65 ++++++++++++-------------------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/sentry/tests/test_basic.rs b/sentry/tests/test_basic.rs index a79ec1a9..6d239bf3 100644 --- a/sentry/tests/test_basic.rs +++ b/sentry/tests/test_basic.rs @@ -177,52 +177,27 @@ fn test_attached_stacktrace() { #[test] fn test_attachment_sent_from_scope() { - struct TestTransport(Arc); - - impl sentry::Transport for TestTransport { - fn send_envelope(&self, envelope: sentry::Envelope) { - for item in envelope.items() { - if let EnvelopeItem::Attachment(attachment) = item { - assert_eq!(attachment.filename, "test-file.txt"); - assert_eq!(attachment.buffer, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); - self.0.fetch_add(1, Ordering::SeqCst); - } - } - } - } - - let events = Arc::new(AtomicUsize::new(0)); - - let events_for_options = events.clone(); - let options = sentry::ClientOptions { - dsn: "http://foo@example.com/42".parse().ok(), - transport: Some(Arc::new( - move |opts: &sentry::ClientOptions| -> Arc { - assert_eq!(opts.dsn.as_ref().unwrap().host(), "example.com"); - Arc::new(TestTransport(events_for_options.clone())) + 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() + }) }, - )), - ..Default::default() - }; + || sentry::capture_message("test", sentry::Level::Error), + ); + }); - sentry::Hub::run( - Arc::new(sentry::Hub::new( - Some(Arc::new(options.into())), - Arc::new(Default::default()), - )), - || { - sentry::with_scope( - |scope| { - scope.add_attachment(Attachment { - buffer: vec![1, 2, 3, 4, 5, 6, 7, 8, 9], - filename: "test-file.txt".to_string(), - ..Default::default() - }) - }, - || sentry::capture_message("test", sentry::Level::Error), - ) - }, - ); + assert_eq!(envelopes.len(), 1); - assert_eq!(events.load(Ordering::SeqCst), 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] + )); }