relay_event_normalization/normalize/
nel.rs

1//! Contains helper function for NEL reports.
2
3use chrono::{Duration, Utc};
4use relay_event_schema::protocol::{
5    Contexts, Event, HeaderName, HeaderValue, Headers, LogEntry, NelContext, NetworkReportRaw,
6    Request, ResponseContext, Timestamp,
7};
8use relay_protocol::Annotated;
9
10/// Enriches the event with new values using the provided [`NetworkReportRaw`].
11pub fn enrich_event(event: &mut Event, nel: Annotated<NetworkReportRaw>) {
12    // If the incoming NEL report is empty or it contains an empty body, just exit.
13    let Some(nel) = nel.into_value() else {
14        return;
15    };
16    let Some(body) = nel.body.into_value() else {
17        return;
18    };
19
20    event.logger = Annotated::from("nel".to_string());
21
22    event.logentry = Annotated::new(LogEntry::from({
23        if nel.ty.value().map_or("<unknown-type>", |v| v.as_str()) == "http.error" {
24            format!(
25                "{} / {} ({})",
26                body.phase.as_str().unwrap_or("<unknown-phase>"),
27                body.ty.as_str().unwrap_or("<unknown-type>"),
28                body.status_code.value().unwrap_or(&0)
29            )
30        } else {
31            format!(
32                "{} / {}",
33                body.phase.as_str().unwrap_or("<unknown-phase>"),
34                body.ty.as_str().unwrap_or("<unknown-type>"),
35            )
36        }
37    }));
38
39    let request = event.request.get_or_insert_with(Request::default);
40    request.url = nel.url;
41    request.method = body.method;
42    request.protocol = body.protocol;
43
44    let headers = request.headers.get_or_insert_with(Headers::default);
45
46    if let Some(ref user_agent) = nel.user_agent.value() {
47        if !user_agent.is_empty() {
48            headers.insert(
49                HeaderName::new("user-agent"),
50                HeaderValue::new(user_agent).into(),
51            );
52        }
53    }
54
55    if let Some(referrer) = body.referrer.value() {
56        headers.insert(
57            HeaderName::new("referer"),
58            HeaderValue::new(referrer).into(),
59        );
60    }
61
62    let contexts = event.contexts.get_or_insert_with(Contexts::new);
63
64    let nel_context = contexts.get_or_default::<NelContext>();
65    nel_context.server_ip = body.server_ip;
66    nel_context.elapsed_time = body.elapsed_time;
67    nel_context.error_type = body.ty;
68    nel_context.phase = body.phase;
69    nel_context.sampling_fraction = body.sampling_fraction;
70
71    // Set response status code only if it's bigger than zero.
72    let status_code = body
73        .status_code
74        .map_value(|v| u64::try_from(v).unwrap_or(0));
75    if status_code.value().unwrap_or(&0) > &0 {
76        let response_context = contexts.get_or_default::<ResponseContext>();
77        response_context.status_code = status_code;
78    }
79
80    // Set the timestamp on the event when it actually occurred.
81    let event_time = event
82        .timestamp
83        .value_mut()
84        .map_or(Utc::now(), |timestamp| timestamp.into_inner());
85    if let Some(event_time) =
86        event_time.checked_sub_signed(Duration::milliseconds(*nel.age.value().unwrap_or(&0)))
87    {
88        event.timestamp = Annotated::new(Timestamp::from(event_time))
89    }
90}