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