Skip to content

yawn/wacks

wacks

CI

Structured WASM panic stack traces for browsers.

wacks captures Error.stack from inside a WASM panic hook, parses it into structured Frames across Chrome, Firefox, and Safari, and demangles Rust symbols — giving you data suitable for error reporting services (PostHog, Sentry, Datadog, etc.).

Function names are always resolved from the WASM binary's name section, making symbolication reliable across all browsers — including Safari/WebKit, which nondeterministically drops names from Error.stack.

Usage

wacks::Builder::new()
    .framemap(include_bytes!("app.framemap"))
    .install(|frames, info| {
        // frames: Vec<Frame>, info: &PanicHookInfo
    });

Frame::parse works on any target (not just WASM), so you can use it server-side to process stack traces sent from browsers:

use wacks::Frame;

let frames = Frame::parse(stack_string);

Build configuration for useful stack traces

The WASM binary must retain its name section for function names to appear. Without it, every frame will be anonymous.

Add this to your Cargo.toml:

[profile.release]
strip = "none"           # keep the name section
debug = "line-tables-only" # minimal debug info for file/line mapping

If you use wasm-bindgen, pass --keep-debug to preserve debug info through the bindgen step:

wasm-bindgen --keep-debug --target web ...

Browser support

Browser Result
Chrome Full frames with demangled function names + WASM byte offsets
Firefox Full frames with demangled function names + WASM byte offsets
Safari Full frames with demangled function names + byte offsets via framemap

Framemap

The framemap is a compact build artifact generated by framemap-gen that provides:

  1. WebKit byte offset resolution — Safari only provides function indices in Error.stack, not byte offsets. The framemap maps (caller, callee) function pairs to exact call instruction offsets.
  2. Source location resolution — When the WASM binary contains DWARF debug info, the framemap includes a byte-offset → (filename, line, col) table, resolving frames to real Rust source locations at runtime without external source maps.

The framemap uses delta encoding for sorted address sequences and postcard varint serialization to minimize file size.

Generating framemaps (framemap-gen)

cargo install wacks --features framemap-gen
framemap-gen input.wasm output.framemap

This requires debug = "line-tables-only" (or higher) in your release profile for source location resolution.

Loading at runtime

The caller fetches the .framemap file and passes the raw bytes to Builder::framemap:

const framemap = new Uint8Array(await (await fetch("app.framemap")).arrayBuffer());
// pass to your wasm_bindgen init function that calls Builder::framemap(&bytes)

Result

With a framemap, frames sent to error reporters contain real source locations:

{
  "function": "pipeline::commit",
  "filename": "src/lib.rs",
  "lineno": 89,
  "colno": 9,
  "in_app": true
}

Features

  • framemap-gen — builds the framemap-gen binary
  • serde — derives Serialize / Deserialize on Frame

License

MIT OR Apache-2.0

About

Capture WASM stacks

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors