Skip to content

Commit

Permalink
[crashtracker] Implement RFC 0005 (#738)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielsn authored Nov 14, 2024
1 parent b57f46a commit 0ded706
Show file tree
Hide file tree
Showing 13 changed files with 1,068 additions and 0 deletions.
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

472 changes: 472 additions & 0 deletions LICENSE-3rdparty.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crashtracker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ tokio = { version = "1.23", features = ["rt", "macros", "io-std", "io-util"] }
http = "0.2"
portable-atomic = { version = "1.6.0", features = ["serde"] }
rand = "0.8.5"
schemars = "0.8.21"

[dev-dependencies]
tempfile = { version = "3.3" }
3 changes: 3 additions & 0 deletions crashtracker/src/crash_info/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ impl CrashInfo {
let path = ddcommon::decode_uri_path_in_authority(&endpoint.url)
.context("crash output file was not correctly formatted")?;
self.to_file(&path)?;
let new_path = path.with_extension("rfc5.json");
let rfc5: crate::rfc5_crash_info::CrashInfo = self.clone().into();
rfc5.to_file(&new_path)?;
}
}

Expand Down
1 change: 1 addition & 0 deletions crashtracker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ mod collector;
mod crash_info;
#[cfg(all(unix, feature = "receiver"))]
mod receiver;
mod rfc5_crash_info;
#[cfg(all(unix, any(feature = "collector", feature = "receiver")))]
mod shared;

Expand Down
63 changes: 63 additions & 0 deletions crashtracker/src/rfc5_crash_info/error_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
use super::stacktrace::StackTrace;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ErrorData {
pub is_crash: bool,
pub kind: ErrorKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
pub source_type: SourceType,
pub stack: StackTrace,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub threads: Vec<ThreadData>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum SourceType {
Crashtracking,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum ErrorKind {
Panic,
UnhandledException,
UnixSignal,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ThreadData {
pub crashed: bool,
pub name: String,
pub stack: StackTrace,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
}

impl From<(String, Vec<crate::StackFrame>)> for ThreadData {
fn from(value: (String, Vec<crate::StackFrame>)) -> Self {
let crashed = false; // Currently, only .Net uses this, and I believe they don't put the crashing thread here
let name = value.0;
let stack = value.1.into();
let state = None;
Self {
crashed,
name,
stack,
state,
}
}
}

pub fn thread_data_from_additional_stacktraces(
additional_stacktraces: HashMap<String, Vec<crate::StackFrame>>,
) -> Vec<ThreadData> {
additional_stacktraces
.into_iter()
.map(|x| x.into())
.collect()
}
30 changes: 30 additions & 0 deletions crashtracker/src/rfc5_crash_info/metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Metadata {
pub library_name: String,
pub library_version: String,
pub family: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
/// A list of "key:value" tuples.
pub tags: Vec<String>,
}

impl From<crate::crash_info::CrashtrackerMetadata> for Metadata {
fn from(value: crate::crash_info::CrashtrackerMetadata) -> Self {
let tags = value
.tags
.into_iter()
.map(|t: ddcommon::tag::Tag| t.to_string())
.collect();
Self {
library_name: value.library_name,
library_version: value.library_version,
family: value.family,
tags,
}
}
}
138 changes: 138 additions & 0 deletions crashtracker/src/rfc5_crash_info/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0

mod error_data;
mod metadata;
mod os_info;
mod proc_info;
mod sig_info;
mod spans;
mod stacktrace;

use anyhow::Context;
use error_data::{thread_data_from_additional_stacktraces, ErrorData, ErrorKind, SourceType};
use metadata::Metadata;
use os_info::OsInfo;
use proc_info::ProcInfo;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use sig_info::SigInfo;
use spans::Span;
use std::{collections::HashMap, fs::File, path::Path};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CrashInfo {
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub counters: HashMap<String, i64>,
pub data_schema_version: String,
pub error: ErrorData,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub files: HashMap<String, Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fingerprint: Option<String>,
pub incomplete: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub log_messages: Vec<String>,
pub metadata: Metadata,
pub os_info: OsInfo,
pub proc_info: ProcInfo,
pub sig_info: SigInfo,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub span_ids: Vec<Span>,
pub timestamp: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub trace_ids: Vec<Span>,
pub uuid: String,
}

impl From<crate::crash_info::CrashInfo> for CrashInfo {
fn from(value: crate::crash_info::CrashInfo) -> Self {
let counters = value.counters;
let data_schema_version = String::from("1.0");
let error = {
let is_crash = true;
let kind = ErrorKind::UnixSignal;
let message = None;
let source_type = SourceType::Crashtracking;
let stack = value.stacktrace.into();
let threads = thread_data_from_additional_stacktraces(value.additional_stacktraces);
ErrorData {
is_crash,
kind,
message,
source_type,
stack,
threads,
}
};
let files = value.files;
let fingerprint = None;
let incomplete = value.incomplete;
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 span_ids = value
.span_ids
.into_iter()
.map(|s| Span {
id: s.to_string(),
thread_name: None,
})
.collect();
let trace_ids = value
.trace_ids
.into_iter()
.map(|s| Span {
id: s.to_string(),
thread_name: None,
})
.collect();
let timestamp = value.timestamp.unwrap().to_string();
let uuid = value.uuid.to_string();
Self {
counters,
data_schema_version,
error,
files,
fingerprint,
incomplete,
log_messages,
metadata,
os_info,
proc_info,
sig_info,
span_ids,
trace_ids,
timestamp,
uuid,
}
}
}

impl CrashInfo {
/// Emit the CrashInfo as structured json in file `path`.
pub fn to_file(&self, path: &Path) -> anyhow::Result<()> {
let file = File::options()
.create(true)
.append(true)
.open(path)
.with_context(|| format!("Failed to create {}", path.display()))?;
serde_json::to_writer_pretty(file, self)
.with_context(|| format!("Failed to write json to {}", path.display()))?;
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
#[ignore]
#[test]
/// Utility function to print the schema.
fn print_schema() {
let schema = schemars::schema_for!(CrashInfo);
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
}
}
27 changes: 27 additions & 0 deletions crashtracker/src/rfc5_crash_info/os_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct OsInfo {
pub architecture: String,
pub bitness: String,
pub os_type: String,
pub version: String,
}

impl From<os_info::Info> for OsInfo {
fn from(value: os_info::Info) -> Self {
let architecture = value.architecture().unwrap_or("unknown").to_string();
let bitness = value.bitness().to_string();
let os_type = value.os_type().to_string();
let version = value.version().to_string();
Self {
architecture,
bitness,
os_type,
version,
}
}
}
15 changes: 15 additions & 0 deletions crashtracker/src/rfc5_crash_info/proc_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ProcInfo {
pid: u32,
}

impl From<crate::crash_info::ProcessInfo> for ProcInfo {
fn from(value: crate::crash_info::ProcessInfo) -> Self {
Self { pid: value.pid }
}
}
Loading

0 comments on commit 0ded706

Please sign in to comment.