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: {start_timestamp}"
57                )));
58                return Err(ProcessingAction::DeleteValueHard);
59            }
60        }
61        if let Some(end_timestamp) = span.timestamp.value() {
62            if end_timestamp.into_inner().timestamp_millis() < 0 {
63                meta.add_error(Error::invalid(format!(
64                    "timestamp is too stale: {end_timestamp}"
65                )));
66                return Err(ProcessingAction::DeleteValueHard);
67            }
68        }
69
70        Ok(())
71    }
72
73    fn process_breadcrumb(
74        &mut self,
75        breadcrumb: &mut Breadcrumb,
76        meta: &mut Meta,
77        _: &ProcessingState<'_>,
78    ) -> ProcessingResult where {
79        if let Some(timestamp) = breadcrumb.timestamp.value() {
80            if timestamp.into_inner().timestamp_millis() < 0 {
81                meta.add_error(Error::invalid(format!(
82                    "timestamp is too stale: {timestamp}"
83                )));
84                return Err(ProcessingAction::DeleteValueHard);
85            }
86        }
87
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use relay_event_schema::processor::{ProcessingState, process_value};
95    use relay_event_schema::protocol::{Breadcrumb, Event, Span};
96    use relay_protocol::{Annotated, assert_annotated_snapshot, get_value};
97
98    use crate::timestamp::TimestampProcessor;
99
100    #[test]
101    fn test_accept_recent_errors() {
102        let json = r#"{
103  "event_id": "52df9022835246eeb317dbd739ccd059",
104  "timestamp": 1
105}"#;
106        let mut error = Annotated::<Event>::from_json(json).unwrap();
107        assert!(
108            process_value(&mut error, &mut TimestampProcessor, ProcessingState::root()).is_ok()
109        );
110        assert_eq!(get_value!(error.timestamp!).into_inner().timestamp(), 1);
111    }
112
113    #[test]
114    fn test_reject_stale_errors() {
115        let json = r#"{
116  "event_id": "52df9022835246eeb317dbd739ccd059",
117  "timestamp": -1
118}"#;
119        let mut error = Annotated::<Event>::from_json(json).unwrap();
120        assert_eq!(
121            process_value(&mut error, &mut TimestampProcessor, ProcessingState::root())
122                .unwrap_err()
123                .to_string(),
124            "invalid transaction event: timestamp is too stale"
125        );
126    }
127
128    #[test]
129    fn test_accept_recent_transactions() {
130        let json = r#"{
131  "event_id": "52df9022835246eeb317dbd739ccd059",
132  "start_timestamp": 1,
133  "timestamp": 2
134}"#;
135        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
136        assert!(
137            process_value(
138                &mut transaction,
139                &mut TimestampProcessor,
140                ProcessingState::root()
141            )
142            .is_ok()
143        );
144    }
145
146    #[test]
147    fn test_reject_stale_transactions() {
148        let json = r#"{
149  "event_id": "52df9022835246eeb317dbd739ccd059",
150  "start_timestamp": -2,
151  "timestamp": -1
152}"#;
153        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
154        assert_eq!(
155            process_value(
156                &mut transaction,
157                &mut TimestampProcessor,
158                ProcessingState::root()
159            )
160            .unwrap_err()
161            .to_string(),
162            "invalid transaction event: timestamp is too stale"
163        );
164    }
165
166    #[test]
167    fn test_reject_long_running_transactions() {
168        let json = r#"{
169  "event_id": "52df9022835246eeb317dbd739ccd059",
170  "start_timestamp": -1,
171  "timestamp": 1
172}"#;
173        let mut transaction = Annotated::<Event>::from_json(json).unwrap();
174        assert_eq!(
175            process_value(
176                &mut transaction,
177                &mut TimestampProcessor,
178                ProcessingState::root()
179            )
180            .unwrap_err()
181            .to_string(),
182            "invalid transaction event: timestamp is too stale"
183        );
184    }
185
186    #[test]
187    fn test_accept_recent_span() {
188        let json = r#"{
189      "span_id": "52df9022835246eeb317dbd739ccd050",
190      "start_timestamp": 1,
191      "timestamp": 2
192    }"#;
193        let mut span = Annotated::<Span>::from_json(json).unwrap();
194        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
195        assert_eq!(
196            get_value!(span.start_timestamp!).into_inner().timestamp(),
197            1
198        );
199        assert_eq!(get_value!(span.timestamp!).into_inner().timestamp(), 2);
200    }
201
202    #[test]
203    fn test_reject_stale_span() {
204        let json = r#"{
205      "span_id": "52df9022835246eeb317dbd739ccd050",
206      "start_timestamp": -2,
207      "timestamp": -1
208    }"#;
209        let mut span = Annotated::<Span>::from_json(json).unwrap();
210        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
211        assert_annotated_snapshot!(&span, @r###"
212        {
213          "_meta": {
214            "": {
215              "err": [
216                [
217                  "invalid_data",
218                  {
219                    "reason": "start_timestamp is too stale: 1969-12-31 23:59:58 UTC"
220                  }
221                ]
222              ]
223            }
224          }
225        }
226        "###);
227    }
228
229    #[test]
230    fn test_reject_long_running_span() {
231        let json = r#"{
232      "span_id": "52df9022835246eeb317dbd739ccd050",
233      "start_timestamp": -1,
234      "timestamp": 1
235    }"#;
236        let mut span = Annotated::<Span>::from_json(json).unwrap();
237        assert!(process_value(&mut span, &mut TimestampProcessor, ProcessingState::root()).is_ok());
238        assert_annotated_snapshot!(&span, @r###"
239        {
240          "_meta": {
241            "": {
242              "err": [
243                [
244                  "invalid_data",
245                  {
246                    "reason": "start_timestamp is too stale: 1969-12-31 23:59:59 UTC"
247                  }
248                ]
249              ]
250            }
251          }
252        }
253        "###);
254    }
255
256    #[test]
257    fn test_accept_recent_breadcrumb() {
258        let json = r#"{
259      "timestamp": 1
260    }"#;
261        let mut breadcrumb = Annotated::<Breadcrumb>::from_json(json).unwrap();
262        assert!(
263            process_value(
264                &mut breadcrumb,
265                &mut TimestampProcessor,
266                ProcessingState::root()
267            )
268            .is_ok()
269        );
270        assert_eq!(
271            get_value!(breadcrumb.timestamp!).into_inner().timestamp(),
272            1
273        );
274    }
275
276    #[test]
277    fn test_reject_stale_breadcrumb() {
278        let json = r#"{
279      "timestamp": -1
280    }"#;
281        let mut breadcrumb = Annotated::<Breadcrumb>::from_json(json).unwrap();
282        assert!(
283            process_value(
284                &mut breadcrumb,
285                &mut TimestampProcessor,
286                ProcessingState::root()
287            )
288            .is_ok()
289        );
290        assert_annotated_snapshot!(&breadcrumb, @r###"
291        {
292          "_meta": {
293            "": {
294              "err": [
295                [
296                  "invalid_data",
297                  {
298                    "reason": "timestamp is too stale: 1969-12-31 23:59:59 UTC"
299                  }
300                ]
301              ]
302            }
303          }
304        }
305        "###);
306    }
307}