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::{ProcessingState, process_value};
98    use relay_event_schema::protocol::{Breadcrumb, Event, Span};
99    use relay_protocol::{Annotated, assert_annotated_snapshot, get_value};
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!(
140            process_value(
141                &mut transaction,
142                &mut TimestampProcessor,
143                ProcessingState::root()
144            )
145            .is_ok()
146        );
147    }
148
149    #[test]
150    fn test_reject_stale_transactions() {
151        let json = r#"{
152  "event_id": "52df9022835246eeb317dbd739ccd059",
153  "start_timestamp": -2,
154  "timestamp": -1
155}"#;
156        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
157        assert_eq!(
158            process_value(
159                &mut transaction,
160                &mut TimestampProcessor,
161                ProcessingState::root()
162            )
163            .unwrap_err()
164            .to_string(),
165            "invalid transaction event: timestamp is too stale"
166        );
167    }
168
169    #[test]
170    fn test_reject_long_running_transactions() {
171        let json = r#"{
172  "event_id": "52df9022835246eeb317dbd739ccd059",
173  "start_timestamp": -1,
174  "timestamp": 1
175}"#;
176        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
177        assert_eq!(
178            process_value(
179                &mut transaction,
180                &mut TimestampProcessor,
181                ProcessingState::root()
182            )
183            .unwrap_err()
184            .to_string(),
185            "invalid transaction event: timestamp is too stale"
186        );
187    }
188
189    #[test]
190    fn test_accept_recent_span() {
191        let json = r#"{
192      "span_id": "52df9022835246eeb317dbd739ccd050",
193      "start_timestamp": 1,
194      "timestamp": 2
195    }"#;
196        let mut span = Annotated::<Span>::from_json(json).unwrap();
197        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
198        assert_eq!(
199            get_value!(span.start_timestamp!).into_inner().timestamp(),
200            1
201        );
202        assert_eq!(get_value!(span.timestamp!).into_inner().timestamp(), 2);
203    }
204
205    #[test]
206    fn test_reject_stale_span() {
207        let json = r#"{
208      "span_id": "52df9022835246eeb317dbd739ccd050",
209      "start_timestamp": -2,
210      "timestamp": -1
211    }"#;
212        let mut span = Annotated::<Span>::from_json(json).unwrap();
213        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
214        assert_annotated_snapshot!(&span, @r###"
215        {
216          "_meta": {
217            "": {
218              "err": [
219                [
220                  "invalid_data",
221                  {
222                    "reason": "start_timestamp is too stale: 1969-12-31 23:59:58 UTC"
223                  }
224                ]
225              ]
226            }
227          }
228        }
229        "###);
230    }
231
232    #[test]
233    fn test_reject_long_running_span() {
234        let json = r#"{
235      "span_id": "52df9022835246eeb317dbd739ccd050",
236      "start_timestamp": -1,
237      "timestamp": 1
238    }"#;
239        let mut span = Annotated::<Span>::from_json(json).unwrap();
240        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
241        assert_annotated_snapshot!(&span, @r###"
242        {
243          "_meta": {
244            "": {
245              "err": [
246                [
247                  "invalid_data",
248                  {
249                    "reason": "start_timestamp is too stale: 1969-12-31 23:59:59 UTC"
250                  }
251                ]
252              ]
253            }
254          }
255        }
256        "###);
257    }
258
259    #[test]
260    fn test_accept_recent_breadcrumb() {
261        let json = r#"{
262      "timestamp": 1
263    }"#;
264        let mut breadcrumb = Annotated::<Breadcrumb>::from_json(json).unwrap();
265        assert!(
266            process_value(
267                &mut breadcrumb,
268                &mut TimestampProcessor,
269                ProcessingState::root()
270            )
271            .is_ok()
272        );
273        assert_eq!(
274            get_value!(breadcrumb.timestamp!).into_inner().timestamp(),
275            1
276        );
277    }
278
279    #[test]
280    fn test_reject_stale_breadcrumb() {
281        let json = r#"{
282      "timestamp": -1
283    }"#;
284        let mut breadcrumb = Annotated::<Breadcrumb>::from_json(json).unwrap();
285        assert!(
286            process_value(
287                &mut breadcrumb,
288                &mut TimestampProcessor,
289                ProcessingState::root()
290            )
291            .is_ok()
292        );
293        assert_annotated_snapshot!(&breadcrumb, @r###"
294        {
295          "_meta": {
296            "": {
297              "err": [
298                [
299                  "invalid_data",
300                  {
301                    "reason": "timestamp is too stale: 1969-12-31 23:59:59 UTC"
302                  }
303                ]
304              ]
305            }
306          }
307        }
308        "###);
309    }
310}