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 stack: Option<StackTrace>,
20 pub threads: Option<Vec<ThreadData>>,
21}
22
23impl ErrorDataBuilder {
24 pub fn build(self) -> anyhow::Result<(ErrorData, bool )> {
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 #[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 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 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 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 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 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 #[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}