-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Network Error Logging (#2421)
The first part of the NEL ([Network Error Logging](https://developer.mozilla.org/en-US/docs/Web/HTTP/Network_Error_Logging)) implementation. * Adds a new `Nel` EventType * Adds a new `Nel` protocol with minimal validation (i.e. only specific fields are captured) * Adds a new `/api/:project_id/nel/` endpoint for NEL reports ingestion * simple content type validation * splits payload into separate envelopes (a single HTTP NEL request could contain several independent reports) * `user.ip_address` field is set to the IP address of the request (real user's IP address) * An event is enriched with browser information derived from the request's `User-Agent` header Related PRs: getsentry/sentry#55135 getsentry/sentry-kafka-schemas#177 --------- Co-authored-by: Oleksandr Kylymnychenko <oleksandr@sentry.io> Co-authored-by: Oleksandr <1931331+olksdr@users.noreply.github.com> Co-authored-by: Jan Michael Auer <mail@jauer.org>
- Loading branch information
1 parent
71e819c
commit de5e0fa
Showing
25 changed files
with
811 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
//! Contains helper function for NEL reports. | ||
use chrono::{Duration, Utc}; | ||
use relay_event_schema::protocol::{ | ||
Contexts, Event, HeaderName, HeaderValue, Headers, LogEntry, NelContext, NetworkReportRaw, | ||
Request, ResponseContext, Timestamp, | ||
}; | ||
use relay_protocol::Annotated; | ||
|
||
/// Enriches the event with new values using the provided [`NetworkReportRaw`]. | ||
pub fn enrich_event(event: &mut Event, nel: Annotated<NetworkReportRaw>) { | ||
// If the incoming NEL report is empty or it contains an empty body, just exit. | ||
let Some(nel) = nel.into_value() else { | ||
return; | ||
}; | ||
let Some(body) = nel.body.into_value() else { | ||
return; | ||
}; | ||
|
||
event.logger = Annotated::from("nel".to_string()); | ||
|
||
event.logentry = Annotated::new(LogEntry::from({ | ||
if nel.ty.value().map_or("<unknown-type>", |v| v.as_str()) == "http.error" { | ||
format!( | ||
"{} / {} ({})", | ||
body.phase.as_str().unwrap_or("<unknown-phase>"), | ||
body.ty.as_str().unwrap_or("<unknown-type>"), | ||
body.status_code.value().unwrap_or(&0) | ||
) | ||
} else { | ||
format!( | ||
"{} / {}", | ||
body.phase.as_str().unwrap_or("<unknown-phase>"), | ||
body.ty.as_str().unwrap_or("<unknown-type>"), | ||
) | ||
} | ||
})); | ||
|
||
let request = event.request.get_or_insert_with(Request::default); | ||
request.url = nel.url; | ||
request.method = body.method; | ||
request.protocol = body.protocol; | ||
|
||
let headers = request.headers.get_or_insert_with(Headers::default); | ||
|
||
if let Some(ref user_agent) = nel.user_agent.value() { | ||
if !user_agent.is_empty() { | ||
headers.insert( | ||
HeaderName::new("user-agent"), | ||
HeaderValue::new(user_agent).into(), | ||
); | ||
} | ||
} | ||
|
||
if let Some(referrer) = body.referrer.value() { | ||
headers.insert( | ||
HeaderName::new("referer"), | ||
HeaderValue::new(referrer).into(), | ||
); | ||
} | ||
|
||
let contexts = event.contexts.get_or_insert_with(Contexts::new); | ||
|
||
let nel_context = contexts.get_or_default::<NelContext>(); | ||
nel_context.server_ip = body.server_ip; | ||
nel_context.elapsed_time = body.elapsed_time; | ||
nel_context.error_type = body.ty; | ||
nel_context.phase = body.phase; | ||
nel_context.sampling_fraction = body.sampling_fraction; | ||
|
||
// Set response status code only if it's bigger than zero. | ||
let status_code = body | ||
.status_code | ||
.map_value(|v| u64::try_from(v).unwrap_or(0)); | ||
if status_code.value().unwrap_or(&0) > &0 { | ||
let response_context = contexts.get_or_default::<ResponseContext>(); | ||
response_context.status_code = status_code; | ||
} | ||
|
||
// Set the timestamp on the event when it actually occurred. | ||
let event_time = event | ||
.timestamp | ||
.value_mut() | ||
.map_or(Utc::now(), |timestamp| timestamp.into_inner()); | ||
if let Some(event_time) = | ||
event_time.checked_sub_signed(Duration::milliseconds(*nel.age.value().unwrap_or(&0))) | ||
{ | ||
event.timestamp = Annotated::new(Timestamp::from(event_time)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#[cfg(feature = "jsonschema")] | ||
use relay_jsonschema_derive::JsonSchema; | ||
use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value}; | ||
|
||
use crate::processor::ProcessValue; | ||
use crate::protocol::{IpAddr, NetworkReportPhases}; | ||
|
||
/// Contains NEL report information. | ||
/// | ||
/// Network Error Logging (NEL) is a browser feature that allows reporting of failed network | ||
/// requests from the client side. See the following resources for more information: | ||
/// | ||
/// - [W3C Editor's Draft](https://w3c.github.io/network-error-logging/) | ||
/// - [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Network_Error_Logging) | ||
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)] | ||
#[cfg_attr(feature = "jsonschema", derive(JsonSchema))] | ||
pub struct NelContext { | ||
/// If request failed, the type of its network error. If request succeeded, "ok". | ||
pub error_type: Annotated<String>, | ||
/// Server IP where the requests was sent to. | ||
#[metastructure(pii = "maybe")] | ||
pub server_ip: Annotated<IpAddr>, | ||
/// The number of milliseconds between the start of the resource fetch and when it was aborted by the user agent. | ||
pub elapsed_time: Annotated<u64>, | ||
/// If request failed, the phase of its network error. If request succeeded, "application". | ||
pub phase: Annotated<NetworkReportPhases>, | ||
/// The sampling rate. | ||
pub sampling_fraction: Annotated<f64>, | ||
/// For forward compatibility. | ||
#[metastructure(additional_properties, pii = "maybe")] | ||
pub other: Object<Value>, | ||
} | ||
|
||
impl super::DefaultContext for NelContext { | ||
fn default_key() -> &'static str { | ||
"nel" | ||
} | ||
|
||
fn from_context(context: super::Context) -> Option<Self> { | ||
match context { | ||
super::Context::Nel(c) => Some(*c), | ||
_ => None, | ||
} | ||
} | ||
|
||
fn cast(context: &super::Context) -> Option<&Self> { | ||
match context { | ||
super::Context::Nel(c) => Some(c), | ||
_ => None, | ||
} | ||
} | ||
|
||
fn cast_mut(context: &mut super::Context) -> Option<&mut Self> { | ||
match context { | ||
super::Context::Nel(c) => Some(c), | ||
_ => None, | ||
} | ||
} | ||
|
||
fn into_context(self) -> super::Context { | ||
super::Context::Nel(Box::new(self)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.