Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rust/common/symbol_data/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::string::FromUtf8Error;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum Error {
#[error("Wrong version: {0}, expected {1}")]
WrongVersion(u32, u32),
Expand Down
76 changes: 71 additions & 5 deletions rust/cymbal/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub enum UnhandledError {
// NOTE - these are serialized and deserialized, so that when we fail to get a symbol set from
// some provider (e.g. we fail to look up a sourcemap), we can return the correct error in the future
// without hitting their infra again (by storing it in PG).
#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum FrameError {
#[error(transparent)]
JavaScript(#[from] JsResolveErr),
Expand All @@ -78,7 +78,7 @@ pub enum FrameError {
MissingChunkIdData(String),
}

#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum JsResolveErr {
#[error("This frame had no source url or chunk id")]
NoUrlOrChunkId,
Expand Down Expand Up @@ -130,7 +130,7 @@ pub enum JsResolveErr {
NoSourcemapUploaded(String),
}

#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum HermesError {
#[error("Data error: {0}")]
DataError(#[from] SymbolDataError),
Expand All @@ -144,7 +144,7 @@ pub enum HermesError {
NoTokenForColumn(u32, String),
}

#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum ProguardError {
#[error("Data error: {0}")]
DataError(#[from] SymbolDataError),
Expand All @@ -164,7 +164,7 @@ pub enum ProguardError {
InvalidClass,
}

#[derive(Debug, Error, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Error, Serialize, Deserialize)]
pub enum AppleError {
#[error("Data error: {0}")]
DataError(#[from] SymbolDataError),
Expand Down Expand Up @@ -206,6 +206,72 @@ pub enum EventError {
FilteredByTeamId,
}

impl JsResolveErr {
pub fn metric_reason(&self) -> &'static str {
match self {
Self::NoUrlOrChunkId | Self::NoSourceUrl => "no_reference",
Self::NoSourcemap(_) | Self::NoSourcemapUploaded(_) => "no_symbol_set",
Self::TokenNotFound(..) => "symbol_not_found",
Self::Timeout(_)
| Self::HttpStatus(..)
| Self::NetworkError(_)
| Self::RedirectError(_) => "network_error",
Self::InvalidSourceMap(_)
| Self::InvalidSourceUrl(_)
| Self::InvalidSourceMapHeader(_)
| Self::InvalidSourceMapUrl(_)
| Self::InvalidDataUrl(..)
| Self::JSDataError(_)
| Self::InvalidSourceAndMap => "invalid_data",
}
}
}

impl HermesError {
pub fn metric_reason(&self) -> &'static str {
match self {
Self::NoChunkId => "no_reference",
Self::NoSourcemapUploaded(_) => "no_symbol_set",
Self::NoTokenForColumn(..) => "symbol_not_found",
Self::DataError(_) | Self::InvalidMap(_) => "invalid_data",
}
}
}

impl ProguardError {
pub fn metric_reason(&self) -> &'static str {
match self {
Self::NoMapId | Self::NoModuleProvided => "no_reference",
Self::MissingMap(_) => "no_symbol_set",
Self::NoOriginalFrames | Self::MissingClass => "symbol_not_found",
Self::DataError(_) | Self::InvalidMapping | Self::InvalidClass => "invalid_data",
}
}
}

impl AppleError {
pub fn metric_reason(&self) -> &'static str {
match self {
Self::NoDebugId | Self::NoMatchingDebugImage => "no_reference",
Self::MissingDsym(_) => "no_symbol_set",
Self::SymbolNotFound(_) => "symbol_not_found",
Self::DataError(_) | Self::InvalidAddress(_) | Self::ParseError(_) => "invalid_data",
}
}
}

impl FrameError {
pub fn metric_reason(&self) -> &'static str {
match self {
Self::JavaScript(e) => e.metric_reason(),
Self::Hermes(e) => e.metric_reason(),
Self::Proguard(e) => e.metric_reason(),
Self::Apple(e) => e.metric_reason(),
Self::MissingChunkIdData(_) => "no_symbol_set",
}
}
}

impl From<JsResolveErr> for ResolveError {
fn from(e: JsResolveErr) -> Self {
FrameError::JavaScript(e).into()
Expand Down
8 changes: 8 additions & 0 deletions rust/cymbal/src/fingerprinting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ mod test {
resolved_name: Some("bar".to_string()),
resolved: true,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand All @@ -163,6 +164,7 @@ mod test {
resolved_name: Some("baz".to_string()),
resolved: true,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand All @@ -184,6 +186,7 @@ mod test {
resolved_name: None,
resolved: false,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand Down Expand Up @@ -236,6 +239,7 @@ mod test {
resolved_name: Some("bar".to_string()),
resolved: false,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand All @@ -255,6 +259,7 @@ mod test {
resolved_name: Some("baz".to_string()),
resolved: false,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand All @@ -274,6 +279,7 @@ mod test {
resolved_name: None,
resolved: false,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand Down Expand Up @@ -321,6 +327,7 @@ mod test {
resolved_name: Some("bar".to_string()),
resolved: false,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand All @@ -341,6 +348,7 @@ mod test {
resolved_name: Some("baz".to_string()),
resolved: false,
resolve_failure: None,

lang: "javascript".to_string(),
junk_drawer: None,
code_variables: None,
Expand Down
61 changes: 56 additions & 5 deletions rust/cymbal/src/frames/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
error::UnhandledError,
error::{FrameError, UnhandledError},
fingerprinting::{FingerprintBuilder, FingerprintComponent, FingerprintRecordPart},
langs::{
apple::{AppleDebugImage, RawAppleFrame},
Expand All @@ -20,7 +20,7 @@ use crate::{
python::RawPythonFrame,
ruby::RawRubyFrame,
},
metric_consts::{LEGACY_JS_FRAME_RESOLVED, PER_FRAME_TIME},
metric_consts::{FRAME_NOT_RESOLVED, FRAME_RESOLVED, LEGACY_JS_FRAME_RESOLVED, PER_FRAME_TIME},
sanitize_string,
symbol_store::Catalog,
};
Expand Down Expand Up @@ -108,6 +108,29 @@ impl RawFrame {
.label("lang", lang_tag)
.fin();

if let Ok(frames) = &res {
for frame in frames {
if frame.resolved {
metrics::counter!(FRAME_RESOLVED, "lang" => lang_tag).increment(1);
} else if let Some(err) = &frame.resolve_failure {
let reason = err.metric_reason();
match reason {
"network_error" | "invalid_data" | "symbol_not_found" => {
tracing::warn!(lang = lang_tag, reason = reason, error = %err, "frame resolution failed");
}
_ => {
tracing::debug!(lang = lang_tag, reason = reason, error = %err, "frame resolution failed");
}
}
metrics::counter!(FRAME_NOT_RESOLVED, "lang" => lang_tag, "reason" => reason)
.increment(1);
} else {
metrics::counter!(FRAME_NOT_RESOLVED, "lang" => lang_tag, "reason" => "unknown")
.increment(1);
}
}
}

res
}

Expand Down Expand Up @@ -176,8 +199,13 @@ pub struct Frame {
pub resolved_name: Option<String>, // The name of the function, after symbolification
pub lang: String, // The language of the frame. Always known (I guess?)
pub resolved: bool, // Did we manage to resolve the frame?
#[serde(skip_serializing_if = "Option::is_none")]
pub resolve_failure: Option<String>, // If we failed to resolve the frame, why?
#[serde(
serialize_with = "frame_error_serde::serialize",
skip_deserializing,
skip_serializing_if = "Option::is_none",
default
)]
pub resolve_failure: Option<FrameError>, // If we failed to resolve the frame, why?

#[serde(default)] // Defaults to false
pub synthetic: bool, // Some SDKs construct stack traces, or partially reconstruct them. This flag indicates whether the frame is synthetic or not.
Expand Down Expand Up @@ -344,7 +372,11 @@ impl std::fmt::Display for Frame {
writeln!(
f,
" resolve_failure: {}",
self.resolve_failure.as_deref().unwrap_or("no failure")
self.resolve_failure
.as_ref()
.map(|e| e.to_string())
.as_deref()
.unwrap_or("no failure")
)?;

// Context
Expand Down Expand Up @@ -397,6 +429,25 @@ impl From<Frame> for FrameData {
}
}

mod frame_error_serde {
use super::FrameError;
use serde::Serializer;

pub fn serialize<S>(value: &Option<FrameError>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// skip_serializing_if = "Option::is_none" guarantees value is Some here,
// but we match exhaustively to satisfy the type checker.
match value {
Some(err) => serializer.serialize_str(&err.to_string()),
None => {
unreachable!("skip_serializing_if = Option::is_none prevents None reaching here")
}
}
}
}

fn to_vec<T, E>(item: Result<T, E>) -> Result<Vec<T>, E> {
item.map(|t| vec![t])
}
Expand Down
4 changes: 3 additions & 1 deletion rust/cymbal/src/langs/apple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ impl RawAppleFrame {
lang: lang_from_filename(symbol_info.filename.as_deref()).to_string(),
resolved: true,
resolve_failure: None,

junk_drawer: None,
release: None,
synthetic: self.meta.synthetic,
Expand Down Expand Up @@ -366,7 +367,7 @@ impl RawAppleFrame {
resolved_name,
lang: lang_from_filename(self.filename.as_deref()).to_string(),
resolved: false,
resolve_failure: Some(err.to_string()),
resolve_failure: Some(FrameError::from(err)),
junk_drawer: None,
release: None,
synthetic: self.meta.synthetic,
Expand Down Expand Up @@ -435,6 +436,7 @@ impl From<&RawAppleFrame> for Frame {
lang: lang_from_filename(raw.filename.as_deref()).to_string(),
resolved: raw.function.is_some(),
resolve_failure: None,

junk_drawer: None,
release: None,
synthetic: raw.meta.synthetic,
Expand Down
1 change: 1 addition & 0 deletions rust/cymbal/src/langs/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ impl From<&CustomFrame> for Frame {
lang: value.lang.clone(),
resolved: value.resolved,
resolve_failure: None,

junk_drawer: None,
context: value.get_context(),
release: None,
Expand Down
1 change: 1 addition & 0 deletions rust/cymbal/src/langs/dart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ impl From<&RawDartFrame> for Frame {
lang: "dart".to_string(),
resolved: true,
resolve_failure: None,

junk_drawer: None,
release: None,
synthetic: raw.meta.synthetic,
Expand Down
1 change: 1 addition & 0 deletions rust/cymbal/src/langs/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ impl From<&RawGoFrame> for Frame {
lang: "go".to_string(),
resolved: true,
resolve_failure: None,

synthetic: frame.meta.synthetic,
junk_drawer: None,
context: None,
Expand Down
7 changes: 3 additions & 4 deletions rust/cymbal/src/langs/hermes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use crate::{
utils::{add_raw_to_junk, get_token_context},
CommonFrameMetadata,
},
metric_consts::FRAME_NOT_RESOLVED,
sanitize_string,
symbol_store::{chunk_id::OrChunkId, hermesmap::ParsedHermesMap, SymbolCatalog},
};
Expand Down Expand Up @@ -134,7 +133,7 @@ impl From<(&RawHermesFrame, HermesError)> for Frame {
resolved_name: None,
lang: "javascript".to_string(),
resolved: false,
resolve_failure: Some(err.to_string()),
resolve_failure: Some(FrameError::from(err)),
synthetic: frame.meta.synthetic,
junk_drawer: None,
code_variables: None,
Expand Down Expand Up @@ -169,6 +168,7 @@ impl From<(&RawHermesFrame, Token<'_>, Option<String>)> for Frame {
lang: "javascript".to_string(),
resolved: true,
resolve_failure: None,

synthetic: frame.meta.synthetic,
junk_drawer: None,
code_variables: None,
Expand All @@ -188,8 +188,6 @@ impl From<(&RawHermesFrame, Token<'_>, Option<String>)> for Frame {
// probably a native function or something else weird
impl From<&RawHermesFrame> for Frame {
fn from(raw_frame: &RawHermesFrame) -> Self {
metrics::counter!(FRAME_NOT_RESOLVED, "lang" => "hermes").increment(1);

// If this is a source_url: <anonymous> frame, we always assume it's not in_app
let is_anon = raw_frame.source.eq("<anonymous>");

Expand All @@ -206,6 +204,7 @@ impl From<&RawHermesFrame> for Frame {
lang: "javascript".to_string(),
resolved: true, // Without location information, we're assuming this is not minified
resolve_failure: None,

junk_drawer: None,
code_variables: None,
context: None,
Expand Down
Loading
Loading