Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Recipe

Instrument a request handler

Use when: a service handles many requests at once and its log lines can't be told apart or searched

Goal: every line a request writes carries that request's facts as fields, at the right level, with nothing private in it

Taught in Structured logging and tracing

  1. 1

    Call tracing_subscriber::fmt() with an EnvFilter, and .json() when the output goes to a log store.

    Why: the macros record nothing until a subscriber exists, and one filter controls every crate's output.

  2. 2

    Add #[instrument(skip_all, fields(...))] or skip(...) for anything private or large.

    Example

    #[instrument(skip(state, req), fields(sailing = %req.sailing))] on async fn book(...).

    Why: the span stamps every event inside the call, including those from functions it calls, with the request's facts.

  3. 3

    info!(seats, booking_id, "booking confirmed"), with % for Display and ? for Debug.

    Why: fields are what a log store can filter and count; values inside the message are only text.

  4. 4

    error! for a failure someone must look at, warn! for something odd the service survived, info! for the main steps, debug! for detail.

    Why: production runs at info, so the level decides whether a line is seen at all.

Common mistakes

  • Holding a .enter() guard across an .await.
  • Recording a password, token or whole request body as a field.
  • Logging the same error at every level of the call stack instead of once, where it's handled.