From f7eaea6b98efa9267989725722508a2281dc64eb Mon Sep 17 00:00:00 2001 From: Oliver Browne Date: Sun, 6 Oct 2024 02:43:31 +0300 Subject: [PATCH] feat(err): parse stack traces (#25393) Co-authored-by: David Newell --- rust/Cargo.lock | 2 + rust/common/types/src/event.rs | 56 +++++++++++++++ rust/common/types/src/lib.rs | 1 + rust/cymbal/Cargo.toml | 2 + rust/cymbal/src/lib.rs | 2 + rust/cymbal/src/main.rs | 49 ++++++++++++-- rust/cymbal/src/symbols/js.rs | 25 +++++++ rust/cymbal/src/symbols/mod.rs | 3 + rust/cymbal/src/symbols/symbolifier.rs | 9 +++ rust/cymbal/src/symbols/types.rs | 79 ++++++++++++++++++++++ rust/cymbal/src/traits.rs | 13 ++++ rust/cymbal/tests/static/raw_js_stack.json | 11 +++ rust/cymbal/tests/types.rs | 26 +++++++ 13 files changed, 272 insertions(+), 6 deletions(-) create mode 100644 rust/cymbal/src/symbols/js.rs create mode 100644 rust/cymbal/src/symbols/mod.rs create mode 100644 rust/cymbal/src/symbols/symbolifier.rs create mode 100644 rust/cymbal/src/symbols/types.rs create mode 100644 rust/cymbal/src/traits.rs create mode 100644 rust/cymbal/tests/static/raw_js_stack.json create mode 100644 rust/cymbal/tests/types.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index c11edfa5f39fa..741e65eefd95c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -965,10 +965,12 @@ dependencies = [ "common-alloc", "common-kafka", "common-metrics", + "common-types", "envconfig", "health", "metrics", "rdkafka", + "serde", "serde_json", "sqlx", "thiserror", diff --git a/rust/common/types/src/event.rs b/rust/common/types/src/event.rs index 9a3c95c3a8e02..8f837345d34be 100644 --- a/rust/common/types/src/event.rs +++ b/rust/common/types/src/event.rs @@ -23,3 +23,59 @@ impl CapturedEvent { format!("{}:{}", self.token, self.distinct_id) } } + +#[derive(Debug, Deserialize, Serialize, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum PersonMode { + Full, + Propertyless, + ForceUpgrade, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct ClickHouseEvent { + pub uuid: Uuid, + pub team_id: i32, + pub event: String, + pub distinct_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub properties: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub person_id: Option, + // TODO: verify timestamp format + pub timestamp: String, + // TODO: verify timestamp format + pub created_at: String, + pub elements_chain: String, + // TODO: verify timestamp format + #[serde(skip_serializing_if = "Option::is_none")] + pub person_created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub person_properties: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group0_properties: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group1_properties: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group2_properties: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group3_properties: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub group4_properties: Option, + // TODO: verify timestamp format + #[serde(skip_serializing_if = "Option::is_none")] + pub group0_created_at: Option, + // TODO: verify timestamp format + #[serde(skip_serializing_if = "Option::is_none")] + pub group1_created_at: Option, + // TODO: verify timestamp format + #[serde(skip_serializing_if = "Option::is_none")] + pub group2_created_at: Option, + // TODO: verify timestamp format + #[serde(skip_serializing_if = "Option::is_none")] + pub group3_created_at: Option, + // TODO: verify timestamp format + #[serde(skip_serializing_if = "Option::is_none")] + pub group4_created_at: Option, + pub person_mode: PersonMode, +} diff --git a/rust/common/types/src/lib.rs b/rust/common/types/src/lib.rs index 79afc98c1e83f..61cad2345f0ed 100644 --- a/rust/common/types/src/lib.rs +++ b/rust/common/types/src/lib.rs @@ -3,6 +3,7 @@ mod team; // Events pub use event::CapturedEvent; +pub use event::ClickHouseEvent; // Teams pub use team::Team; diff --git a/rust/cymbal/Cargo.toml b/rust/cymbal/Cargo.toml index 5f337398b4b46..ca533f1a8fe0a 100644 --- a/rust/cymbal/Cargo.toml +++ b/rust/cymbal/Cargo.toml @@ -15,9 +15,11 @@ metrics = { workspace = true } common-metrics = { path = "../common/metrics" } common-alloc = { path = "../common/alloc" } common-kafka = { path = "../common/kafka" } +common-types = { path = "../common/types" } thiserror = { workspace = true } sqlx = { workspace = true } serde_json = { workspace = true } +serde = { workspace = true } [lints] workspace = true diff --git a/rust/cymbal/src/lib.rs b/rust/cymbal/src/lib.rs index 806fb49e9cef5..107aab957b8de 100644 --- a/rust/cymbal/src/lib.rs +++ b/rust/cymbal/src/lib.rs @@ -1,3 +1,5 @@ pub mod app_context; pub mod config; pub mod error; +pub mod symbols; +pub mod traits; diff --git a/rust/cymbal/src/main.rs b/rust/cymbal/src/main.rs index 66d9d0af13593..b6d9e5b47d36d 100644 --- a/rust/cymbal/src/main.rs +++ b/rust/cymbal/src/main.rs @@ -3,9 +3,14 @@ use std::{future::ready, sync::Arc}; use axum::{routing::get, Router}; use common_kafka::kafka_consumer::RecvErr; use common_metrics::{serve, setup_metrics_routes}; -use cymbal::{app_context::AppContext, config::Config, error::Error}; +use common_types::ClickHouseEvent; +use cymbal::{ + app_context::AppContext, + config::Config, + error::Error, + symbols::types::{PropertyView, RawFrame}, +}; use envconfig::Envconfig; -use serde_json::Value; use tokio::task::JoinHandle; use tracing::{error, info}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; @@ -57,7 +62,7 @@ async fn main() -> Result<(), Error> { context.worker_liveness.report_healthy().await; // Just grab the event as a serde_json::Value and immediately drop it, // we can work out a real type for it later (once we're deployed etc) - let (_, offset): (Value, _) = match context.consumer.json_recv().await { + let (event, offset): (ClickHouseEvent, _) = match context.consumer.json_recv().await { Ok(r) => r, Err(RecvErr::Kafka(e)) => { return Err(e.into()); // Just die if we recieve a Kafka error @@ -65,14 +70,46 @@ async fn main() -> Result<(), Error> { Err(err) => { // If we failed to parse the message, or it was empty, just log and continue, our // consumer has already stored the offset for us. - metrics::counter!("error_tracking_errors", "cause" => "recv_err").increment(1); + metrics::counter!("cymbal_errors", "cause" => "recv_err").increment(1); error!("Error receiving message: {:?}", err); continue; } }; - metrics::counter!("error_tracking_events_received").increment(1); + metrics::counter!("cymbal_events_received").increment(1); - // This is where the rest of the processing would go offset.store().unwrap(); + + if event.event != "$exception" { + error!("event of type {}", event.event); + continue; + } + + let Some(properties) = &event.properties else { + metrics::counter!("cymbal_errors", "cause" => "no_properties").increment(1); + continue; + }; + + let properties: PropertyView = match serde_json::from_str(properties) { + Ok(r) => r, + Err(err) => { + metrics::counter!("cymbal_errors", "cause" => "invalid_exception_properties") + .increment(1); + error!("Error parsing properties: {:?}", err); + continue; + } + }; + + let _stack_trace: Vec = + match serde_json::from_str(&properties.exception_stack_trace_raw) { + Ok(r) => r, + Err(err) => { + metrics::counter!("cymbal_errors", "cause" => "invalid_stack_trace") + .increment(1); + error!("Error parsing stack trace: {:?}", err); + continue; + } + }; + + metrics::counter!("cymbal_stack_track_processed").increment(1); } } diff --git a/rust/cymbal/src/symbols/js.rs b/rust/cymbal/src/symbols/js.rs new file mode 100644 index 0000000000000..59701e30d3ac8 --- /dev/null +++ b/rust/cymbal/src/symbols/js.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +// A minifed JS stack frame. Just the minimal information needed to lookup some +// sourcemap for it and produce a "real" stack frame. +#[derive(Debug, Clone, Deserialize)] +pub struct RawJSFrame { + #[serde(rename = "lineno")] + pub line: u32, + #[serde(rename = "colno")] + pub column: u32, + #[serde(rename = "filename")] + pub uri: String, + pub in_app: bool, + #[serde(rename = "function")] + pub fn_name: String, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ProcessedFrame { + pub line: u32, + pub column: u32, + pub uri: String, + pub in_app: bool, + pub fn_name: String, +} diff --git a/rust/cymbal/src/symbols/mod.rs b/rust/cymbal/src/symbols/mod.rs new file mode 100644 index 0000000000000..88cc15bfba33b --- /dev/null +++ b/rust/cymbal/src/symbols/mod.rs @@ -0,0 +1,3 @@ +pub mod js; +pub mod symbolifier; +pub mod types; diff --git a/rust/cymbal/src/symbols/symbolifier.rs b/rust/cymbal/src/symbols/symbolifier.rs new file mode 100644 index 0000000000000..75f53d781cce1 --- /dev/null +++ b/rust/cymbal/src/symbols/symbolifier.rs @@ -0,0 +1,9 @@ +use super::types::{ProcessedStack, RawStack}; + +pub struct Symbolifier; + +impl Symbolifier { + pub fn symbolify(_stack: RawStack) -> ProcessedStack { + unimplemented!() + } +} diff --git a/rust/cymbal/src/symbols/types.rs b/rust/cymbal/src/symbols/types.rs new file mode 100644 index 0000000000000..08239ab5c4254 --- /dev/null +++ b/rust/cymbal/src/symbols/types.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use serde::Serialize; +use serde_json::Value; + +use super::{js::RawJSFrame, symbolifier::Symbolifier}; + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum RawFrame { + JavaScript(RawJSFrame), +} + +// Stacks don't care what the "type" of the frames they contain are, and even permit +// frames of different types to be mixed together, because we're going to end up "exploding" +// them into their frame-set anyway, and dispatching a task per frame in a language-agnostic +// way. Supporting mixed-type stacks is a side benefit of this - I don't know that we'll ever +// see them, but we get the flexibility "for free" +#[derive(Debug, Deserialize)] +pub struct RawStack { + pub frames: Vec, +} + +pub enum ProcessedFrame { + JavaScript(), +} + +pub struct ProcessedStack { + pub frames: Vec, +} + +impl RawStack { + pub async fn process(self, _sym: &Symbolifier) -> ProcessedStack { + unimplemented!() + } +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct PropertyView { + #[serde(rename = "$exception_type")] + pub exception_type: String, + #[serde(rename = "$exception_message")] + pub exception_message: String, + #[serde(rename = "$exception_stack_trace_raw")] + pub exception_stack_trace_raw: String, + #[serde(rename = "$exception_level")] + pub exception_level: String, + #[serde(rename = "$exception_source")] + pub exception_source: String, + #[serde(rename = "$exception_lineno")] + pub exception_line: u32, + #[serde(rename = "$exception_colno")] + pub exception_col: u32, + #[serde(flatten)] + other: HashMap, +} + +#[cfg(test)] +mod test { + use common_types::ClickHouseEvent; + + use crate::symbols::types::{PropertyView, RawFrame}; + + #[test] + fn it_symbolifies() { + let raw: &'static str = include_str!("../../tests/static/raw_js_stack.json"); + + let raw: ClickHouseEvent = serde_json::from_str(raw).unwrap(); + + let exception_properties: PropertyView = + serde_json::from_str(&raw.properties.unwrap()).unwrap(); + + let stack_trace: Vec = + serde_json::from_str(&exception_properties.exception_stack_trace_raw).unwrap(); + + println!("{:?}", stack_trace); + } +} diff --git a/rust/cymbal/src/traits.rs b/rust/cymbal/src/traits.rs new file mode 100644 index 0000000000000..1449c934793d5 --- /dev/null +++ b/rust/cymbal/src/traits.rs @@ -0,0 +1,13 @@ +use crate::symbols::types::RawStack; + +// An "exception" is anything that can self-identify with a "fingerprint" +pub trait Exception { + fn id(&self) -> String; + fn to_raw(self) -> serde_json::Value; +} + +// Some excpetions have a raw stack trace we can process. If they do, + +pub trait Stacked: Exception { + fn stack(&self) -> RawStack; +} diff --git a/rust/cymbal/tests/static/raw_js_stack.json b/rust/cymbal/tests/static/raw_js_stack.json new file mode 100644 index 0000000000000..2bb362f511dc6 --- /dev/null +++ b/rust/cymbal/tests/static/raw_js_stack.json @@ -0,0 +1,11 @@ +{ + "uuid": "01925277-f069-7eb2-9236-bf86c4c3ed7d", + "event": "$exception", + "properties": "{\n \"$os\": \"Mac OS X\",\n \"$os_version\": \"10.15.7\",\n \"$browser\": \"Chrome\",\n \"$device_type\": \"Desktop\",\n \"$current_url\": \"https://us.posthog.com/project/2/feature_flags/50888\",\n \"$host\": \"us.posthog.com\",\n \"$pathname\": \"/project/2/feature_flags/50888\",\n \"$raw_user_agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36\",\n \"$browser_version\": 129,\n \"$browser_language\": \"en-US\",\n \"$screen_height\": 982,\n \"$screen_width\": 1512,\n \"$viewport_height\": 882,\n \"$viewport_width\": 1086,\n \"$lib\": \"web\",\n \"$lib_version\": \"1.166.0\",\n \"$insert_id\": \"xa7aywslwzwsilxn\",\n \"$time\": 1727960445.033,\n \"distinct_id\": \"UAnHBWWjwZPH8Ky85ZJ4BfYpMD45CvIVohyeJk97Wy9\",\n \"$device_id\": \"018fc4d4-4fc5-7e78-843d-f2d478a82f74\",\n \"$console_log_recording_enabled_server_side\": true,\n \"$session_recording_network_payload_capture\": {\n \"capturePerformance\": {\n \"network_timing\": true,\n \"web_vitals\": true,\n \"web_vitals_allowed_metrics\": null\n },\n \"recordBody\": true,\n \"recordHeaders\": true\n },\n \"$session_recording_canvas_recording\": {\n \"enabled\": false,\n \"fps\": null,\n \"quality\": null\n },\n \"$replay_sample_rate\": null,\n \"$replay_minimum_duration\": 2000,\n \"$initial_person_info\": {\n \"r\": \"$direct\",\n \"u\": \"https://us.posthog.com/project/84498/replay/01922131-3fc4-7c41-bc7a-fc4cd3cb5b12\"\n },\n \"$active_feature_flags\": [\n \"artificial-hog\",\n \"alerts\",\n \"hog-functions\",\n \"heatmaps-ui\",\n \"web-analytics-lcp-score\",\n \"error-tracking\"\n ],\n \"$feature/artificial-hog\": true,\n \"$feature/alerts\": true,\n \"$feature/hog-functions\": true,\n \"$feature/purchase-credits\": false,\n \"$feature/environments\": false,\n \"$feature/test-trend-juraj-1\": \"test\",\n \"$feature/onboarding-dashboard-templates\": \"control\",\n \"$feature_flag_payloads\": {\n \"changelog-notification\": \"{\\n \\\"notificationDate\\\": \\\"2024-08-26\\\", \\n \\\"markdown\\\": \\\"New this week: [React Native session replays](https://posthog.com/changelog/2024#react-native-session-replay-now-in-beta), [link Vitally to your data warehouse](https://posthog.com/changelog/2024#vitally-now-supported-as-a-warehouse-source), and [more](https://posthog.com/changelog)!\\\"\\n}\",\n \"string-payload\": \"true\",\n \"survey-test-target\": \"[1]\",\n \"product-ctas\": \"{\\\"title\\\": \\\"View pricing\\\", \\\"url\\\": \\\"pricing\\\"}\",\n \"available-plans\": \"{\\n \\\"planKeys\\\": {\\n \\\"marketing-website\\\": [\\\"123\\\", \\\"456\\\"],\\n \\\"app\\\": [\\\"789\\\", \\\"101112\\\"]\\n }\\n}\"\n },\n \"$user_id\": \"UAnHBWWjwZPH8Ky85ZJ4BfYpMD45CvIVohyeJk97Wy9\",\n \"is_demo_project\": false,\n \"$groups\": {\n \"project\": \"fc445b88-e2c4-488e-bb52-aa80cd7918c9\",\n \"organization\": \"4dc8564d-bd82-1065-2f40-97f7c50f67cf\",\n \"customer\": \"cus_IK2DWsWVn2ZM16\",\n \"instance\": \"https://us.posthog.com\"\n },\n \"realm\": \"cloud\",\n \"email_service_available\": true,\n \"slack_service_available\": true,\n \"commit_sha\": \"a71b50163b\",\n \"$autocapture_disabled_server_side\": false,\n \"$web_vitals_enabled_server_side\": true,\n \"$web_vitals_allowed_metrics\": null,\n \"$exception_capture_endpoint_suffix\": \"/e/\",\n \"$exception_capture_enabled_server_side\": true,\n \"has_billing_plan\": true,\n \"customer_deactivated\": false,\n \"current_total_amount_usd\": \"0.00\",\n \"custom_limits_usd.surveys\": 500,\n \"percentage_usage.product_analytics\": 0,\n \"current_amount_usd.product_analytics\": \"0.00\",\n \"unit_amount_usd.product_analytics\": null,\n \"usage_limit.product_analytics\": null,\n \"current_usage.product_analytics\": 13531521,\n \"projected_usage.product_analytics\": 225037109,\n \"free_allocation.product_analytics\": 0,\n \"percentage_usage.session_replay\": 0,\n \"current_amount_usd.session_replay\": \"0.00\",\n \"unit_amount_usd.session_replay\": null,\n \"usage_limit.session_replay\": null,\n \"current_usage.session_replay\": 70886,\n \"projected_usage.session_replay\": 1268192,\n \"free_allocation.session_replay\": 0,\n \"percentage_usage.feature_flags\": 0,\n \"current_amount_usd.feature_flags\": \"0.00\",\n \"unit_amount_usd.feature_flags\": null,\n \"usage_limit.feature_flags\": null,\n \"current_usage.feature_flags\": 16287759,\n \"projected_usage.feature_flags\": 461418955,\n \"free_allocation.feature_flags\": 0,\n \"percentage_usage.surveys\": 0,\n \"current_amount_usd.surveys\": \"0.00\",\n \"unit_amount_usd.surveys\": null,\n \"usage_limit.surveys\": null,\n \"current_usage.surveys\": 110,\n \"projected_usage.surveys\": 3047,\n \"free_allocation.surveys\": 0,\n \"percentage_usage.data_warehouse\": 0,\n \"current_amount_usd.data_warehouse\": \"0.00\",\n \"unit_amount_usd.data_warehouse\": null,\n \"usage_limit.data_warehouse\": null,\n \"current_usage.data_warehouse\": 109714956,\n \"projected_usage.data_warehouse\": 2121050491,\n \"free_allocation.data_warehouse\": 0,\n \"percentage_usage.integrations\": 0,\n \"current_amount_usd.integrations\": null,\n \"unit_amount_usd.integrations\": null,\n \"usage_limit.integrations\": 0,\n \"current_usage.integrations\": 0,\n \"projected_usage.integrations\": 0,\n \"free_allocation.integrations\": 0,\n \"percentage_usage.platform_and_support\": 0,\n \"current_amount_usd.platform_and_support\": null,\n \"unit_amount_usd.platform_and_support\": null,\n \"usage_limit.platform_and_support\": 0,\n \"current_usage.platform_and_support\": 0,\n \"projected_usage.platform_and_support\": 0,\n \"free_allocation.platform_and_support\": 0,\n \"billing_period_start\": {\n \"$L\": \"en\",\n \"$d\": {},\n \"$y\": 2024,\n \"$M\": 9,\n \"$D\": 2,\n \"$W\": 3,\n \"$H\": 17,\n \"$m\": 9,\n \"$s\": 56,\n \"$ms\": 0,\n \"$x\": {},\n \"$isDayjsObject\": true\n },\n \"billing_period_end\": {\n \"$L\": \"en\",\n \"$d\": {},\n \"$y\": 2024,\n \"$M\": 10,\n \"$D\": 2,\n \"$W\": 6,\n \"$H\": 16,\n \"$m\": 9,\n \"$s\": 56,\n \"$ms\": 0,\n \"$x\": {},\n \"$isDayjsObject\": true\n },\n \"$surveys_activated\": [\"0190fe51-92a0-0000-4ba3-f85f5f0ef78f\"],\n \"custom_limits_usd.data_warehouse\": 0,\n \"custom_limits_usd.feature_flags\": 0,\n \"custom_limits_usd.session_replay\": 0,\n \"custom_limits_usd.product_analytics\": 0,\n \"$referrer\": \"https://us.posthog.com/project/2/error_tracking\",\n \"$referring_domain\": \"us.posthog.com\",\n \"$exception_type\": \"Error\",\n \"$exception_message\": \"Unexpected usage\\n\\nError: Unexpected usage\\n at n.loadForeignModule (https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js:64:15003)\\n at https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js:64:25112\",\n \"$exception_stack_trace_raw\": \"[{\\\"filename\\\":\\\"https://app-static-prod.posthog.com/static/chunk-3LWGKVZR.js\\\",\\\"function\\\":\\\"r\\\",\\\"in_app\\\":true,\\\"lineno\\\":19,\\\"colno\\\":2044},{\\\"filename\\\":\\\"https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js\\\",\\\"function\\\":\\\"?\\\",\\\"in_app\\\":true,\\\"lineno\\\":3,\\\"colno\\\":12},{\\\"filename\\\":\\\"https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js\\\",\\\"function\\\":\\\"?\\\",\\\"in_app\\\":true,\\\"lineno\\\":64,\\\"colno\\\":25112},{\\\"filename\\\":\\\"https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js\\\",\\\"function\\\":\\\"n.loadForeignModule\\\",\\\"in_app\\\":true,\\\"lineno\\\":64,\\\"colno\\\":15003}]\",\n \"$exception_level\": \"error\",\n \"$exception_source\": \"https://app-static-prod.posthog.com/static/chunk-3LWGKVZR.js\",\n \"$exception_lineno\": 19,\n \"$exception_colno\": 2067,\n \"$exception_personURL\": \"https://us.posthog.com/project/sTMFPsFhdP1Ssg/person/UAnHBWWjwZPH8Ky85ZJ4BfYpMD45CvIVohyeJk97Wy9\",\n \"token\": \"sYTIHGHJKJHGFHJG\",\n \"$session_id\": \"01925255-fc87-771a-8aef-607444f71b4c\",\n \"$window_id\": \"01925273-b871-75c4-bd8f-54a978ead4f9\",\n \"$lib_custom_api_host\": \"https://internal-t.posthog.com\",\n \"$is_identified\": true,\n \"$lib_rate_limit_remaining_tokens\": 98.02,\n \"$set_once\": {\n \"$initial_os\": \"Mac OS X\",\n \"$initial_os_version\": \"10.15.7\",\n \"$initial_browser\": \"Chrome\",\n \"$initial_device_type\": \"Desktop\",\n \"$initial_current_url\": \"https://us.posthog.com/project/84498/replay/01922131-3fc4-7c41-bc7a-fc4cd3cb5b12\",\n \"$initial_pathname\": \"/project/84498/replay/01922131-3fc4-7c41-bc7a-fc4cd3cb5b12\",\n \"$initial_browser_version\": 129,\n \"$initial_referrer\": \"$direct\",\n \"$initial_referring_domain\": \"$direct\",\n \"$initial_geoip_city_name\": \"London\",\n \"$initial_geoip_city_confidence\": null,\n \"$initial_geoip_subdivision_2_name\": null,\n \"$initial_geoip_subdivision_2_code\": null,\n \"$initial_geoip_subdivision_2_confidence\": null,\n \"$initial_geoip_subdivision_1_name\": \"England\",\n \"$initial_geoip_subdivision_1_code\": \"ENG\",\n \"$initial_geoip_subdivision_1_confidence\": null,\n \"$initial_geoip_country_name\": \"United Kingdom\",\n \"$initial_geoip_country_code\": \"GB\",\n \"$initial_geoip_country_confidence\": null,\n \"$initial_geoip_continent_name\": \"Europe\",\n \"$initial_geoip_continent_code\": \"EU\",\n \"$initial_geoip_postal_code\": \"EC4R\",\n \"$initial_geoip_postal_code_confidence\": null,\n \"$initial_geoip_latitude\": 51.5088,\n \"$initial_geoip_longitude\": -0.093,\n \"$initial_geoip_accuracy_radius\": 20,\n \"$initial_geoip_time_zone\": \"Europe/London\",\n \"$initial_host\": \"us.posthog.com\"\n },\n \"$ip\": \"62.23.207.10\",\n \"$set\": {\n \"$os\": \"Mac OS X\",\n \"$os_version\": \"10.15.7\",\n \"$browser\": \"Chrome\",\n \"$device_type\": \"Desktop\",\n \"$current_url\": \"https://us.posthog.com/project/2/feature_flags/50888\",\n \"$pathname\": \"/project/2/feature_flags/50888\",\n \"$browser_version\": 129,\n \"$referrer\": \"https://us.posthog.com/project/2/error_tracking\",\n \"$referring_domain\": \"us.posthog.com\",\n \"$geoip_city_name\": \"London\",\n \"$geoip_city_confidence\": null,\n \"$geoip_subdivision_2_name\": null,\n \"$geoip_subdivision_2_code\": null,\n \"$geoip_subdivision_2_confidence\": null,\n \"$geoip_subdivision_1_name\": \"England\",\n \"$geoip_subdivision_1_code\": \"ENG\",\n \"$geoip_subdivision_1_confidence\": null,\n \"$geoip_country_name\": \"United Kingdom\",\n \"$geoip_country_code\": \"GB\",\n \"$geoip_country_confidence\": null,\n \"$geoip_continent_name\": \"Europe\",\n \"$geoip_continent_code\": \"EU\",\n \"$geoip_postal_code\": \"EC4R\",\n \"$geoip_postal_code_confidence\": null,\n \"$geoip_latitude\": 51.5088,\n \"$geoip_longitude\": -0.093,\n \"$geoip_accuracy_radius\": 20,\n \"$geoip_time_zone\": \"Europe/London\"\n },\n \"$sent_at\": \"2024-10-03T13:00:45.158000+00:00\",\n \"$geoip_city_name\": \"London\",\n \"$geoip_city_confidence\": null,\n \"$geoip_country_name\": \"United Kingdom\",\n \"$geoip_country_code\": \"GB\",\n \"$geoip_country_confidence\": null,\n \"$geoip_continent_name\": \"Europe\",\n \"$geoip_continent_code\": \"EU\",\n \"$geoip_postal_code\": \"EC4R\",\n \"$geoip_postal_code_confidence\": null,\n \"$geoip_latitude\": 51.5088,\n \"$geoip_longitude\": -0.093,\n \"$geoip_accuracy_radius\": 20,\n \"$geoip_time_zone\": \"Europe/London\",\n \"$geoip_subdivision_1_code\": \"ENG\",\n \"$geoip_subdivision_1_name\": \"England\",\n \"$geoip_subdivision_1_confidence\": null,\n \"$lib_version__major\": 1,\n \"$lib_version__minor\": 166,\n \"$lib_version__patch\": 0,\n \"$group_2\": \"fc445b88-e2c4-488e-bb52-aa80cd7918c9\",\n \"$group_0\": \"4dc8564d-bd82-1065-2f40-97f7c50f67cf\",\n \"$group_3\": \"cus_IK2DWsWVn2ZM16\",\n \"$group_1\": \"https://us.posthog.com\",\n \"$exception_fingerprint\": [\n \"Error\",\n \"Unexpected usage\\n\\nError: Unexpected usage\\n at n.loadForeignModule (https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js:64:15003)\\n at https://app-static-prod.posthog.com/static/chunk-PGUQKT6S.js:64:25112\",\n \"r\"\n ]\n }", + "timestamp": "2024-10-03T06:00:45.069000-07:00", + "team_id": 2, + "distinct_id": "UAnHBWWjwZPH8Ky85ZJ4BfYpMD45CvIVohyeJk97Wy9", + "elements_chain": "", + "created_at": "2024-10-03T06:01:25.266000-07:00", + "person_mode": "full" +} diff --git a/rust/cymbal/tests/types.rs b/rust/cymbal/tests/types.rs new file mode 100644 index 0000000000000..302c7697a73b3 --- /dev/null +++ b/rust/cymbal/tests/types.rs @@ -0,0 +1,26 @@ +use std::str::FromStr; + +use common_types::ClickHouseEvent; +use cymbal::symbols::types::PropertyView; +use serde_json::Value; + +#[test] +fn serde_passthrough() { + let raw: &'static str = include_str!("./static/raw_js_stack.json"); + let before: Value = serde_json::from_str(raw).unwrap(); + let raw: ClickHouseEvent = serde_json::from_str(raw).unwrap(); + + let before_properties: Value = serde_json::from_str(raw.properties.as_ref().unwrap()).unwrap(); + let properties_parsed: PropertyView = + serde_json::from_str(raw.properties.as_ref().unwrap()).unwrap(); + + let properties_raw = serde_json::to_string(&properties_parsed).unwrap(); + let after_properties = Value::from_str(&properties_raw).unwrap(); + + assert_eq!(before_properties, after_properties); + + let after = serde_json::to_string(&raw).unwrap(); + let after = Value::from_str(&after).unwrap(); + + assert_eq!(before, after) +}