relay_server/utils/
native.rs1use std::collections::BTreeMap;
7use std::error::Error;
8
9use chrono::{TimeZone, Utc};
10use minidump::{
11 MinidumpAnnotation, MinidumpCrashpadInfo, MinidumpModuleList, Module, StabilityReport,
12};
13use relay_event_schema::protocol::{
14 ClientSdkInfo, Context, Contexts, Event, Exception, JsonLenientString, Level, Mechanism,
15 StabilityReportContext, Values,
16};
17use relay_protocol::{Annotated, Value};
18
19type Minidump<'a> = minidump::Minidump<'a, &'a [u8]>;
20
21#[derive(Debug)]
30struct NativePlaceholder {
31 exception_type: &'static str,
33 exception_value: &'static str,
35 mechanism_type: &'static str,
37}
38
39fn write_native_placeholder(event: &mut Event, placeholder: NativePlaceholder) {
45 let platform = event.platform.value_mut();
47 *platform = Some("native".to_owned());
48
49 event.level.get_or_insert_with(|| Level::Fatal);
54
55 let exceptions = event
59 .exceptions
60 .value_mut()
61 .get_or_insert_with(Values::default)
62 .values
63 .value_mut()
64 .get_or_insert_with(Vec::new);
65
66 exceptions.clear(); exceptions.push(Annotated::new(Exception {
69 ty: Annotated::new(placeholder.exception_type.to_owned()),
70 value: Annotated::new(JsonLenientString(placeholder.exception_value.to_owned())),
71 mechanism: Annotated::new(Mechanism {
72 ty: Annotated::from(placeholder.mechanism_type.to_owned()),
73 handled: Annotated::from(false),
74 synthetic: Annotated::from(true),
75 ..Mechanism::default()
76 }),
77 ..Exception::default()
78 }));
79}
80
81fn write_crashpad_annotations(
91 event: &mut Event,
92 minidump: &Minidump<'_>,
93) -> Result<(), minidump::Error> {
94 let module_list = minidump.get_stream::<MinidumpModuleList>()?;
95 let crashpad_info = match minidump.get_stream::<MinidumpCrashpadInfo>() {
96 Err(minidump::Error::StreamNotFound) => return Ok(()),
97 result => result?,
98 };
99
100 let contexts = event.contexts.get_or_insert_with(Contexts::new);
101
102 if !crashpad_info.simple_annotations.is_empty() {
103 let crashpad_context = crashpad_info
106 .simple_annotations
107 .into_iter()
108 .map(|(key, value)| (key, Annotated::new(Value::from(value))))
109 .collect();
110
111 contexts.insert("crashpad".to_owned(), Context::Other(crashpad_context));
112 }
113
114 match minidump.get_stream::<StabilityReport>() {
115 Ok(stability_report) => {
116 contexts.add(StabilityReportContext::from(stability_report));
117 }
118 Err(minidump::Error::StreamNotFound) => {
119 }
121 Err(err) => {
122 relay_log::debug!(
123 error = &err as &dyn Error,
124 "failed to parse stability report"
125 );
126 }
127 };
128
129 if crashpad_info.module_list.is_empty() {
130 return Ok(());
131 }
132
133 let modules = module_list.iter().collect::<Vec<_>>();
134
135 for module_info in crashpad_info.module_list {
136 let module = match modules.get(module_info.module_index) {
140 Some(module) => module,
141 None => {
142 relay_log::debug!(
143 module_index = module_info.module_index,
144 "Skipping invalid minidump module index",
145 );
146 continue;
147 }
148 };
149
150 let code_file = module.code_file();
154 let (_, module_name) = symbolic_common::split_path(&code_file);
155
156 let mut module_context = BTreeMap::new();
157 module_context.insert(
158 "type".to_owned(),
159 Annotated::new(Value::String("crashpad".to_owned())),
160 );
161
162 for (key, value) in module_info.simple_annotations {
163 module_context.insert(key, Annotated::new(Value::String(value)));
164 }
165
166 for (key, annotation) in module_info.annotation_objects {
167 if let MinidumpAnnotation::String(value) = annotation {
168 module_context.insert(key, Annotated::new(Value::String(value)));
169 }
170 }
171
172 if !module_info.list_annotations.is_empty() {
173 let annotation_list = module_info
176 .list_annotations
177 .into_iter()
178 .map(|s| Annotated::new(Value::String(s)))
179 .collect();
180
181 module_context.insert(
182 "annotations".to_owned(),
183 Annotated::new(Value::Array(annotation_list)),
184 );
185 }
186
187 contexts.insert(module_name.to_owned(), Context::Other(module_context));
188 }
189
190 Ok(())
191}
192
193pub fn process_minidump(event: &mut Event, data: &[u8]) {
198 let placeholder = NativePlaceholder {
199 exception_type: "Minidump",
200 exception_value: "Invalid Minidump",
201 mechanism_type: "minidump",
202 };
203 write_native_placeholder(event, placeholder);
204
205 let minidump = match Minidump::read(data) {
206 Ok(minidump) => minidump,
207 Err(err) => {
208 relay_log::debug!(error = &err as &dyn Error, "failed to parse minidump");
209 return;
210 }
211 };
212
213 let client_sdk_name = if minidump.get_stream::<MinidumpCrashpadInfo>().is_ok() {
214 "minidump.crashpad"
215 } else if minidump
216 .get_stream::<minidump::MinidumpBreakpadInfo>()
217 .is_ok()
218 {
219 "minidump.breakpad"
220 } else {
221 "minidump.unknown"
222 };
223
224 event.client_sdk.get_or_insert_with(|| ClientSdkInfo {
226 name: Annotated::new(client_sdk_name.to_owned()),
227 version: "0.0.0".to_owned().into(),
228 ..ClientSdkInfo::default()
229 });
230
231 let timestamp = Utc
234 .timestamp_opt(minidump.header.time_date_stamp.into(), 0)
235 .latest();
236
237 if let Some(timestamp) = timestamp {
238 event.timestamp.set_value(Some(timestamp.into()));
239 }
240
241 if let Err(err) = write_crashpad_annotations(event, &minidump) {
244 relay_log::debug!(
246 error = &err as &dyn Error,
247 "failed to parse minidump module list"
248 );
249 }
250}
251
252pub fn process_apple_crash_report(event: &mut Event, _data: &[u8]) {
255 let placeholder = NativePlaceholder {
256 exception_type: "AppleCrashReport",
257 exception_value: "Invalid Apple Crash Report",
258 mechanism_type: "applecrashreport",
259 };
260 write_native_placeholder(event, placeholder);
261}