relay_event_normalization/
timestamp.rs

1use relay_event_schema::processor::{
2    ProcessValue, ProcessingAction, ProcessingResult, ProcessingState, Processor,
3};
4use relay_event_schema::protocol::{Breadcrumb, Event, Span};
5use relay_protocol::{Error, Meta};
6
7/// Ensures an event's timestamps are not stale.
8///
9/// Stale timestamps are those that happened before January 1, 1970 UTC. The
10/// processor validates the start and end timestamps of an event and returns an
11/// error if any of these are stale. Additionally, spans and breadcrumbs with
12/// stale timestamps are removed from the event.
13///
14/// The processor checks the timestamps individually and it's not responsible
15/// for decisions that relate timestamps together, including but not limited to:
16/// - Ensuring the start timestamp is not later than the end timestamp.
17/// - The event finished in the last X days.
18pub struct TimestampProcessor;
19
20impl Processor for TimestampProcessor {
21    fn process_event(
22        &mut self,
23        event: &mut Event,
24        _: &mut Meta,
25        state: &ProcessingState,
26    ) -> ProcessingResult {
27        if let Some(end_timestamp) = event.timestamp.value() {
28            if end_timestamp.into_inner().timestamp_millis() < 0 {
29                return Err(ProcessingAction::InvalidTransaction(
30                    "timestamp is too stale",
31                ));
32            }
33        }
34        if let Some(start_timestamp) = event.start_timestamp.value() {
35            if start_timestamp.into_inner().timestamp_millis() < 0 {
36                return Err(ProcessingAction::InvalidTransaction(
37                    "timestamp is too stale",
38                ));
39            }
40        }
41
42        event.process_child_values(self, state)?;
43
44        Ok(())
45    }
46
47    fn process_span(
48        &mut self,
49        span: &mut Span,
50        meta: &mut Meta,
51        _: &ProcessingState<'_>,
52    ) -> ProcessingResult {
53        if let Some(start_timestamp) = span.start_timestamp.value() {
54            if start_timestamp.into_inner().timestamp_millis() < 0 {
55                meta.add_error(Error::invalid(format!(
56                    "start_timestamp is too stale: {}",
57                    start_timestamp
58                )));
59                return Err(ProcessingAction::DeleteValueHard);
60            }
61        }
62        if let Some(end_timestamp) = span.timestamp.value() {
63            if end_timestamp.into_inner().timestamp_millis() < 0 {
64                meta.add_error(Error::invalid(format!(
65                    "timestamp is too stale: {}",
66                    end_timestamp
67                )));
68                return Err(ProcessingAction::DeleteValueHard);
69            }
70        }
71
72        Ok(())
73    }
74
75    fn process_breadcrumb(
76        &mut self,
77        breadcrumb: &mut Breadcrumb,
78        meta: &mut Meta,
79        _: &ProcessingState<'_>,
80    ) -> ProcessingResult where {
81        if let Some(timestamp) = breadcrumb.timestamp.value() {
82            if timestamp.into_inner().timestamp_millis() < 0 {
83                meta.add_error(Error::invalid(format!(
84                    "timestamp is too stale: {}",
85                    timestamp
86                )));
87                return Err(ProcessingAction::DeleteValueHard);
88            }
89        }
90
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use relay_event_schema::processor::{process_value, ProcessingState};
98    use relay_event_schema::protocol::{Breadcrumb, Event, Span};
99    use relay_protocol::{assert_annotated_snapshot, get_value, Annotated};
100
101    use crate::timestamp::TimestampProcessor;
102
103    #[test]
104    fn test_accept_recent_errors() {
105        let json = r#"{
106  "event_id": "52df9022835246eeb317dbd739ccd059",
107  "timestamp": 1
108}"#;
109        let mut error = Annotated::<Event>::from_json(json).unwrap();
110        assert!(
111            process_value(&mut error, &mut TimestampProcessor, ProcessingState::root()).is_ok()
112        );
113        assert_eq!(get_value!(error.timestamp!).into_inner().timestamp(), 1);
114    }
115
116    #[test]
117    fn test_reject_stale_errors() {
118        let json = r#"{
119  "event_id": "52df9022835246eeb317dbd739ccd059",
120  "timestamp": -1
121}"#;
122        let mut error = Annotated::<Event>::from_json(json).unwrap();
123        assert_eq!(
124            process_value(&mut error, &mut TimestampProcessor, ProcessingState::root())
125                .unwrap_err()
126                .to_string(),
127            "invalid transaction event: timestamp is too stale"
128        );
129    }
130
131    #[test]
132    fn test_accept_recent_transactions() {
133        let json = r#"{
134  "event_id": "52df9022835246eeb317dbd739ccd059",
135  "start_timestamp": 1,
136  "timestamp": 2
137}"#;
138        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
139        assert!(process_value(
140            &mut transaction,
141            &mut TimestampProcessor,
142            ProcessingState::root()
143        )
144        .is_ok());
145    }
146
147    #[test]
148    fn test_reject_stale_transactions() {
149        let json = r#"{
150  "event_id": "52df9022835246eeb317dbd739ccd059",
151  "start_timestamp": -2,
152  "timestamp": -1
153}"#;
154        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
155        assert_eq!(
156            process_value(
157                &mut transaction,
158                &mut TimestampProcessor,
159                ProcessingState::root()
160            )
161            .unwrap_err()
162            .to_string(),
163            "invalid transaction event: timestamp is too stale"
164        );
165    }
166
167    #[test]
168    fn test_reject_long_running_transactions() {
169        let json = r#"{
170  "event_id": "52df9022835246eeb317dbd739ccd059",
171  "start_timestamp": -1,
172  "timestamp": 1
173}"#;
174        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
175        assert_eq!(
176            process_value(
177                &mut transaction,
178                &mut TimestampProcessor,
179                ProcessingState::root()
180            )
181            .unwrap_err()
182            .to_string(),
183            "invalid transaction event: timestamp is too stale"
184        );
185    }
186
187    #[test]
188    fn test_accept_recent_span() {
189        let json = r#"{
190      "span_id": "52df9022835246eeb317dbd739ccd050",
191      "start_timestamp": 1,
192      "timestamp": 2
193    }"#;
194        let mut span = Annotated::<Span>::from_json(json).unwrap();
195        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
196        assert_eq!(
197            get_value!(span.start_timestamp!).into_inner().timestamp(),
198            1
199        );
200        assert_eq!(get_value!(span.timestamp!).into_inner().timestamp(), 2);
201    }
202
203    #[test]
204    fn test_reject_stale_span() {
205        let json = r#"{
206      "span_id": "52df9022835246eeb317dbd739ccd050",
207      "start_timestamp": -2,
208      "timestamp": -1
209    }"#;
210        let mut span = Annotated::<Span>::from_json(json).unwrap();
211        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
212        assert_annotated_snapshot!(&span, @r###"
213        {
214          "_meta": {
215            "": {
216              "err": [
217                [
218                  "invalid_data",
219                  {
220                    "reason": "start_timestamp is too stale: 1969-12-31 23:59:58 UTC"
221                  }
222                ]
223              ]
224            }
225          }
226        }
227        "###);
228    }
229
230    #[test]
231    fn test_reject_long_running_span() {
232        let json = r#"{
233      "span_id": "52df9022835246eeb317dbd739ccd050",
234      "start_timestamp": -1,
235      "timestamp": 1
236    }"#;
237        let mut span = Annotated::<Span>::from_json(json).unwrap();
238        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
239        assert_annotated_snapshot!(&span, @r###"
240        {
241          "_meta": {
242            "": {
243              "err": [
244                [
245                  "invalid_data",
246                  {
247                    "reason": "start_timestamp is too stale: 1969-12-31 23:59:59 UTC"
248                  }
249                ]
250              ]
251            }
252          }
253        }
254        "###);
255    }
256
257    #[test]
258    fn test_accept_recent_breadcrumb() {
259        let json = r#"{
260      "timestamp": 1
261    }"#;
262        let mut breadcrumb = Annotated::<Breadcrumb>::from_json(json).unwrap();
263        assert!(process_value(
264            &mut breadcrumb,
265            &mut TimestampProcessor,
266            ProcessingState::root()
267        )
268        .is_ok());
269        assert_eq!(
270            get_value!(breadcrumb.timestamp!).into_inner().timestamp(),
271            1
272        );
273    }
274
275    #[test]
276    fn test_reject_stale_breadcrumb() {
277        let json = r#"{
278      "timestamp": -1
279    }"#;
280        let mut breadcrumb = Annotated::<Breadcrumb>::from_json(json).unwrap();
281        assert!(process_value(
282            &mut breadcrumb,
283            &mut TimestampProcessor,
284            ProcessingState::root()
285        )
286        .is_ok());
287        assert_annotated_snapshot!(&breadcrumb, @r###"
288        {
289          "_meta": {
290            "": {
291              "err": [
292                [
293                  "invalid_data",
294                  {
295                    "reason": "timestamp is too stale: 1969-12-31 23:59:59 UTC"
296                  }
297                ]
298              ]
299            }
300          }
301        }
302        "###);
303    }
304}