Skip to main content

ckb_sentry_types/protocol/
envelope.rs

1use std::io::Write;
2
3use uuid::Uuid;
4
5use super::v7::Event;
6use super::v7::SessionUpdate;
7use super::v7::Transaction;
8
9/// An Envelope Item.
10///
11/// See the [documentation on Items](https://develop.sentry.dev/sdk/envelopes/#items)
12/// for more details.
13#[derive(Clone, Debug, PartialEq)]
14#[non_exhaustive]
15pub enum EnvelopeItem {
16    /// An Event Item.
17    ///
18    /// See the [Event Item documentation](https://develop.sentry.dev/sdk/envelopes/#event)
19    /// for more details.
20    Event(Event<'static>),
21    /// A Session Item.
22    ///
23    /// See the [Session Item documentation](https://develop.sentry.dev/sdk/envelopes/#session)
24    /// for more details.
25    SessionUpdate(SessionUpdate<'static>),
26    /// A Transaction Item.
27    ///
28    /// See the [Transaction Item documentation](https://develop.sentry.dev/sdk/envelopes/#transaction)
29    /// for more details.
30    Transaction(Transaction<'static>),
31    // TODO:
32    // * Attachment,
33    // etc…
34}
35
36impl From<Event<'static>> for EnvelopeItem {
37    fn from(event: Event<'static>) -> Self {
38        EnvelopeItem::Event(event)
39    }
40}
41
42impl From<SessionUpdate<'static>> for EnvelopeItem {
43    fn from(session: SessionUpdate<'static>) -> Self {
44        EnvelopeItem::SessionUpdate(session)
45    }
46}
47
48impl From<Transaction<'static>> for EnvelopeItem {
49    fn from(session: Transaction<'static>) -> Self {
50        EnvelopeItem::Transaction(session)
51    }
52}
53
54/// An Iterator over the items of an Envelope.
55#[derive(Clone)]
56pub struct EnvelopeItemIter<'s> {
57    inner: std::slice::Iter<'s, EnvelopeItem>,
58}
59
60impl<'s> Iterator for EnvelopeItemIter<'s> {
61    type Item = &'s EnvelopeItem;
62
63    fn next(&mut self) -> Option<Self::Item> {
64        self.inner.next()
65    }
66}
67
68/// A Sentry Envelope.
69///
70/// An Envelope is the data format that Sentry uses for Ingestion. It can contain
71/// multiple Items, some of which are related, such as Events, and Event Attachments.
72/// Other Items, such as Sessions are independent.
73///
74/// See the [documentation on Envelopes](https://develop.sentry.dev/sdk/envelopes/)
75/// for more details.
76#[derive(Clone, Default, Debug, PartialEq)]
77pub struct Envelope {
78    event_id: Option<Uuid>,
79    items: Vec<EnvelopeItem>,
80}
81
82impl Envelope {
83    /// Creates a new empty Envelope.
84    pub fn new() -> Envelope {
85        Default::default()
86    }
87
88    /// Add a new Envelope Item.
89    pub fn add_item<I>(&mut self, item: I)
90    where
91        I: Into<EnvelopeItem>,
92    {
93        let item = item.into();
94        if self.event_id.is_none() {
95            if let EnvelopeItem::Event(ref event) = item {
96                self.event_id = Some(event.event_id);
97            } else if let EnvelopeItem::Transaction(ref transaction) = item {
98                self.event_id = Some(transaction.event_id);
99            }
100        }
101        self.items.push(item);
102    }
103
104    /// Create an [`Iterator`] over all the [`EnvelopeItem`]s.
105    pub fn items(&self) -> EnvelopeItemIter {
106        EnvelopeItemIter {
107            inner: self.items.iter(),
108        }
109    }
110
111    /// Returns the Envelopes Uuid, if any.
112    pub fn uuid(&self) -> Option<&Uuid> {
113        self.event_id.as_ref()
114    }
115
116    /// Returns the [`Event`] contained in this Envelope, if any.
117    ///
118    /// [`Event`]: struct.Event.html
119    pub fn event(&self) -> Option<&Event<'static>> {
120        self.items
121            .iter()
122            .filter_map(|item| match item {
123                EnvelopeItem::Event(event) => Some(event),
124                _ => None,
125            })
126            .next()
127    }
128
129    /// Serialize the Envelope into the given [`Write`].
130    ///
131    /// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
132    pub fn to_writer<W>(&self, mut writer: W) -> std::io::Result<()>
133    where
134        W: Write,
135    {
136        let mut item_buf = Vec::new();
137
138        // write the headers:
139        let event_id = self.uuid();
140        match event_id {
141            Some(uuid) => writeln!(writer, r#"{{"event_id":"{}"}}"#, uuid)?,
142            _ => writeln!(writer, "{{}}")?,
143        }
144
145        // write each item:
146        for item in &self.items {
147            // we write them to a temporary buffer first, since we need their length
148            match item {
149                EnvelopeItem::Event(event) => serde_json::to_writer(&mut item_buf, event)?,
150                EnvelopeItem::SessionUpdate(session) => {
151                    serde_json::to_writer(&mut item_buf, session)?
152                }
153                EnvelopeItem::Transaction(transaction) => {
154                    serde_json::to_writer(&mut item_buf, transaction)?
155                }
156            }
157            let item_type = match item {
158                EnvelopeItem::Event(_) => "event",
159                EnvelopeItem::SessionUpdate(_) => "session",
160                EnvelopeItem::Transaction(_) => "transaction",
161            };
162            writeln!(
163                writer,
164                r#"{{"type":"{}","length":{}}}"#,
165                item_type,
166                item_buf.len()
167            )?;
168            writer.write_all(&item_buf)?;
169            writeln!(writer)?;
170            item_buf.clear();
171        }
172
173        Ok(())
174    }
175}
176
177impl From<Event<'static>> for Envelope {
178    fn from(event: Event<'static>) -> Self {
179        let mut envelope = Self::default();
180        envelope.add_item(event);
181        envelope
182    }
183}
184
185impl From<Transaction<'static>> for Envelope {
186    fn from(transaction: Transaction<'static>) -> Self {
187        let mut envelope = Self::default();
188        envelope.add_item(transaction);
189        envelope
190    }
191}
192
193#[cfg(test)]
194mod test {
195    use chrono::{DateTime, Utc};
196
197    use super::*;
198    use crate::protocol::v7::{SessionAttributes, SessionStatus, Span};
199
200    fn to_str(envelope: Envelope) -> String {
201        let mut vec = Vec::new();
202        envelope.to_writer(&mut vec).unwrap();
203        String::from_utf8_lossy(&vec).to_string()
204    }
205
206    #[test]
207    fn test_empty() {
208        assert_eq!(to_str(Envelope::new()), "{}\n");
209    }
210
211    #[test]
212    fn test_event() {
213        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
214        let timestamp = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
215        let event = Event {
216            event_id,
217            timestamp,
218            ..Default::default()
219        };
220        let envelope: Envelope = event.into();
221        assert_eq!(
222            to_str(envelope),
223            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
224{"type":"event","length":74}
225{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","timestamp":1595256674.296}
226"#
227        )
228    }
229
230    #[test]
231    fn test_session() {
232        let session_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
233        let started = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
234        let session = SessionUpdate {
235            session_id,
236            distinct_id: Some("foo@bar.baz".to_owned()),
237            sequence: None,
238            timestamp: None,
239            started,
240            init: true,
241            duration: Some(1.234),
242            status: SessionStatus::Ok,
243            errors: 123,
244            attributes: SessionAttributes {
245                release: "foo-bar@1.2.3".into(),
246                environment: Some("production".into()),
247                ip_address: None,
248                user_agent: None,
249            },
250        };
251        let mut envelope = Envelope::new();
252        envelope.add_item(session);
253        assert_eq!(
254            to_str(envelope),
255            r#"{}
256{"type":"session","length":222}
257{"sid":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c","did":"foo@bar.baz","started":"2020-07-20T14:51:14.296Z","init":true,"duration":1.234,"status":"ok","errors":123,"attrs":{"release":"foo-bar@1.2.3","environment":"production"}}
258"#
259        )
260    }
261
262    #[test]
263    fn test_transaction() {
264        let event_id = Uuid::parse_str("22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c").unwrap();
265        let span_id = Uuid::parse_str("d42cee9f-c3e7-4f5c-ada9-47ab601a14d2").unwrap();
266        let trace_id = Uuid::parse_str("335e53d6-1447-4acc-9f89-e632b776cc28").unwrap();
267        let start_timestamp = "2020-07-20T14:51:14.296Z".parse::<DateTime<Utc>>().unwrap();
268        let spans = vec![Span {
269            span_id,
270            trace_id,
271            start_timestamp,
272            ..Default::default()
273        }];
274        let transaction = Transaction {
275            event_id,
276            start_timestamp,
277            spans,
278            ..Default::default()
279        };
280        let envelope: Envelope = transaction.into();
281        assert_eq!(
282            to_str(envelope),
283            r#"{"event_id":"22d00b3f-d1b1-4b5d-8d20-49d138cd8a9c"}
284{"type":"transaction","length":216}
285{"event_id":"22d00b3fd1b14b5d8d2049d138cd8a9c","start_timestamp":1595256674.296,"spans":[{"span_id":"d42cee9fc3e74f5cada947ab601a14d2","trace_id":"335e53d614474acc9f89e632b776cc28","start_timestamp":1595256674.296}]}
286"#
287        )
288    }
289}