libdd_crashtracker/crash_info/
builder.rs1use 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 thread_name: Option<String>,
20 pub stack: Option<StackTrace>,
21 pub threads: Option<Vec<ThreadData>>,
22}
23
24impl ErrorDataBuilder {
25 pub fn build(self) -> anyhow::Result<(ErrorData, bool )> {
26 let incomplete = self.stack.is_none();
27 let is_crash = true;
28 let kind = self.kind.context("required field 'kind' missing")?;
29 let message = self.message;
30 let thread_name = self.thread_name;
31 let source_type = SourceType::Crashtracking;
32 let stack = self.stack.unwrap_or_else(StackTrace::missing);
33 let threads = self.threads;
34 Ok((
35 ErrorData {
36 is_crash,
37 kind,
38 message,
39 thread_name,
40 source_type,
41 stack,
42 threads,
43 },
44 incomplete,
45 ))
46 }
47
48 pub fn new() -> Self {
49 Self::default()
50 }
51
52 pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<()> {
53 self.kind = Some(kind);
54 Ok(())
55 }
56
57 pub fn with_message(&mut self, message: String) -> anyhow::Result<()> {
58 self.message = Some(message);
59 Ok(())
60 }
61
62 pub fn with_thread_name(&mut self, thread_name: String) -> anyhow::Result<()> {
63 if thread_name.trim().is_empty() {
64 return Ok(());
65 }
66 self.thread_name = Some(thread_name);
67 Ok(())
68 }
69
70 pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<()> {
71 self.stack = Some(stack);
72 Ok(())
73 }
74
75 pub fn with_stack_frame(&mut self, frame: StackFrame, incomplete: bool) -> anyhow::Result<()> {
76 if let Some(stack) = &mut self.stack {
77 stack.push_frame(frame, incomplete)?;
78 } else {
79 self.stack = Some(StackTrace::from_frames(vec![frame], incomplete));
80 }
81 Ok(())
82 }
83
84 pub fn with_stack_set_complete(&mut self) -> anyhow::Result<()> {
85 if let Some(stack) = &mut self.stack {
86 stack.set_complete()?;
87 } else {
88 self.stack = Some(StackTrace::new_incomplete());
93 }
94 Ok(())
95 }
96
97 pub fn with_threads(&mut self, threads: Vec<ThreadData>) -> anyhow::Result<()> {
98 if threads.is_empty() {
99 return Ok(());
100 }
101 self.threads = Some(threads);
102 Ok(())
103 }
104
105 pub fn with_thread(&mut self, thread: ThreadData) -> anyhow::Result<()> {
106 self.threads.get_or_insert_with(Vec::new).push(thread);
107 Ok(())
108 }
109}
110
111#[derive(Debug, PartialEq)]
112pub struct CrashInfoBuilder {
113 pub counters: Option<HashMap<String, i64>>,
114 pub error: ErrorDataBuilder,
115 pub experimental: Option<Experimental>,
116 pub files: Option<HashMap<String, Vec<String>>>,
117 pub fingerprint: Option<String>,
118 pub incomplete: Option<bool>,
119 pub log_messages: Option<Vec<String>>,
120 pub metadata: Option<Metadata>,
121 pub os_info: Option<OsInfo>,
122 pub proc_info: Option<ProcInfo>,
123 pub sig_info: Option<SigInfo>,
124 pub span_ids: Option<Vec<Span>>,
125 pub timestamp: Option<DateTime<Utc>>,
126 pub trace_ids: Option<Vec<Span>>,
127 pub ucontext: Option<Ucontext>,
128 pub uuid: Uuid,
129}
130
131impl Default for CrashInfoBuilder {
132 fn default() -> Self {
133 Self {
134 counters: None,
135 error: ErrorDataBuilder::default(),
136 experimental: None,
137 files: None,
138 fingerprint: None,
139 incomplete: None,
140 log_messages: None,
141 metadata: None,
142 os_info: None,
143 proc_info: None,
144 sig_info: None,
145 span_ids: None,
146 timestamp: None,
147 trace_ids: None,
148 ucontext: None,
149 uuid: Uuid::new_v4(),
150 }
151 }
152}
153
154impl CrashInfoBuilder {
155 pub fn build(self) -> anyhow::Result<CrashInfo> {
156 let counters = self.counters.unwrap_or_default();
157 let data_schema_version = CrashInfo::current_schema_version().to_string();
158 let (error, incomplete_error) = self.error.build()?;
159 let experimental = self.experimental;
160 let files = self.files.unwrap_or_default();
161 let fingerprint = self.fingerprint;
162 let incomplete = incomplete_error || self.incomplete.unwrap_or(false);
163 let log_messages = self.log_messages.unwrap_or_default();
164 let metadata = self.metadata.unwrap_or_else(Metadata::unknown_value);
165 let os_info = self.os_info.unwrap_or_else(OsInfo::unknown_value);
166 let proc_info = self.proc_info;
167 let sig_info = self.sig_info;
168 let span_ids = self.span_ids.unwrap_or_default();
169 let timestamp = self.timestamp.unwrap_or_else(Utc::now).to_string();
170 let trace_ids = self.trace_ids.unwrap_or_default();
171 let ucontext = self.ucontext;
172 let uuid = self.uuid;
173 Ok(CrashInfo {
174 counters,
175 data_schema_version,
176 error,
177 experimental,
178 files,
179 fingerprint,
180 incomplete,
181 log_messages,
182 metadata,
183 os_info,
184 proc_info,
185 sig_info,
186 span_ids,
187 timestamp,
188 trace_ids,
189 ucontext,
190 uuid: uuid.to_string(),
191 })
192 }
193
194 pub fn has_data(&self) -> bool {
198 let blank = Self {
199 uuid: self.uuid,
200 ..Self::default()
201 };
202
203 self != &blank
204 }
205
206 pub fn new() -> Self {
207 Self::default()
208 }
209
210 pub fn with_counter(&mut self, name: String, value: i64) -> anyhow::Result<()> {
212 anyhow::ensure!(!name.is_empty(), "Empty counter name not allowed");
213 if let Some(ref mut counters) = &mut self.counters {
214 counters.insert(name, value);
215 } else {
216 self.counters = Some(HashMap::from([(name, value)]));
217 }
218 Ok(())
219 }
220
221 pub fn with_counters(&mut self, counters: HashMap<String, i64>) -> anyhow::Result<()> {
222 self.counters = Some(counters);
223 Ok(())
224 }
225
226 pub fn with_experimental_additional_tags(
227 &mut self,
228 additional_tags: Vec<String>,
229 ) -> anyhow::Result<()> {
230 if let Some(experimental) = &mut self.experimental {
231 experimental.additional_tags = additional_tags;
232 } else {
233 self.experimental = Some(Experimental::new().with_additional_tags(additional_tags));
234 }
235 Ok(())
236 }
237
238 pub fn with_experimental_runtime_stack(
239 &mut self,
240 runtime_stack: RuntimeStack,
241 ) -> anyhow::Result<()> {
242 if let Some(experimental) = &mut self.experimental {
243 experimental.runtime_stack = Some(runtime_stack);
244 } else {
245 self.experimental = Some(Experimental::new().with_runtime_stack(runtime_stack));
246 }
247 Ok(())
248 }
249
250 pub fn with_kind(&mut self, kind: ErrorKind) -> anyhow::Result<()> {
251 self.error.with_kind(kind)
252 }
253
254 pub fn with_file(&mut self, filename: String) -> anyhow::Result<()> {
255 let file = File::open(&filename).with_context(|| format!("filename: {filename}"))?;
256 let lines: std::io::Result<Vec<_>> = BufReader::new(file).lines().collect();
257 self.with_file_and_contents(filename, lines?)?;
258 Ok(())
259 }
260
261 pub fn with_file_and_contents(
263 &mut self,
264 filename: String,
265 contents: Vec<String>,
266 ) -> anyhow::Result<()> {
267 if let Some(ref mut files) = &mut self.files {
268 files.insert(filename, contents);
269 } else {
270 self.files = Some(HashMap::from([(filename, contents)]));
271 }
272 Ok(())
273 }
274
275 pub fn with_files(&mut self, files: HashMap<String, Vec<String>>) -> anyhow::Result<()> {
277 self.files = Some(files);
278 Ok(())
279 }
280
281 pub fn with_fingerprint(&mut self, fingerprint: String) -> anyhow::Result<()> {
282 anyhow::ensure!(!fingerprint.is_empty(), "Expect non-empty fingerprint");
283 self.fingerprint = Some(fingerprint);
284 Ok(())
285 }
286
287 pub fn with_incomplete(&mut self, incomplete: bool) -> anyhow::Result<()> {
288 self.incomplete = Some(incomplete);
289 Ok(())
290 }
291
292 pub fn with_log_message(&mut self, message: String, also_print: bool) -> anyhow::Result<()> {
294 if also_print {
295 eprintln!("{message}");
296 }
297
298 if let Some(ref mut messages) = &mut self.log_messages {
299 messages.push(message);
300 } else {
301 self.log_messages = Some(vec![message]);
302 }
303 Ok(())
304 }
305
306 pub fn with_log_messages(&mut self, log_messages: Vec<String>) -> anyhow::Result<()> {
307 self.log_messages = Some(log_messages);
308 Ok(())
309 }
310
311 pub fn with_message(&mut self, message: String) -> anyhow::Result<()> {
312 self.error.with_message(message)
313 }
314
315 pub fn with_thread_name(&mut self, thread_name: String) -> anyhow::Result<()> {
316 self.error.with_thread_name(thread_name)
317 }
318
319 pub fn with_metadata(&mut self, metadata: Metadata) -> anyhow::Result<()> {
320 self.metadata = Some(metadata);
321 Ok(())
322 }
323
324 pub fn with_os_info(&mut self, os_info: OsInfo) -> anyhow::Result<()> {
325 self.os_info = Some(os_info);
326 Ok(())
327 }
328
329 pub fn with_os_info_this_machine(&mut self) -> anyhow::Result<()> {
330 self.with_os_info(::os_info::get().into())?;
331 Ok(())
332 }
333
334 pub fn with_proc_info(&mut self, proc_info: ProcInfo) -> anyhow::Result<()> {
335 self.proc_info = Some(proc_info);
336 Ok(())
337 }
338
339 pub fn with_sig_info(&mut self, sig_info: SigInfo) -> anyhow::Result<()> {
340 self.sig_info = Some(sig_info);
341 Ok(())
342 }
343
344 pub fn with_span_id(&mut self, span_id: Span) -> anyhow::Result<()> {
345 if let Some(ref mut span_ids) = &mut self.span_ids {
346 span_ids.push(span_id);
347 } else {
348 self.span_ids = Some(vec![span_id]);
349 }
350 Ok(())
351 }
352
353 pub fn with_span_ids(&mut self, span_ids: Vec<Span>) -> anyhow::Result<()> {
354 self.span_ids = Some(span_ids);
355 Ok(())
356 }
357
358 pub fn with_stack(&mut self, stack: StackTrace) -> anyhow::Result<()> {
359 self.error.with_stack(stack)
360 }
361
362 pub fn with_stack_frame(&mut self, frame: StackFrame, incomplete: bool) -> anyhow::Result<()> {
363 self.error.with_stack_frame(frame, incomplete)
364 }
365
366 pub fn with_stack_set_complete(&mut self) -> anyhow::Result<()> {
367 let had_no_frames = self.error.stack.is_none();
368 self.error.with_stack_set_complete()?;
369 if had_no_frames {
370 self.with_log_message(
371 "No native stack frames received; stack unwinding may have failed".to_string(),
372 true,
373 )?;
374 }
375 Ok(())
376 }
377
378 pub fn with_thread(&mut self, thread: ThreadData) -> anyhow::Result<()> {
379 self.error.with_thread(thread)
380 }
381
382 pub fn with_threads(&mut self, threads: Vec<ThreadData>) -> anyhow::Result<()> {
383 self.error.with_threads(threads)
384 }
385
386 pub fn with_timestamp(&mut self, timestamp: DateTime<Utc>) -> anyhow::Result<()> {
387 self.timestamp = Some(timestamp);
388 Ok(())
389 }
390
391 pub fn with_timestamp_now(&mut self) -> anyhow::Result<()> {
392 self.with_timestamp(Utc::now())?;
393 Ok(())
394 }
395
396 pub fn with_trace_id(&mut self, trace_id: Span) -> anyhow::Result<()> {
397 if let Some(ref mut trace_ids) = &mut self.trace_ids {
398 trace_ids.push(trace_id);
399 } else {
400 self.trace_ids = Some(vec![trace_id]);
401 }
402 Ok(())
403 }
404
405 pub fn with_trace_ids(&mut self, trace_ids: Vec<Span>) -> anyhow::Result<()> {
406 self.trace_ids = Some(trace_ids);
407 Ok(())
408 }
409
410 pub fn with_ucontext(&mut self, ucontext: Ucontext) -> anyhow::Result<()> {
411 self.ucontext = Some(ucontext);
412 Ok(())
413 }
414
415 pub fn build_crash_ping(&self) -> anyhow::Result<CrashPing> {
417 let metadata = self.metadata.clone().context("metadata is required")?;
418 let kind = self.error.kind.clone().context("kind is required")?;
419 let message = self.error.message.clone();
420 let sig_info = self.sig_info.clone();
421
422 let mut builder = CrashPingBuilder::new(self.uuid)
423 .with_metadata(metadata)
424 .with_kind(kind);
425 if let Some(sig_info) = sig_info {
426 builder = builder.with_sig_info(sig_info);
427 }
428 if let Some(message) = message {
429 builder = builder.with_custom_message(message);
430 }
431 builder.build()
432 }
433
434 pub fn is_ping_ready(&self) -> bool {
435 self.metadata.is_some() && self.error.kind.is_some()
436 }
437
438 pub fn has_message(&self) -> bool {
439 self.error.message.is_some()
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::crash_info::test_utils::TestInstance;
447
448 #[test]
449 fn test_crash_info_builder_to_crash_ping() {
450 let sig_info = SigInfo::test_instance(42);
451 let metadata = Metadata::test_instance(1);
452
453 let mut crash_info_builder = CrashInfoBuilder::new();
454 crash_info_builder.with_sig_info(sig_info.clone()).unwrap();
455 crash_info_builder.with_metadata(metadata.clone()).unwrap();
456 crash_info_builder.with_kind(ErrorKind::Panic).unwrap();
457
458 let crash_ping = crash_info_builder.build_crash_ping().unwrap();
459
460 assert!(!crash_ping.crash_uuid().is_empty());
461 assert!(Uuid::parse_str(crash_ping.crash_uuid()).is_ok());
462 assert_eq!(crash_ping.siginfo(), Some(&sig_info));
463 assert_eq!(crash_ping.metadata(), &metadata);
464 assert!(crash_ping.message().contains("crash processing started"));
465 }
466
467 #[test]
468 fn test_with_message() {
469 let mut builder = CrashInfoBuilder::new();
470
471 builder.with_kind(ErrorKind::UnixSignal).unwrap();
472 let test_message = "Test error message".to_string();
473
474 let result = builder.with_message(test_message.clone());
475 assert!(result.is_ok());
476 assert!(builder.has_message());
477
478 let sig_info = SigInfo::test_instance(42);
480 builder.with_sig_info(sig_info).unwrap();
481 builder.with_metadata(Metadata::test_instance(1)).unwrap();
482 builder.with_kind(ErrorKind::UnixSignal).unwrap();
483
484 let crash_ping = builder.build_crash_ping().unwrap();
485 assert!(crash_ping.message().contains(&test_message));
486 }
487
488 #[test]
489 fn test_has_data_empty() {
490 assert!(!CrashInfoBuilder::new().has_data());
493 }
494
495 #[test]
496 fn test_has_data_after_setting() {
497 let mut builder = CrashInfoBuilder::new();
498 builder.with_kind(ErrorKind::Panic).unwrap();
499 assert!(builder.has_data());
500
501 let mut builder = CrashInfoBuilder::new();
502 builder.with_metadata(Metadata::test_instance(1)).unwrap();
503 assert!(builder.has_data());
504
505 let mut builder = CrashInfoBuilder::new();
506 builder.with_incomplete(false).unwrap();
507 assert!(builder.has_data());
508 }
509
510 #[test]
511 fn test_has_message_empty() {
512 let builder = CrashInfoBuilder::new();
513 assert!(!builder.has_message());
514 }
515
516 #[test]
517 fn test_has_message_after_setting() {
518 let mut builder = CrashInfoBuilder::new();
519 builder.with_message("test".to_string()).unwrap();
520 assert!(builder.has_message());
521 }
522
523 #[test]
524 fn test_message_overwrite() {
525 let mut builder = CrashInfoBuilder::new();
526
527 builder.with_message("first message".to_string()).unwrap();
528 assert!(builder.has_message());
529
530 builder.with_message("second message".to_string()).unwrap();
532 assert!(builder.has_message());
533
534 let sig_info = SigInfo::test_instance(42);
536 builder.with_sig_info(sig_info).unwrap();
537 builder.with_metadata(Metadata::test_instance(1)).unwrap();
538 builder.with_kind(ErrorKind::UnixSignal).unwrap();
539
540 let crash_ping = builder.build_crash_ping().unwrap();
541 assert!(crash_ping.message().contains("second message"));
542 assert!(!crash_ping.message().contains("first message"));
543 builder.with_kind(ErrorKind::Panic).unwrap();
544
545 let report = builder.build().unwrap();
546 assert_eq!(report.error.message.as_deref(), Some("second message"));
547 }
548
549 #[test]
550 fn test_message_with_special_characters() {
551 let mut builder = CrashInfoBuilder::new();
552 let special_message = "Error: 'panic' with \"quotes\" and\nnewlines\t\ttabs";
553
554 builder.with_message(special_message.to_string()).unwrap();
555 builder.with_sig_info(SigInfo::test_instance(42)).unwrap();
556 builder.with_metadata(Metadata::test_instance(1)).unwrap();
557 builder.with_kind(ErrorKind::UnixSignal).unwrap();
558
559 let crash_ping = builder.build_crash_ping().unwrap();
560 assert!(crash_ping.message().contains(special_message));
561 builder.with_kind(ErrorKind::UnixSignal).unwrap();
562
563 let report = builder.build().unwrap();
564 assert_eq!(report.error.message.as_deref(), Some(special_message));
565 }
566
567 #[test]
568 fn test_very_long_message() {
569 let mut builder = CrashInfoBuilder::new();
570 let long_message = "x".repeat(10000); builder.with_message(long_message.clone()).unwrap();
573 assert!(builder.has_message());
574
575 builder.with_sig_info(SigInfo::test_instance(42)).unwrap();
576 builder.with_metadata(Metadata::test_instance(1)).unwrap();
577 builder.with_kind(ErrorKind::UnixSignal).unwrap();
578
579 let crash_ping = builder.build_crash_ping().unwrap();
580 assert!(crash_ping.message().len() >= 10000);
581
582 builder.with_kind(ErrorKind::UnixSignal).unwrap();
583 let report = builder.build().unwrap();
584 assert!(report.error.message.as_ref().unwrap().len() >= 10000);
585 }
586
587 #[test]
588 fn test_no_frames_is_incomplete() {
589 let mut builder = ErrorDataBuilder::new();
592 assert!(builder.stack.is_none());
593
594 let result = builder.with_stack_set_complete();
596 assert!(result.is_ok());
597
598 assert!(builder.stack.is_some());
600 let stack = builder.stack.as_ref().unwrap();
601 assert!(stack.frames.is_empty());
602 assert!(stack.incomplete);
603 }
604
605 #[test]
606 fn test_with_stack_set_complete_with_frames() {
607 let mut builder = ErrorDataBuilder::new();
609
610 let frame = StackFrame::test_instance(1);
612 builder.with_stack_frame(frame, true).unwrap();
613 assert!(builder.stack.as_ref().unwrap().incomplete);
614
615 builder.with_stack_set_complete().unwrap();
617
618 let stack = builder.stack.as_ref().unwrap();
620 assert_eq!(stack.frames.len(), 1);
621 assert!(!stack.incomplete);
622 }
623
624 #[test]
625 fn test_crash_info_builder_empty_stack_is_incomplete() {
626 let mut builder = CrashInfoBuilder::new();
629 builder.with_kind(ErrorKind::UnixSignal).unwrap();
630
631 builder.with_stack_set_complete().unwrap();
633
634 let crash_info = builder.build().unwrap();
635
636 assert!(crash_info.error.stack.frames.is_empty());
638 assert!(crash_info.error.stack.incomplete);
639
640 assert!(!crash_info.incomplete);
642
643 assert!(crash_info
645 .log_messages
646 .iter()
647 .any(|msg| msg.contains("No native stack frames received")));
648 }
649
650 #[test]
651 #[cfg_attr(miri, ignore)] fn test_with_os_info_this_machine() {
653 let mut builder = CrashInfoBuilder::new();
654 builder.with_kind(ErrorKind::UnixSignal).unwrap();
655
656 builder.with_os_info_this_machine().unwrap();
657
658 let crash_info = builder.build().unwrap();
659
660 assert!(!crash_info.os_info.architecture.is_empty());
662 assert!(!crash_info.os_info.bitness.is_empty());
663 assert!(!crash_info.os_info.os_type.is_empty());
664 assert!(!crash_info.os_info.version.is_empty());
665
666 assert_ne!(
668 crash_info.os_info.architecture, "unknown",
669 "architecture should not be 'unknown'"
670 );
671 assert_ne!(
672 crash_info.os_info.bitness, "unknown bitness",
673 "bitness should not be 'unknown bitness'"
674 );
675 assert_ne!(
676 crash_info.os_info.os_type, "Unknown",
677 "os_type should not be 'Unknown'"
678 );
679 assert_ne!(
680 crash_info.os_info.version, "Unknown",
681 "version should not be 'Unknown'"
682 );
683 }
684}