diff --git a/crashtracker/src/lib.rs b/crashtracker/src/lib.rs index 3eef00a18..fd8989512 100644 --- a/crashtracker/src/lib.rs +++ b/crashtracker/src/lib.rs @@ -49,7 +49,7 @@ mod collector; mod crash_info; #[cfg(all(unix, feature = "receiver"))] mod receiver; -mod rfc5_crash_info; +pub mod rfc5_crash_info; #[cfg(all(unix, any(feature = "collector", feature = "receiver")))] mod shared; diff --git a/crashtracker/src/rfc5_crash_info/builder.rs b/crashtracker/src/rfc5_crash_info/builder.rs new file mode 100644 index 000000000..ed6864e0a --- /dev/null +++ b/crashtracker/src/rfc5_crash_info/builder.rs @@ -0,0 +1,221 @@ +// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use chrono::{DateTime, Utc}; +use error_data::ThreadData; +use stacktrace::StackTrace; +use unknown_value::UnknownValue; +use uuid::Uuid; + +use super::*; + +#[derive(Debug, Default)] +pub struct ErrorDataBuilder { + pub kind: Option, + pub message: Option, + pub stack: Option, + pub threads: Option>, +} + +impl ErrorDataBuilder { + pub fn build(self) -> anyhow::Result<(ErrorData, bool /* incomplete */)> { + let incomplete = self.stack.is_none(); + let is_crash = true; + let kind = self.kind.context("required field 'kind' missing")?; + let message = self.message; + let source_type = SourceType::Crashtracking; + let stack = self.stack.unwrap_or(StackTrace { + format: "Missing Stacktrace".to_string(), + frames: vec![], + }); + let threads = self.threads.unwrap_or_default(); + Ok(( + ErrorData { + is_crash, + kind, + message, + source_type, + stack, + threads, + }, + incomplete, + )) + } + + pub fn new() -> Self { + Self::default() + } + + pub fn with_kind(&mut self, kind: ErrorKind) -> &mut Self { + self.kind = Some(kind); + self + } + + pub fn with_message(&mut self, message: String) -> &mut Self { + self.message = Some(message); + self + } + + pub fn with_stack(&mut self, stack: StackTrace) -> &mut Self { + self.stack = Some(stack); + self + } + + pub fn with_threads(&mut self, threads: Vec) -> &mut Self { + self.threads = Some(threads); + self + } +} + +#[derive(Debug, Default)] +pub struct CrashInfoBuilder { + pub counters: Option>, + pub error: ErrorDataBuilder, + pub files: Option>>, + pub fingerprint: Option, + pub incomplete: Option, + pub log_messages: Option>, + pub metadata: Option, + pub os_info: Option, + pub proc_info: Option, + pub sig_info: Option, + pub span_ids: Option>, + pub timestamp: Option>, + pub trace_ids: Option>, + pub uuid: Option, +} + +impl CrashInfoBuilder { + pub fn build(self) -> anyhow::Result { + let counters = self.counters.unwrap_or_default(); + let data_schema_version = CrashInfo::current_schema_version().to_string(); + let (error, incomplete_error) = self.error.build()?; + let files = self.files.unwrap_or_default(); + let fingerprint = self.fingerprint; + let incomplete = incomplete_error; // TODO + let log_messages = self.log_messages.unwrap_or_default(); + let metadata = self.metadata.unwrap_or_else(Metadata::unknown_value); + let os_info = self.os_info.unwrap_or_else(OsInfo::unknown_value); + let proc_info = self.proc_info; + let sig_info = self.sig_info; + let span_ids = self.span_ids.unwrap_or_default(); + let timestamp = self.timestamp.unwrap_or_else(Utc::now).to_string(); + let trace_ids = self.trace_ids.unwrap_or_default(); + let uuid = self.uuid.unwrap_or_else(|| Uuid::new_v4().to_string()); + Ok(CrashInfo { + counters, + data_schema_version, + error, + files, + fingerprint, + incomplete, + log_messages, + metadata, + os_info, + proc_info, + sig_info, + span_ids, + timestamp, + trace_ids, + uuid, + }) + } + + pub fn new() -> Self { + Self::default() + } + + pub fn with_counters(&mut self, counters: HashMap) -> &mut Self { + self.counters = Some(counters); + self + } + + pub fn with_kind(&mut self, kind: ErrorKind) -> &mut Self { + self.error.with_kind(kind); + self + } + + pub fn with_files(&mut self, files: HashMap>) -> &mut Self { + self.files = Some(files); + self + } + pub fn with_fingerprint(&mut self, fingerprint: String) -> &mut Self { + self.fingerprint = Some(fingerprint); + self + } + pub fn with_incomplete(&mut self, incomplete: bool) -> &mut Self { + self.incomplete = Some(incomplete); + self + } + pub fn with_log_messages(&mut self, log_messages: Vec) -> &mut Self { + self.log_messages = Some(log_messages); + self + } + + pub fn with_message(&mut self, message: String) -> &mut Self { + self.error.with_message(message); + self + } + + pub fn with_metadata(&mut self, metadata: Metadata) -> &mut Self { + self.metadata = Some(metadata); + self + } + + pub fn with_os_info(&mut self, os_info: OsInfo) -> &mut Self { + self.os_info = Some(os_info); + self + } + + pub fn with_os_info_this_machine(&mut self) -> &mut Self { + self.with_os_info(::os_info::get().into()) + } + + pub fn with_proc_info(&mut self, proc_info: ProcInfo) -> &mut Self { + self.proc_info = Some(proc_info); + self + } + + pub fn with_sig_info(&mut self, sig_info: SigInfo) -> &mut Self { + self.sig_info = Some(sig_info); + self + } + + pub fn with_span_ids(&mut self, span_ids: Vec) -> &mut Self { + self.span_ids = Some(span_ids); + self + } + + pub fn with_stack(&mut self, stack: StackTrace) -> &mut Self { + self.error.with_stack(stack); + self + } + + pub fn with_threads(&mut self, threads: Vec) -> &mut Self { + self.error.with_threads(threads); + self + } + + pub fn with_timestamp(&mut self, timestamp: DateTime) -> &mut Self { + self.timestamp = Some(timestamp); + self + } + + pub fn with_timestamp_now(&mut self) -> &mut Self { + self.with_timestamp(Utc::now()) + } + + pub fn with_trace_ids(&mut self, trace_ids: Vec) -> &mut Self { + self.trace_ids = Some(trace_ids); + self + } + + pub fn with_uuid(&mut self, uuid: String) -> &mut Self { + self.uuid = Some(uuid); + self + } + + pub fn with_uuid_random(&mut self) -> &mut Self { + self.with_uuid(Uuid::new_v4().to_string()) + } +} diff --git a/crashtracker/src/rfc5_crash_info/metadata.rs b/crashtracker/src/rfc5_crash_info/metadata.rs index 67836493b..79d819b12 100644 --- a/crashtracker/src/rfc5_crash_info/metadata.rs +++ b/crashtracker/src/rfc5_crash_info/metadata.rs @@ -3,6 +3,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use super::unknown_value::UnknownValue; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct Metadata { pub library_name: String, @@ -13,6 +15,17 @@ pub struct Metadata { pub tags: Vec, } +impl UnknownValue for Metadata { + fn unknown_value() -> Self { + Self { + library_name: "unknown".to_string(), + library_version: "unknown".to_string(), + family: "unknown".to_string(), + tags: vec![], + } + } +} + impl From for Metadata { fn from(value: crate::crash_info::CrashtrackerMetadata) -> Self { let tags = value diff --git a/crashtracker/src/rfc5_crash_info/mod.rs b/crashtracker/src/rfc5_crash_info/mod.rs index e65bc6aeb..cf53dc093 100644 --- a/crashtracker/src/rfc5_crash_info/mod.rs +++ b/crashtracker/src/rfc5_crash_info/mod.rs @@ -1,6 +1,7 @@ // Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 +mod builder; mod error_data; mod metadata; mod os_info; @@ -8,6 +9,9 @@ mod proc_info; mod sig_info; mod spans; mod stacktrace; +mod unknown_value; + +pub use builder::*; use anyhow::Context; use error_data::{thread_data_from_additional_stacktraces, ErrorData, ErrorKind, SourceType}; @@ -35,8 +39,8 @@ pub struct CrashInfo { pub log_messages: Vec, pub metadata: Metadata, pub os_info: OsInfo, - pub proc_info: ProcInfo, - pub sig_info: SigInfo, + pub proc_info: Option, //TODO, update the schema + pub sig_info: Option, //TODO, update the schema #[serde(default, skip_serializing_if = "Vec::is_empty")] pub span_ids: Vec, pub timestamp: String, @@ -45,10 +49,16 @@ pub struct CrashInfo { pub uuid: String, } +impl CrashInfo { + pub fn current_schema_version() -> String { + "1.0".to_string() + } +} + impl From for CrashInfo { fn from(value: crate::crash_info::CrashInfo) -> Self { let counters = value.counters; - let data_schema_version = String::from("1.0"); + let data_schema_version = CrashInfo::current_schema_version(); let error = { let is_crash = true; let kind = ErrorKind::UnixSignal; @@ -71,8 +81,8 @@ impl From for CrashInfo { let log_messages = vec![]; let metadata = value.metadata.unwrap().into(); let os_info = value.os_info.into(); - let proc_info = value.proc_info.unwrap().into(); - let sig_info = value.siginfo.unwrap().into(); + let proc_info = value.proc_info.map(ProcInfo::from); + let sig_info = value.siginfo.map(SigInfo::from); let span_ids = value .span_ids .into_iter() diff --git a/crashtracker/src/rfc5_crash_info/os_info.rs b/crashtracker/src/rfc5_crash_info/os_info.rs index 56dbf5e22..ee9d16fa8 100644 --- a/crashtracker/src/rfc5_crash_info/os_info.rs +++ b/crashtracker/src/rfc5_crash_info/os_info.rs @@ -3,6 +3,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use super::unknown_value::UnknownValue; + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct OsInfo { pub architecture: String, @@ -11,6 +13,12 @@ pub struct OsInfo { pub version: String, } +impl UnknownValue for OsInfo { + fn unknown_value() -> Self { + os_info::Info::unknown().into() + } +} + impl From for OsInfo { fn from(value: os_info::Info) -> Self { let architecture = value.architecture().unwrap_or("unknown").to_string(); diff --git a/crashtracker/src/rfc5_crash_info/stacktrace.rs b/crashtracker/src/rfc5_crash_info/stacktrace.rs index 2cc1f4a10..a8498d5ad 100644 --- a/crashtracker/src/rfc5_crash_info/stacktrace.rs +++ b/crashtracker/src/rfc5_crash_info/stacktrace.rs @@ -8,8 +8,8 @@ use crate::NormalizedAddress; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct StackTrace { - format: String, - frames: Vec, + pub format: String, + pub frames: Vec, } impl From> for StackTrace { diff --git a/crashtracker/src/rfc5_crash_info/unknown_value.rs b/crashtracker/src/rfc5_crash_info/unknown_value.rs new file mode 100644 index 000000000..ba2e42e82 --- /dev/null +++ b/crashtracker/src/rfc5_crash_info/unknown_value.rs @@ -0,0 +1,6 @@ +// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +pub trait UnknownValue { + fn unknown_value() -> Self; +}