Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(on-demand): Add computed contexts #4239

Merged
merged 14 commits into from
Nov 12, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- Run internal worker threads with a lower priority. ([#4222](https://github.com/getsentry/relay/pull/4222))
- Add additional fields to the `Event` `Getter`. ([#4238](https://github.com/getsentry/relay/pull/4238))
- Replace u64 with `OrganizationId` new-type struct for organization id. ([#4159](https://github.com/getsentry/relay/pull/4159))
- Add computed contexts for `os`, `browser` and `runtime`. ([#4239](https://github.com/getsentry/relay/pull/4239))

## 24.10.0

Expand Down
7 changes: 6 additions & 1 deletion py/tests/test_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,12 @@ def test_normalize_user_agent(must_normalize):

if must_normalize:
assert event["contexts"] == {
"browser": {"name": "Firefox", "version": "15.0.1", "type": "browser"},
"browser": {
"browser": "Firefox 15.0.1",
"name": "Firefox",
"version": "15.0.1",
"type": "browser",
},
"client_os": {"name": "Ubuntu", "type": "os"},
}
else:
Expand Down
105 changes: 104 additions & 1 deletion relay-event-normalization/src/normalize/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use std::collections::HashMap;

use once_cell::sync::Lazy;
use regex::Regex;
use relay_event_schema::protocol::{Context, Cookies, OsContext, ResponseContext, RuntimeContext};
use relay_event_schema::protocol::{
BrowserContext, Context, Cookies, OsContext, ResponseContext, RuntimeContext,
};
use relay_protocol::{Annotated, Empty, Value};

/// Environment.OSVersion (GetVersionEx) or RuntimeInformation.OSDescription on Windows
Expand Down Expand Up @@ -138,6 +140,14 @@ fn normalize_runtime_context(runtime: &mut RuntimeContext) {
}
}
}

// Calculation of the computed context for the runtime.
// The equivalent calculation is done in `sentry` in `src/sentry/interfaces/contexts.py`.
if runtime.runtime.value().is_none() {
if let (Some(name), Some(version)) = (runtime.name.value(), runtime.version.value()) {
runtime.runtime = Annotated::from(format!("{} {}", name, version));
}
}
}

/// Parses the Windows build number from the description and maps it to a marketing name.
Expand Down Expand Up @@ -183,6 +193,7 @@ pub fn get_android_api_version(description: &str) -> Option<&str> {

fn normalize_os_context(os: &mut OsContext) {
if os.name.value().is_some() || os.version.value().is_some() {
compute_os_context(os);
return;
}

Expand Down Expand Up @@ -248,6 +259,28 @@ fn normalize_os_context(os: &mut OsContext) {
.into();
}
}

compute_os_context(os);
}

fn compute_os_context(os: &mut OsContext) {
// Calculation of the computed context for the os.
// The equivalent calculation is done in `sentry` in `src/sentry/interfaces/contexts.py`.
if os.os.value().is_none() {
if let (Some(name), Some(version)) = (os.name.value(), os.version.value()) {
os.os = Annotated::from(format!("{} {}", name, version));
}
}
}

fn normalize_browser_context(browser: &mut BrowserContext) {
// Calculation of the computed context for the browser.
// The equivalent calculation is done in `sentry` in `src/sentry/interfaces/contexts.py`.
if browser.browser.value().is_none() {
if let (Some(name), Some(version)) = (browser.name.value(), browser.version.value()) {
browser.browser = Annotated::from(format!("{} {}", name, version));
}
}
}

fn parse_raw_response_data(response: &ResponseContext) -> Option<(&'static str, Value)> {
Expand Down Expand Up @@ -307,6 +340,7 @@ pub fn normalize_context(context: &mut Context) {
match context {
Context::Runtime(runtime) => normalize_runtime_context(runtime),
Context::Os(os) => normalize_os_context(os),
Context::Browser(browser) => normalize_browser_context(browser),
Context::Response(response) => normalize_response(response),
Context::Device(device) => {
if let Some(product_name) = device
Expand Down Expand Up @@ -771,4 +805,73 @@ mod tests {
assert_eq!(response.inferred_content_type.value(), None);
assert_eq!(response.data.as_str(), Some(r#"{"foo":"b"#));
}

#[test]
fn test_os_computed_context() {
let mut os = OsContext {
name: "Windows".to_string().into(),
version: "10".to_string().into(),
..OsContext::default()
};

normalize_os_context(&mut os);
assert_eq!(Some("Windows 10"), os.os.as_str());
}

#[test]
fn test_os_computed_context_missing_version() {
let mut os = OsContext {
name: "Windows".to_string().into(),
..OsContext::default()
};

normalize_os_context(&mut os);
assert_eq!(None, os.os.value());
}

#[test]
fn test_runtime_computed_context() {
let mut runtime = RuntimeContext {
name: "Python".to_string().into(),
version: "3.9.0".to_string().into(),
..RuntimeContext::default()
};

normalize_runtime_context(&mut runtime);
assert_eq!(Some("Python 3.9.0"), runtime.runtime.as_str());
}

#[test]
fn test_runtime_computed_context_missing_version() {
let mut runtime = RuntimeContext {
name: "Python".to_string().into(),
..RuntimeContext::default()
};

normalize_runtime_context(&mut runtime);
assert_eq!(None, runtime.runtime.value());
}

#[test]
fn test_browser_computed_context() {
let mut browser = BrowserContext {
name: "Firefox".to_string().into(),
version: "89.0".to_string().into(),
..BrowserContext::default()
};

normalize_browser_context(&mut browser);
assert_eq!(Some("Firefox 89.0"), browser.browser.as_str());
}

#[test]
fn test_browser_computed_context_missing_version() {
let mut browser = BrowserContext {
name: "Firefox".to_string().into(),
..BrowserContext::default()
};

normalize_browser_context(&mut browser);
assert_eq!(None, browser.browser.value());
}
}
60 changes: 32 additions & 28 deletions relay-event-normalization/src/normalize/user_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -821,13 +821,14 @@ mod tests {
let browser =
BrowserContext::from_hints_or_ua(&RawUserAgentInfo::from_headers(&headers)).unwrap();

insta::assert_debug_snapshot!(browser, @r#"
insta::assert_debug_snapshot!(browser, @r###"
BrowserContext {
browser: ~,
name: "Google Chrome",
version: "109",
other: {},
}
"#);
"###);
}

#[test]
Expand Down Expand Up @@ -873,13 +874,14 @@ mod tests {
let browser =
BrowserContext::from_hints_or_ua(&RawUserAgentInfo::from_headers(&headers)).unwrap();

insta::assert_debug_snapshot!(browser, @r#"
insta::assert_debug_snapshot!(browser, @r###"
BrowserContext {
browser: ~,
name: "weird browser",
version: "109",
other: {},
}
"#);
"###);
}

#[test]
Expand Down Expand Up @@ -952,18 +954,19 @@ mod tests {

let os = OsContext::from_hints_or_ua(&RawUserAgentInfo::from_headers(&headers)).unwrap();

insta::assert_debug_snapshot!(os, @r#"
OsContext {
name: "macOS",
version: "13.1.0",
build: ~,
kernel_version: ~,
rooted: ~,
distribution: ~,
raw_description: ~,
other: {},
}
"#);
insta::assert_debug_snapshot!(os, @r###"
OsContext {
os: ~,
name: "macOS",
version: "13.1.0",
build: ~,
kernel_version: ~,
rooted: ~,
distribution: ~,
raw_description: ~,
other: {},
}
"###);
}

#[test]
Expand Down Expand Up @@ -1024,18 +1027,19 @@ OsContext {

let os = OsContext::from_hints_or_ua(&RawUserAgentInfo::from_headers(&headers)).unwrap();

insta::assert_debug_snapshot!(os, @r#"
OsContext {
name: "Mac OS X",
version: "10.15.6",
build: ~,
kernel_version: ~,
rooted: ~,
distribution: ~,
raw_description: ~,
other: {},
}
"#);
insta::assert_debug_snapshot!(os, @r###"
OsContext {
os: ~,
name: "Mac OS X",
version: "10.15.6",
build: ~,
kernel_version: ~,
rooted: ~,
distribution: ~,
raw_description: ~,
other: {},
}
"###);
}

#[test]
Expand Down
5 changes: 5 additions & 0 deletions relay-event-schema/src/protocol/contexts/browser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::processor::ProcessValue;
/// Web browser information.
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
pub struct BrowserContext {
/// Computed field from `name` and `version`. Needed by the metrics extraction.
pub browser: Annotated<String>,

/// Display name of the browser application.
pub name: Annotated<String>,

Expand Down Expand Up @@ -55,12 +58,14 @@ mod tests {
#[test]
fn test_browser_context_roundtrip() {
let json = r#"{
"browser": "Google Chrome 67.0.3396.99",
"name": "Google Chrome",
"version": "67.0.3396.99",
"other": "value",
"type": "browser"
}"#;
let context = Annotated::new(Context::Browser(Box::new(BrowserContext {
browser: Annotated::new(String::from("Google Chrome 67.0.3396.99")),
name: Annotated::new("Google Chrome".to_string()),
version: Annotated::new("67.0.3396.99".to_string()),
other: {
Expand Down
7 changes: 7 additions & 0 deletions relay-event-schema/src/protocol/contexts/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use crate::protocol::LenientString;
/// is the operating system of the browser (generally pulled from the User-Agent string).
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
pub struct OsContext {
/// Computed field from `name` and `version`. Needed by the metrics extraction.
pub os: Annotated<String>,

/// Name of the operating system.
pub name: Annotated<String>,

Expand Down Expand Up @@ -106,6 +109,7 @@ mod tests {
#[test]
fn test_os_context_roundtrip() {
let json = r#"{
"os": "iOS 11.4.2",
"name": "iOS",
"version": "11.4.2",
"build": "FEEDFACE",
Expand All @@ -116,6 +120,7 @@ mod tests {
"type": "os"
}"#;
let context = Annotated::new(Context::Os(Box::new(OsContext {
os: Annotated::new("iOS 11.4.2".to_string()),
name: Annotated::new("iOS".to_string()),
version: Annotated::new("11.4.2".to_string()),
build: Annotated::new(LenientString("FEEDFACE".to_string())),
Expand All @@ -140,6 +145,7 @@ mod tests {
#[test]
fn test_os_context_linux_roundtrip() {
let json = r#"{
"os": "Linux 5.15.133",
"name": "Linux",
"version": "5.15.133",
"build": "1-microsoft-standard-WSL2",
Expand All @@ -151,6 +157,7 @@ mod tests {
"type": "os"
}"#;
let context = Annotated::new(Context::Os(Box::new(OsContext {
os: Annotated::new("Linux 5.15.133".to_string()),
name: Annotated::new("Linux".to_string()),
version: Annotated::new("5.15.133".to_string()),
build: Annotated::new(LenientString("1-microsoft-standard-WSL2".to_string())),
Expand Down
5 changes: 5 additions & 0 deletions relay-event-schema/src/protocol/contexts/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ use crate::protocol::LenientString;
/// JavaScript application running on top of JVM).
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
pub struct RuntimeContext {
/// Computed field from `name` and `version`. Needed by the metrics extraction.
pub runtime: Annotated<String>,

/// Runtime name.
pub name: Annotated<String>,

Expand Down Expand Up @@ -72,6 +75,7 @@ mod tests {
#[test]
fn test_runtime_context_roundtrip() {
let json = r#"{
"runtime": "rustc 1.27.0",
"name": "rustc",
"version": "1.27.0",
"build": "stable",
Expand All @@ -80,6 +84,7 @@ mod tests {
"type": "runtime"
}"#;
let context = Annotated::new(Context::Runtime(Box::new(RuntimeContext {
runtime: Annotated::new("rustc 1.27.0".to_string()),
name: Annotated::new("rustc".to_string()),
version: Annotated::new("1.27.0".to_string()),
build: Annotated::new(LenientString("stable".to_string())),
Expand Down
3 changes: 3 additions & 0 deletions relay-event-schema/src/protocol/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,13 +729,15 @@ impl Getter for Event {
.get("slug")?
.value()?
.into(),
"contexts.os" => self.context::<OsContext>()?.os.as_str()?.into(),
"contexts.os.build" => self.context::<OsContext>()?.build.as_str()?.into(),
"contexts.os.kernel_version" => {
self.context::<OsContext>()?.kernel_version.as_str()?.into()
}
"contexts.os.name" => self.context::<OsContext>()?.name.as_str()?.into(),
"contexts.os.version" => self.context::<OsContext>()?.version.as_str()?.into(),
"contexts.os.rooted" => self.context::<OsContext>()?.rooted.value()?.into(),
"contexts.browser" => self.context::<BrowserContext>()?.browser.as_str()?.into(),
"contexts.browser.name" => self.context::<BrowserContext>()?.name.as_str()?.into(),
"contexts.browser.version" => {
self.context::<BrowserContext>()?.version.as_str()?.into()
Expand Down Expand Up @@ -763,6 +765,7 @@ impl Getter for Event {
super::Context::Other(context) => context.get("crash_type")?.value()?.into(),
_ => return None,
},
"contexts.runtime" => self.context::<RuntimeContext>()?.runtime.as_str()?.into(),
"contexts.runtime.name" => self.context::<RuntimeContext>()?.name.as_str()?.into(),

// Computed fields (see Discover)
Expand Down
2 changes: 1 addition & 1 deletion relay-server/src/services/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3492,7 +3492,7 @@ mod tests {
let contexts = event.contexts.into_value().unwrap();
let browser = contexts.0.get("browser").unwrap();
assert_eq!(
r#"{"name":"Chrome","version":"103.0.0","type":"browser"}"#,
r#"{"browser":"Chrome 103.0.0","name":"Chrome","version":"103.0.0","type":"browser"}"#,
browser.to_json().unwrap()
);
}
Expand Down
Loading
Loading