Capsa is experimental software. APIs may change without notice.
Skip to content

Logging Guidelines

Capsa uses the tracing crate for structured logging in library and daemon code, with println!/eprintln! reserved for user-facing CLI output.

When to Use Each Mechanism

MechanismWhen to Use
println!User-facing CLI output (command results, prompts, formatted data)
eprintln!CLI errors/warnings shown to user, test skip messages
tracingAll internal diagnostics, library code, daemon processes

Special Cases

  • Guest-side code (capsa-sandbox-init, capsa-sandbox-agent): Use println!/eprintln! only. These run in minimal VM environments where tracing setup adds unnecessary complexity.
  • Build scripts: Use println! for cargo directives only.
  • Tests: Use eprintln! for skip messages (e.g., eprintln!("Skipping: no /dev/kvm")).

Log Levels

LevelWhen to UseExample
error!Unrecoverable failures, bugs, data corruptionerror!("Failed to create VM: {}", e)
warn!Recoverable errors, degraded operation, unexpected but handledwarn!("Connection reset, retrying")
info!Service lifecycle, significant state changes, config summaryinfo!("VM started")
debug!Flow tracing, connection lifecycle, operational detailsdebug!("NAT: {} -> {}", src, dst)
trace!Hot paths, packet-level, verbose internalstrace!("vCPU exit: {:?}", exit)

Rule of thumb: If you'd want to see it in production logs by default, use info!. If only when debugging, use debug! or trace!.

Initializing Tracing

All binaries that use tracing should follow this pattern:

rust,ignore
use tracing_subscriber::EnvFilter;

fn init_logging() {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| EnvFilter::new("info"))
        )
        .with_writer(std::io::stderr)
        .init();
}

Key points:

  • Default to info level (avoid debug/trace spam by default)
  • Write to stderr (stdout is for program output)
  • Respect RUST_LOG environment variable for override

Structured Logging

Use structured fields for machine-parseable data:

rust,ignore
// Preferred: structured fields + human message
tracing::warn!(port = port, error = %e, "Failed to bind listener");
tracing::debug!(src = %guest_addr, dst = %remote_addr, "NAT connection");

// Avoid: embedding data only in message
tracing::warn!("Failed to bind listener on port {}: {}", port, e);

Field Naming Conventions

FieldUsage
error, errError values (use % for Display: error = %e)
port, addr, pathNetwork/filesystem identifiers
vm_id, conn_idResource identifiers

Use snake_case for all field names.

Subprocess Logging

Capsa spawns subprocesses (e.g., capsa-vmm on macOS and optionally on Linux). Follow these guidelines:

Subprocess Output Routing

  • Subprocesses should write logs to stderr, never stdout
  • The parent process can capture stderr and either:
    • Forward it to its own tracing (preferred for integration)
    • Pass it through to the user's stderr (simpler)
  • Stdout should be reserved for structured IPC (if any)

Log Correlation

When spawning subprocesses, include identifiers to correlate logs:

rust,ignore
// Parent spawns child with VM ID in environment
cmd.env("CAPSA_VM_ID", vm_id);

// Child includes VM ID in logs
let vm_id = std::env::var("CAPSA_VM_ID").unwrap_or_default();
tracing::info!(vm_id = %vm_id, "Subprocess started");

Error Propagation

  • Subprocesses should exit with non-zero status on fatal errors
  • Log the error before exiting so it's captured
  • Parent should check exit status AND captured stderr for diagnostics

Current Subprocess Behavior

SubprocessLoggingOutput
capsa-vmmtracing to stderrRespects RUST_LOG
capsa-sandbox-initprintln to stdoutVisible on VM console
capsa-sandbox-agentprintln to stdoutVisible on VM console

What NOT to Log

  • Sensitive data (credentials, tokens, keys)
  • High-frequency events at info! level or above (use debug! or trace!)
  • Redundant information already in structured fields

Adding Tracing to a Crate

  1. Add the dependency (it's in the workspace):

    toml
    [dependencies]
    tracing = { workspace = true }
  2. For binaries, also add:

    toml
    [dependencies]
    tracing-subscriber = { version = "0.3", features = ["env-filter"] }
  3. Library crates should NOT initialize tracing - that's the binary's responsibility.

Released under the MIT License.