Skip to main content

libdd_crashtracker/crash_info/
builder.rs

1// Copyright 2024-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::runtime_callback::RuntimeStack;
5
6use chrono::{DateTime, Utc};
7use error_data::ThreadData;
8use stacktrace::StackTrace;
9use std::io::{BufRead, BufReader};
10use unknown_value::UnknownValue;
11use uuid::Uuid;
12
13use super::*;
14
15#[derive(Debug, Default, PartialEq)]
16pub struct ErrorDataBuilder {
17    pub kind: Option<ErrorKind>,
18    pub message: Option<String>,
19    pub stack: Option<StackTrace>,
20    pub threads: Option<Vec<ThreadData>>,
21}
22
23impl ErrorDataBuilder {
24    pub fn build(self) -> anyhow::Result<(ErrorData, bool /* incomplete */)> {
25        let incomplete = self.stack.is_none();
26        let is_crash = true;
27        let kind = self.kind.context("required field 'kind' missing")?;
28        let message = self.message;
29        let source_type = SourceType::Crashtracking;
30        let stack = self.stack.unwrap_or_else(StackTrace::missing);
31        let threads = self.threads.unwrap_or_default();
32        Ok((
33            ErrorData {
34                is_crash,
35                kind,
36                message,
37                source_type,
38                stack,
39                threads,
40            },
41            incomplete,
42        ))
43    }
44
45    pub fn new() -> Self {
46        Self::default()
47    }
48
49    pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<&mut Self> {
50        self.kind = Some(kind);
51        Ok(self)
52    }
53
54    pub fn with_message(&mut self, message: String) -> anyhow::Result<&mut Self> {
55        self.message = Some(message);
56        Ok(self)
57    }
58
59    pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<&mut Self> {
60        self.stack = Some(stack);
61        Ok(self)
62    }
63
64    pub fn with_stack_frame(
65        &mut self,
66        frame: StackFrame,
67        incomplete: bool,
68    ) -> anyhow::Result<&mut Self> {
69        if let Some(stack) = &mut self.stack {
70            stack.push_frame(frame, incomplete)?;
71        } else {
72            self.stack = Some(StackTrace::from_frames(vec![frame], incomplete));
73        }
74        Ok(self)
75    }
76
77    pub fn with_stack_set_complete(&mut self) -> anyhow::Result<&mut Self> {
78        if let Some(stack) = &mut self.stack {
79            stack.set_complete()?;
80        } else {
81            // With https://github.com/DataDog/libdatadog/pull/1076 it happens that stack trace are
82            // empty on musl based Linux (Alpine) because stack unwinding may not be able to unwind
83            // passed the signal handler. This by-passing for musl is temporary and needs a fix.
84            #[cfg(target_env = "musl")]
85            return Ok(self);
86            #[cfg(not(target_env = "musl"))]
87            anyhow::bail!("Can't set non-existant stack complete");
88        }
89        Ok(self)
90    }
91
92    pub fn with_threads(&mut self, threads: Vec<ThreadData>) -> anyhow::Result<&mut Self> {
93        self.threads = Some(threads);
94        Ok(self)
95    }
96}
97
98#[derive(Debug, PartialEq)]
99pub struct CrashInfoBuilder {
100    pub counters: Option<HashMap<String, i64>>,
101    pub error: ErrorDataBuilder,
102    pub experimental: Option<Experimental>,
103    pub files: Option<HashMap<String, Vec<String>>>,
104    pub fingerprint: Option<String>,
105    pub incomplete: Option<bool>,
106    pub log_messages: Option<Vec<String>>,
107    pub metadata: Option<Metadata>,
108    pub os_info: Option<OsInfo>,
109    pub proc_info: Option<ProcInfo>,
110    pub sig_info: Option<SigInfo>,
111    pub span_ids: Option<Vec<Span>>,
112    pub timestamp: Option<DateTime<Utc>>,
113    pub trace_ids: Option<Vec<Span>>,
114    pub uuid: Uuid,
115}
116
117impl Default for CrashInfoBuilder {
118    fn default() -> Self {
119        Self {
120            counters: None,
121            error: ErrorDataBuilder::default(),
122            experimental: None,
123            files: None,
124            fingerprint: None,
125            incomplete: None,
126            log_messages: None,
127            metadata: None,
128            os_info: None,
129            proc_info: None,
130            sig_info: None,
131            span_ids: None,
132            timestamp: None,
133            trace_ids: None,
134            uuid: Uuid::new_v4(),
135        }
136    }
137}
138
139impl CrashInfoBuilder {
140    pub fn build(self) -> anyhow::Result<CrashInfo> {
141        let counters = self.counters.unwrap_or_default();
142        let data_schema_version = CrashInfo::current_schema_version().to_string();
143        let (error, incomplete_error) = self.error.build()?;
144        let experimental = self.experimental;
145        let files = self.files.unwrap_or_default();
146        let fingerprint = self.fingerprint;
147        let incomplete = incomplete_error || self.incomplete.unwrap_or(false);
148        let log_messages = self.log_messages.unwrap_or_default();
149        let metadata = self.metadata.unwrap_or_else(Metadata::unknown_value);
150        let os_info = self.os_info.unwrap_or_else(OsInfo::unknown_value);
151        let proc_info = self.proc_info;
152        let sig_info = self.sig_info;
153        let span_ids = self.span_ids.unwrap_or_default();
154        let timestamp = self.timestamp.unwrap_or_else(Utc::now).to_string();
155        let trace_ids = self.trace_ids.unwrap_or_default();
156        let uuid = self.uuid;
157        Ok(CrashInfo {
158            counters,
159            data_schema_version,
160            error,
161            experimental,
162            files,
163            fingerprint,
164            incomplete,
165            log_messages,
166            metadata,
167            os_info,
168            proc_info,
169            sig_info,
170            span_ids,
171            timestamp,
172            trace_ids,
173            uuid: uuid.to_string(),
174        })
175    }
176
177    pub fn has_data(&self) -> bool {
178        *self != Self::default()
179    }
180
181    pub fn new() -> Self {
182        Self::default()
183    }
184
185    /// Inserts the given counter to the current set of counters in the builder.
186    pub fn with_counter(&mut self, name: String, value: i64) -> anyhow::Result<&mut Self> {
187        anyhow::ensure!(!name.is_empty(), "Empty counter name not allowed");
188        if let Some(ref mut counters) = &mut self.counters {
189            counters.insert(name, value);
190        } else {
191            self.counters = Some(HashMap::from([(name, value)]));
192        }
193        Ok(self)
194    }
195
196    pub fn with_counters(&mut self, counters: HashMap<String, i64>) -> anyhow::Result<&mut Self> {
197        self.counters = Some(counters);
198        Ok(self)
199    }
200
201    pub fn with_experimental_additional_tags(
202        &mut self,
203        additional_tags: Vec<String>,
204    ) -> anyhow::Result<&mut Self> {
205        if let Some(experimental) = &mut self.experimental {
206            experimental.additional_tags = additional_tags;
207        } else {
208            self.experimental = Some(Experimental::new().with_additional_tags(additional_tags));
209        }
210        Ok(self)
211    }
212
213    pub fn with_experimental_ucontext(&mut self, ucontext: String) -> anyhow::Result<&mut Self> {
214        if let Some(experimental) = &mut self.experimental {
215            experimental.ucontext = Some(ucontext);
216        } else {
217            self.experimental = Some(Experimental::new().with_ucontext(ucontext));
218        }
219        Ok(self)
220    }
221
222    pub fn with_experimental_runtime_stack(
223        &mut self,
224        runtime_stack: RuntimeStack,
225    ) -> anyhow::Result<&mut Self> {
226        if let Some(experimental) = &mut self.experimental {
227            experimental.runtime_stack = Some(runtime_stack);
228        } else {
229            self.experimental = Some(Experimental::new().with_runtime_stack(runtime_stack));
230        }
231        Ok(self)
232    }
233
234    pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<&mut Self> {
235        self.error.with_kind(kind)?;
236        Ok(self)
237    }
238
239    pub fn with_file(&mut self, filename: String) -> anyhow::Result<&mut Self> {
240        let file = File::open(&filename).with_context(|| format!("filename: {filename}"))?;
241        let lines: std::io::Result<Vec<_>> = BufReader::new(file).lines().collect();
242        self.with_file_and_contents(filename, lines?)
243    }
244
245    /// Appends the given file to the current set of files in the builder.
246    pub fn with_file_and_contents(
247        &mut self,
248        filename: String,
249        contents: Vec<String>,
250    ) -> anyhow::Result<&mut Self> {
251        if let Some(ref mut files) = &mut self.files {
252            files.insert(filename, contents);
253        } else {
254            self.files = Some(HashMap::from([(filename, contents)]));
255        }
256        Ok(self)
257    }
258
259    /// Sets the current set of files in the builder.
260    pub fn with_files(&mut self, files: HashMap<String, Vec<String>>) -> anyhow::Result<&mut Self> {
261        self.files = Some(files);
262        Ok(self)
263    }
264
265    pub fn with_fingerprint(&mut self, fingerprint: String) -> anyhow::Result<&mut Self> {
266        anyhow::ensure!(!fingerprint.is_empty(), "Expect non-empty fingerprint");
267        self.fingerprint = Some(fingerprint);
268        Ok(self)
269    }
270
271    pub fn with_incomplete(&mut self, incomplete: bool) -> anyhow::Result<&mut Self> {
272        self.incomplete = Some(incomplete);
273        Ok(self)
274    }
275
276    /// Appends the given message to the current set of messages in the builder.
277    pub fn with_log_message(
278        &mut self,
279        message: String,
280        also_print: bool,
281    ) -> anyhow::Result<&mut Self> {
282        if also_print {
283            eprintln!("{message}");
284        }
285
286        if let Some(ref mut messages) = &mut self.log_messages {
287            messages.push(message);
288        } else {
289            self.log_messages = Some(vec![message]);
290        }
291        Ok(self)
292    }
293
294    pub fn with_log_messages(&mut self, log_messages: Vec<String>) -> anyhow::Result<&mut Self> {
295        self.log_messages = Some(log_messages);
296        Ok(self)
297    }
298
299    pub fn with_message(&mut self, message: String) -> anyhow::Result<&mut Self> {
300        self.error.with_message(message)?;
301        Ok(self)
302    }
303
304    pub fn with_metadata(&mut self, metadata: Metadata) -> anyhow::Result<&mut Self> {
305        self.metadata = Some(metadata);
306        Ok(self)
307    }
308
309    pub fn with_os_info(&mut self, os_info: OsInfo) -> anyhow::Result<&mut Self> {
310        self.os_info = Some(os_info);
311        Ok(self)
312    }
313
314    pub fn with_os_info_this_machine(&mut self) -> anyhow::Result<&mut Self> {
315        self.with_os_info(::os_info::get().into())
316    }
317
318    pub fn with_proc_info(&mut self, proc_info: ProcInfo) -> anyhow::Result<&mut Self> {
319        self.proc_info = Some(proc_info);
320        Ok(self)
321    }
322
323    pub fn with_sig_info(&mut self, sig_info: SigInfo) -> anyhow::Result<&mut Self> {
324        self.sig_info = Some(sig_info);
325        Ok(self)
326    }
327
328    pub fn with_span_id(&mut self, span_id: Span) -> anyhow::Result<&mut Self> {
329        if let Some(ref mut span_ids) = &mut self.span_ids {
330            span_ids.push(span_id);
331        } else {
332            self.span_ids = Some(vec![span_id]);
333        }
334        Ok(self)
335    }
336
337    pub fn with_span_ids(&mut self, span_ids: Vec<Span>) -> anyhow::Result<&mut Self> {
338        self.span_ids = Some(span_ids);
339        Ok(self)
340    }
341
342    pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<&mut Self> {
343        self.error.with_stack(stack)?;
344        Ok(self)
345    }
346
347    pub fn with_stack_frame(
348        &mut self,
349        frame: StackFrame,
350        incomplete: bool,
351    ) -> anyhow::Result<&mut Self> {
352        self.error.with_stack_frame(frame, incomplete)?;
353        Ok(self)
354    }
355
356    pub fn with_stack_set_complete(&mut self) -> anyhow::Result<&mut Self> {
357        self.error.with_stack_set_complete()?;
358        Ok(self)
359    }
360
361    pub fn with_thread(&mut self, thread: ThreadData) -> anyhow::Result<&mut Self> {
362        if let Some(ref mut threads) = &mut self.error.threads {
363            threads.push(thread);
364        } else {
365            self.error.threads = Some(vec![thread]);
366        }
367        Ok(self)
368    }
369
370    pub fn with_threads(&mut self, threads: Vec<ThreadData>) -> anyhow::Result<&mut Self> {
371        self.error.with_threads(threads)?;
372        Ok(self)
373    }
374
375    pub fn with_timestamp(&mut self, timestamp: DateTime<Utc>) -> anyhow::Result<&mut Self> {
376        self.timestamp = Some(timestamp);
377        Ok(self)
378    }
379
380    pub fn with_timestamp_now(&mut self) -> anyhow::Result<&mut Self> {
381        self.with_timestamp(Utc::now())
382    }
383
384    pub fn with_trace_id(&mut self, trace_id: Span) -> anyhow::Result<&mut Self> {
385        if let Some(ref mut trace_ids) = &mut self.trace_ids {
386            trace_ids.push(trace_id);
387        } else {
388            self.trace_ids = Some(vec![trace_id]);
389        }
390        Ok(self)
391    }
392
393    pub fn with_trace_ids(&mut self, trace_ids: Vec<Span>) -> anyhow::Result<&mut Self> {
394        self.trace_ids = Some(trace_ids);
395        Ok(self)
396    }
397
398    /// This method requires that the builder has a UUID and metadata set.
399    /// Siginfo is optional for platforms that don't support it (like Windows)
400    pub fn build_crash_ping(&self) -> anyhow::Result<CrashPing> {
401        let sig_info = self.sig_info.clone();
402        let metadata = self.metadata.clone().context("metadata is required")?;
403
404        let mut builder = CrashPingBuilder::new(self.uuid).with_metadata(metadata);
405        if let Some(sig_info) = sig_info {
406            builder = builder.with_sig_info(sig_info);
407        }
408        builder.build()
409    }
410
411    pub fn is_ping_ready(&self) -> bool {
412        // On Unix platforms, wait for both metadata and siginfo
413        // On Windows, siginfo is not available, so only wait for metadata
414        #[cfg(unix)]
415        {
416            self.metadata.is_some() && self.sig_info.is_some()
417        }
418        #[cfg(windows)]
419        {
420            self.metadata.is_some()
421        }
422    }
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428    use crate::crash_info::test_utils::TestInstance;
429
430    #[test]
431    fn test_crash_info_builder_to_crash_ping() {
432        let sig_info = SigInfo::test_instance(42);
433        let metadata = Metadata::test_instance(1);
434
435        let mut crash_info_builder = CrashInfoBuilder::new();
436        crash_info_builder.with_sig_info(sig_info.clone()).unwrap();
437        crash_info_builder.with_metadata(metadata.clone()).unwrap();
438        crash_info_builder.with_kind(ErrorKind::Panic).unwrap();
439
440        let crash_ping = crash_info_builder.build_crash_ping().unwrap();
441
442        assert!(!crash_ping.crash_uuid().is_empty());
443        assert!(Uuid::parse_str(crash_ping.crash_uuid()).is_ok());
444        assert_eq!(crash_ping.siginfo(), Some(&sig_info));
445        assert_eq!(crash_ping.metadata(), &metadata);
446        assert!(crash_ping.message().contains("crash processing started"));
447    }
448}