pub fn parse_stack_trace_full(input: &str) -> ParsedStackExpand description
Parse a stack trace string into message + frames.
Examples found in repository?
examples/error_symbolication.rs (line 86)
67fn main() {
68 println!("=== Error Monitoring Symbolication Demo ===\n");
69
70 let source_map_json = build_source_map_json();
71
72 // -----------------------------------------------------------------------
73 // 1. Parse a V8-format stack trace (Chrome / Node.js)
74 // -----------------------------------------------------------------------
75 //
76 // V8 format: `at functionName (file:line:column)` with 1-based positions.
77 // The error message appears on the first line.
78
79 println!("--- Step 1: Parse V8 stack trace ---\n");
80
81 let v8_stack = "\
82TypeError: Cannot read property 'x' of null
83 at processData (bundle.js:3:10)
84 at handleClick (bundle.js:1:1)";
85
86 let v8_parsed = parse_stack_trace_full(v8_stack);
87
88 println!(" Message: {:?}", v8_parsed.message);
89 println!(" Frames: {}", v8_parsed.frames.len());
90 for (i, frame) in v8_parsed.frames.iter().enumerate() {
91 println!(
92 " [{i}] {} {}:{}:{}",
93 frame.function_name.as_deref().unwrap_or("<anonymous>"),
94 frame.file,
95 frame.line,
96 frame.column,
97 );
98 }
99
100 assert_eq!(
101 v8_parsed.message.as_deref(),
102 Some("TypeError: Cannot read property 'x' of null")
103 );
104 assert_eq!(v8_parsed.frames.len(), 2);
105 assert_eq!(
106 v8_parsed.frames[0].function_name.as_deref(),
107 Some("processData")
108 );
109 assert_eq!(v8_parsed.frames[0].file, "bundle.js");
110 assert_eq!(v8_parsed.frames[0].line, 3);
111 assert_eq!(v8_parsed.frames[0].column, 10);
112 assert_eq!(
113 v8_parsed.frames[1].function_name.as_deref(),
114 Some("handleClick")
115 );
116 assert_eq!(v8_parsed.frames[1].line, 1);
117 assert_eq!(v8_parsed.frames[1].column, 1);
118
119 // -----------------------------------------------------------------------
120 // 2. Parse a SpiderMonkey-format stack trace (Firefox)
121 // -----------------------------------------------------------------------
122 //
123 // SpiderMonkey format: `functionName@file:line:column` (no message line).
124
125 println!("\n--- Step 2: Parse SpiderMonkey stack trace ---\n");
126
127 let sm_stack = "\
128processData@bundle.js:3:10
129handleClick@bundle.js:1:1";
130
131 let sm_frames = parse_stack_trace(sm_stack);
132
133 println!(" Frames: {}", sm_frames.len());
134 for (i, frame) in sm_frames.iter().enumerate() {
135 println!(
136 " [{i}] {} {}:{}:{}",
137 frame.function_name.as_deref().unwrap_or("<anonymous>"),
138 frame.file,
139 frame.line,
140 frame.column,
141 );
142 }
143
144 assert_eq!(sm_frames.len(), 2);
145 assert_eq!(sm_frames[0].function_name.as_deref(), Some("processData"));
146 assert_eq!(sm_frames[0].file, "bundle.js");
147 assert_eq!(sm_frames[0].line, 3);
148 assert_eq!(sm_frames[0].column, 10);
149
150 // -----------------------------------------------------------------------
151 // 3. Verify both formats produce the same parsed frames
152 // -----------------------------------------------------------------------
153
154 println!("\n--- Step 3: Compare parsed frames ---\n");
155
156 assert_eq!(v8_parsed.frames.len(), sm_frames.len());
157 for (v8_frame, sm_frame) in v8_parsed.frames.iter().zip(sm_frames.iter()) {
158 assert_eq!(v8_frame.function_name, sm_frame.function_name);
159 assert_eq!(v8_frame.file, sm_frame.file);
160 assert_eq!(v8_frame.line, sm_frame.line);
161 assert_eq!(v8_frame.column, sm_frame.column);
162 }
163 println!(" V8 and SpiderMonkey frames match.");
164
165 // -----------------------------------------------------------------------
166 // 4. Symbolicate the V8 stack trace
167 // -----------------------------------------------------------------------
168 //
169 // The loader closure receives a filename ("bundle.js") and returns the
170 // parsed SourceMap. Internally, the symbolicate function:
171 // 1. Subtracts 1 from the 1-based stack trace positions to get 0-based
172 // 2. Calls SourceMap::original_position_for(line, column)
173 // 3. Adds 1 back to the resolved 0-based original positions
174
175 println!("\n--- Step 4: Symbolicate V8 stack trace ---\n");
176
177 let v8_result = symbolicate(v8_stack, |file| {
178 if file == "bundle.js" {
179 SourceMap::from_json(&source_map_json).ok()
180 } else {
181 None
182 }
183 });
184
185 println!(" Symbolicated stack (Display):\n");
186 // SymbolicatedStack implements Display, printing a V8-style resolved trace
187 print!("{v8_result}");
188
189 assert_eq!(
190 v8_result.message.as_deref(),
191 Some("TypeError: Cannot read property 'x' of null")
192 );
193 assert_eq!(v8_result.frames.len(), 2);
194
195 // Frame 0: bundle.js:3:10 (1-based) -> 0-based (2, 9) -> src/app.ts (9, 4) -> 1-based (10, 5)
196 assert!(v8_result.frames[0].symbolicated);
197 assert_eq!(v8_result.frames[0].file, "src/app.ts");
198 assert_eq!(v8_result.frames[0].line, 10);
199 assert_eq!(v8_result.frames[0].column, 5);
200 assert_eq!(
201 v8_result.frames[0].function_name.as_deref(),
202 Some("processData")
203 );
204
205 // Frame 1: bundle.js:1:1 (1-based) -> 0-based (0, 0) -> src/app.ts (4, 0) -> 1-based (5, 1)
206 assert!(v8_result.frames[1].symbolicated);
207 assert_eq!(v8_result.frames[1].file, "src/app.ts");
208 assert_eq!(v8_result.frames[1].line, 5);
209 assert_eq!(v8_result.frames[1].column, 1);
210 assert_eq!(
211 v8_result.frames[1].function_name.as_deref(),
212 Some("handleClick")
213 );
214
215 // -----------------------------------------------------------------------
216 // 5. Symbolicate the SpiderMonkey stack trace
217 // -----------------------------------------------------------------------
218
219 println!("\n--- Step 5: Symbolicate SpiderMonkey stack trace ---\n");
220
221 let sm_result = symbolicate(sm_stack, |file| {
222 if file == "bundle.js" {
223 SourceMap::from_json(&source_map_json).ok()
224 } else {
225 None
226 }
227 });
228
229 print!("{sm_result}");
230
231 // Both engine formats should resolve to the same original positions
232 assert_eq!(v8_result.frames.len(), sm_result.frames.len());
233 for (v8_frame, sm_frame) in v8_result.frames.iter().zip(sm_result.frames.iter()) {
234 assert_eq!(v8_frame.file, sm_frame.file);
235 assert_eq!(v8_frame.line, sm_frame.line);
236 assert_eq!(v8_frame.column, sm_frame.column);
237 assert_eq!(v8_frame.function_name, sm_frame.function_name);
238 assert_eq!(v8_frame.symbolicated, sm_frame.symbolicated);
239 }
240 println!("\n V8 and SpiderMonkey symbolicated frames match.");
241
242 // -----------------------------------------------------------------------
243 // 6. JSON output
244 // -----------------------------------------------------------------------
245
246 println!("\n--- Step 6: JSON output ---\n");
247
248 let json_output = to_json(&v8_result);
249 println!("{json_output}");
250
251 assert!(json_output.contains("\"symbolicated\": true"));
252 assert!(json_output.contains("src/app.ts"));
253 assert!(json_output.contains("processData"));
254 assert!(json_output.contains("handleClick"));
255 assert!(json_output.contains("Cannot read property"));
256
257 // -----------------------------------------------------------------------
258 // 7. Batch symbolication
259 // -----------------------------------------------------------------------
260 //
261 // In production error monitoring, you often need to symbolicate many stack
262 // traces against a pre-loaded set of source maps. symbolicate_batch avoids
263 // re-parsing source maps for each trace.
264
265 println!("\n--- Step 7: Batch symbolication ---\n");
266
267 let sm = SourceMap::from_json(&source_map_json).expect("valid source map");
268 let mut maps: HashMap<String, SourceMap> = HashMap::new();
269 maps.insert("bundle.js".to_string(), sm);
270
271 let error_stack_1 = "\
272TypeError: Cannot read property 'x' of null
273 at processData (bundle.js:3:10)
274 at handleClick (bundle.js:1:1)";
275
276 let error_stack_2 = "\
277ReferenceError: foo is not defined
278 at handleClick (bundle.js:1:1)";
279
280 let stacks = vec![error_stack_1, error_stack_2];
281 let batch_results = symbolicate_batch(&stacks, &maps);
282
283 assert_eq!(batch_results.len(), 2);
284 println!(
285 " Batch result 1 ({} frames):",
286 batch_results[0].frames.len()
287 );
288 print!("{}", batch_results[0]);
289
290 println!(
291 " Batch result 2 ({} frames):",
292 batch_results[1].frames.len()
293 );
294 print!("{}", batch_results[1]);
295
296 // First stack: same as individual symbolication
297 assert_eq!(batch_results[0].frames.len(), 2);
298 assert!(batch_results[0].frames[0].symbolicated);
299 assert_eq!(batch_results[0].frames[0].file, "src/app.ts");
300
301 // Second stack: single frame
302 assert_eq!(batch_results[1].frames.len(), 1);
303 assert!(batch_results[1].frames[0].symbolicated);
304 assert_eq!(
305 batch_results[1].message.as_deref(),
306 Some("ReferenceError: foo is not defined")
307 );
308
309 // -----------------------------------------------------------------------
310 // 8. Debug ID resolution
311 // -----------------------------------------------------------------------
312 //
313 // In production, source maps are often stored by their debug ID (a UUID
314 // embedded in both the generated file and the source map). This allows
315 // matching without relying on filename conventions.
316
317 println!("\n--- Step 8: Debug ID resolution ---\n");
318
319 let debug_id = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
320
321 let resolved = resolve_by_debug_id(debug_id, &maps);
322 assert!(resolved.is_some(), "should find source map by debug ID");
323
324 let resolved_map = resolved.unwrap();
325 assert_eq!(resolved_map.debug_id.as_deref(), Some(debug_id));
326 println!(" Found source map for debug ID: {debug_id}");
327 println!(" Sources: {:?}", resolved_map.sources);
328 println!(" Names: {:?}", resolved_map.names);
329
330 // Non-existent debug ID returns None
331 let missing = resolve_by_debug_id("00000000-0000-0000-0000-000000000000", &maps);
332 assert!(missing.is_none(), "unknown debug ID should return None");
333 println!(" Unknown debug ID correctly returns None.");
334
335 println!("\n=== All assertions passed. ===");
336}