Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Logging and Observability

Rule

Use tracing with operation spans, structured fields, fixed messages, sparse high-signal events, and explicit redaction.

Why

Structured traces remain searchable and aggregatable, while fixed messages identify event kinds and fields carry per-operation data.

Do

  • Let applications configure subscribers; libraries emit only spans and events.
  • Add spans around requests, jobs, commands, tasks, external calls, and workflow steps.
  • Prefer #[tracing::instrument(skip_all, fields(...))] and opt fields in.
  • Put IDs, states, attempts, counts, and durations in snake_case fields.
  • Keep INFO for lifecycle and important outcomes, DEBUG for investigation, WARN for degradation, and ERROR for failed operations.
  • Record errors with Debug capture (error = ?err) or a &dyn Error field.
  • Use counts, hashes, redacted displays, or booleans instead of sensitive raw values.

Avoid

  • Do not interpolate variable data into message strings.
  • Do not emit duplicate or high-cardinality INFO events.
  • Do not initialize subscribers in reusable libraries.
  • Do not use tracing as user-facing output or durable business events.
  • Do not use bare #[instrument] on arguments that may contain secrets.
  • Do not log credentials, tokens, bodies, raw command output, or unredacted URLs.

Library vs Application

Libraries emit structured telemetry without choosing global formatting or filtering. Applications own destinations, propagation, and subscriber configuration.

Example

#![allow(unused)]
fn main() {
#[tracing::instrument(skip_all, fields(account_id = %account_id))]
pub async fn sync_account(
    account_id: AccountId,
    client: &BillingClient,
) -> Result<(), SyncError> {
    let invoices = client.list_invoices(account_id).await.map_err(|err| {
        tracing::error!(error = ?err, "account sync failed");
        SyncError::ListInvoices(err)
    })?;

    tracing::info!(invoice_count = invoices.len(), "account sync complete");
    Ok(())
}
}

Exceptions

  • Send CLI output through the command output path; justify direct printing with a local lint expectation.
  • Temporarily add DEBUG detail during investigation, then retain only durable signal.
  • Use metrics or durable events for alerts, billing, audit, or product behavior.