Wisp is in pre-release, self-hosted since GitHub suspended us. Install it, or see the changelog.

Wisp

Debugging Rust handlers

This page shows how to find the cause of a bug in a load, an action or an endpoint. It lists the tools, from the quickest to the most exact. To find slow code, see Profiling.

The dev error page

When a handler panics under wisp dev, the server catches the panic and answers with a 500. The dev error page shows:

  • the panic message
  • the place of the panic as file:line:column
  • a code frame: the line that panicked and two lines above and below it, with line numbers and a ^ under the column
  • the request: its method, path and query string
panic at src/routes/posts/[slug]/+page.rs:12:32: called `Option::unwrap()` on a `None` value
  --> src/routes/posts/[slug]/+page.rs:12:32
   |
10 | pub async fn load(cx: &mut Cx) -> Result<Page> {
11 |     let slug = cx.param("slug");
12 |     let post = POSTS.get(slug).unwrap();
   |                                ^
13 |     Ok(Page { post })
14 | }

The same text opens in the dev dialog of the browser. Click the file:line link to open the line in your editor.

A panic in the code that a template makes shows the .wisp file and line, with the frame of the template. See Template errors.

The frame follows these rules:

  • It shows only under wisp dev (a debug build with dev mode on). A release build, and any server with WISP_DEV=off, answers with a plain 500 and shows no message and no source.
  • It reads only files inside the app folder. It reads a relative path from the app folder first, then from the folders above it. If both hold a file with that path, it shows the file of the app folder. A panic in the standard library or in a dependency has the message and the place but no frame.
  • It skips a file that is larger than 1 MiB. A source line shows its first 160 characters.
  • Wisp escapes all of the text, so a source line with HTML in it shows as text.

A handler that returns an Err (not a panic) shows the status and the message on the same page, with the request. Use error(status, message) with a message that names the cause.

dbg! prints the file, the line, the expression and its value to the standard error of the app. wisp dev shows it in the terminal where it runs.

let post = dbg!(POSTS.get(slug));

println! and eprintln! show there too. A dbg! moves its value, so pass a reference or a clone when you need the value after it. Delete each dbg! before you commit. cargo clippy warns about one with the dbg_macro lint when you enable it.

Backtraces

Wisp keeps the panic hook quiet for a handler panic, because the 500 reports it once. Set RUST_BACKTRACE=1 to get the standard panic report with its backtrace in the terminal, as well.

RUST_BACKTRACE=1 wisp dev

Use RUST_BACKTRACE=full for every frame. Backtraces name functions of the app because a dev build has debug information.

Logs

  • WISP_LOG=json writes one JSON line per request, with the status and time. See Logs, metrics and traces.
  • Wisp does not read RUST_LOG. If the app uses the tracing crate or the log crate, its own subscriber reads that variable.
  • Set OTEL_EXPORTER_OTLP_ENDPOINT to send the spans of each request to a trace tool.
  • Under wisp dev, press Alt+Shift+W for the devtools: the route, its params and server values, and the Server-Timing of each phase.

wisp dev prints each request in the terminal. A 5xx adds its cause below the request, and a panic adds its code frame.

Attach a debugger

wisp dev runs a copy of your app, .wisp/run/app-<pid>-<slot> (with .exe on Windows), so that cargo build can replace the original. There are two ways to debug it.

Run the binary yourself. Build with cargo build, then run the debugger from the app folder, because a dev build reads static/ and .wisp/ from its working directory:

cargo build
lldb -- target/debug/my-app      # or: gdb --args target/debug/my-app
(lldb) breakpoint set --file +page.rs --line 12
(lldb) run

Outside wisp dev the app has no parent process to follow. Set PORT if the default port is in use.

Attach to the running app. Find the process id in .wisp/run or in your process list, then attach:

lldb -p <pid>

On Windows, attach WinDbg or the debugger of your editor to app-<pid>-<slot>.exe. In VS Code, the CodeLLDB extension can run the first way from its debug configuration.

A breakpoint stops the whole server thread, so the browser waits. When wisp dev rebuilds, it starts a new copy and stops the old one, and the debugger detaches. Attach again to the new process.

Common causes

  • A 500 with a message about unwrap or expect. The code frame names the call. Replace it with .or_404(), .or_status(400) or ?.
  • A panic with an index out of range. The frame names the index expression. Print the index and the length with dbg!.
  • A hang, not a panic. A blocking call in a handler holds the thread. The dev log of the request names a hold of 100 ms or more.
  • A handler that answers but shows wrong data. Print the input with dbg! first, then the value after each step.

Is This Page Useful?

View This Page's Source

Type to search every docs page and heading.