How to Debug Code Running Inside microsandbox: A Complete Guide

Set --log-level debug via the CLI or SDK, then stream live logs with msb logs -f or the log_stream API to observe guest execution, runtime traces, and kernel messages in real time.

The microsandbox project provides multiple debugging layers for both host-side tooling and guest code running inside micro-VMs. Whether you're using the CLI, Rust SDK, Python SDK, Node-TS SDK, or Go SDK, the same core observability primitives apply: configurable log levels, structured tracing, and programmatic log streaming. This guide walks through each debugging mechanism based on the actual source implementation in the superradcompany/microsandbox repository.

Understanding the Log-Level Architecture

Every debugging session starts with log-level configuration. This flows from the CLI/SDK through to the runtime, ultimately controlling what gets emitted from three sources: the exec process (your guest code), the runtime (the microsandbox host), and the kernel (the underlying VM).

CLI Entry Point: Parsing --log-level

The msb run command accepts a --log-level flag that sets the baseline verbosity for all subsequent operations.

// crates/cli/lib/commands/run.rs#L45-L48
// The run command struct includes log-level parsing:
#[derive(Parser)]
pub struct RunArgs {
    #[arg(long, value_enum, default_value = "info")]
    pub log_level: LogLevel,
}

This value propagates through to the runtime via the spawn module.

Runtime Translation: From Enum to CLI Flag

In crates/runtime/lib/spawn.rs, the SandboxLogLevel stored in the sandbox spec is converted into runtime flags:

// crates/runtime/lib/spawn.rs#L2824-L2830
fn sandbox_log_level_cli_flag(level: SandboxLogLevel) -> &'static str {
    match level {
        SandboxLogLevel::Error => "--error",
        SandboxLogLevel::Warn => "--warn",
        SandboxLogLevel::Info => "--info",
        SandboxLogLevel::Debug => "--debug",
        SandboxLogLevel::Trace => "--trace",
    }
}

This ensures the sandbox process launches with the correct verbosity flag at line 2824-2830 of the spawn implementation.

SDK Propagation: Builder Pattern Across Languages

All SDKs expose a log_level method that sets this value in the sandbox specification:

SDK Method Source Location
Rust builder.log_level(LogLevel::Debug) sdk/rust/lib/sandbox/builder.rs#L375-L382
Python builder.log_level("debug") sdk/python/src/helpers.rs#L485-L499
Node-TS sandboxBuilder.logLevel("debug") [sdk/node-ts/native/sandbox_builder.rs](https://github.com/superradcompany/microsandbox/blob/main/sdk/node-ts/native/sandbox_builder.rs)
Go builder.WithLogLevel(microsandbox.LogLevelDebug) [sdk/go/native/src/lib.rs](https://github.com/superradcompany/microsandbox/blob/main/sdk/go/native/src/lib.rs)

The Rust SDK's builder stores the log level directly into the sandbox spec with type conversion:

// sdk/rust/lib/sandbox/builder.rs#L375-L382
pub fn log_level(mut self, level: LogLevel) -> Self {
    self.spec.log_level = Some(match level {
        LogLevel::Error => SandboxLogLevel::Error,
        LogLevel::Warn => SandboxLogLevel::Warn,
        LogLevel::Info => SandboxLogLevel::Info,
        LogLevel::Debug => SandboxLogLevel::Debug,
        LogLevel::Trace => SandboxLogLevel::Trace,
    });
    self
}

Reading Sandbox Logs: CLI and Programmatic Access

Once a sandbox is running, its output streams are captured to the logs/ directory. You can access these through either the CLI or SDK APIs.

CLI: The msb logs Command

The msb logs subcommand aggregates and streams logs from all sources:


# Read all logs for a sandbox

msb logs my-sandbox

# Follow logs in real time (like tail -f)

msb logs -f my-sandbox

The tracing initialization that enables this happens in:

// crates/cli/lib/log_args.rs#L48-L50
// Sets up the subscriber with the requested level
let subscriber = tracing_subscriber::fmt()
    .with_max_level(log_level)
    .finish();

SDK: Programmatic Log Streaming

For debugging automation, use the log_stream API. This provides cursor-based resumption — you can stop and restart log consumption without losing your place.

The Rust SDK implementation demonstrates full usage:

use microsandbox::{
    Builder, 
    LogLevel, 
    logs::{LogStreamOptions, LogStreamStart}
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Build with debug logging enabled
    let sandbox = Builder::new("debug-demo")
        .log_level(LogLevel::Debug)
        .build()
        .await?;
    
    // 2. Start the sandbox
    sandbox.start().await?;
    
    // 3. Stream logs with cursor support
    let opts = LogStreamOptions {
        start: LogStreamStart::Now,  // or FromCursor(cursor) to resume
        include_rotated: true,         // include archived log files
        ..Default::default()
    };
    
    let mut stream = sandbox.log_stream(&opts).await?;
    while let Some(entry) = stream.next().await {
        println!("[{}] {}", entry.timestamp, entry.message);
    }
    
    Ok(())
}

This pattern is validated in the test suite at sdk/rust/tests/log_stream.rs#L90-L124.

Python SDK: Equivalent Approach

from microsandbox import SandboxBuilder, LogLevel

builder = SandboxBuilder("py-debug")
builder.log_level(LogLevel.Debug)
sandbox = builder.build()

sandbox.start()

# Follow logs with automatic reconnection

for entry in sandbox.log_stream(
    follow=True,
    include_rotated=True,
    start="now"  # or provide cursor string to resume

):
    print(f"[{entry.timestamp}] {entry.source}: {entry.message}")

Debugging the Guest Execution Lifecycle

The runtime emits detailed tracing events at key lifecycle points. With LogLevel::Debug or LogLevel::Trace, you'll see:

Event Source File Description
Sandbox spawn crates/runtime/lib/spawn.rs#L2423-L2425 Command assembly and process launch
Process signals crates/runtime/lib/runtime/handle.rs#L136-L148 SIGTERM, SIGKILL delivery and handling
Sandbox start confirmation sdk/rust/lib/sandbox/mod.rs#L813-L815 Successful initialization logging

The handle module demonstrates debug-level signal tracing:

// crates/runtime/lib/runtime/handle.rs#L136-L148
tracing::debug!(
    sandbox_id = %self.id,
    pid = %child_pid,
    signal = ?sig,
    "delivering signal to sandbox process"
);

Step-by-Step Debugging Workflow

Follow this sequence when troubleshooting code inside microsandbox:

  1. Enable debug logging at build time

    • CLI: msb --log-level debug run <spec>
    • SDK: builder.log_level(LogLevel::Debug) before calling build()
  2. Start the sandbox and capture initial output

    • The first lines of exec.log show environment setup and command execution
  3. Stream logs during execution

    • CLI: msb logs -f <sandbox> in a separate terminal
    • SDK: Use log_stream() with follow=True for programmatic monitoring
  4. Inspect exit status and rotated logs

    • Check msb status <sandbox> or the API return value
    • Use include_rotated=True in log options to see earlier iterations
  5. Escalate to trace level if needed

    • LogLevel::Trace includes kernel-level VMM messages (very verbose, use sparingly)

Key Source Files for Debugging Reference

Component File Purpose
CLI command handling [crates/cli/lib/commands/run.rs](https://github.com/superradcompany/microsandbox/blob/main/crates/cli/lib/commands/run.rs) Parses --log-level and other debug flags
Tracing initialization [crates/cli/lib/log_args.rs](https://github.com/superradcompany/microsandbox/blob/main/crates/cli/lib/log_args.rs) Configures subscriber with requested level
Sandbox spawning [crates/runtime/lib/spawn.rs](https://github.com/superradcompany/microsandbox/blob/main/crates/runtime/lib/spawn.rs) Translates SandboxLogLevel to runtime flags
Process lifecycle [crates/runtime/lib/runtime/handle.rs](https://github.com/superradcompany/microsandbox/blob/main/crates/runtime/lib/runtime/handle.rs) Signal handling with debug traces
Rust SDK builder [sdk/rust/lib/sandbox/builder.rs](https://github.com/superradcompany/microsandbox/blob/main/sdk/rust/lib/sandbox/builder.rs) Stores log level in specification
Log streaming API [sdk/rust/lib/sandbox/mod.rs](https://github.com/superradcompany/microsandbox/blob/main/sdk/rust/lib/sandbox/mod.rs) Implements log_stream() method
Streaming tests [sdk/rust/tests/log_stream.rs](https://github.com/superradcompany/microsandbox/blob/main/sdk/rust/tests/log_stream.rs) Reference implementation for cursor handling
Python bindings [sdk/python/src/helpers.rs](https://github.com/superradcompany/microsandbox/blob/main/sdk/python/src/helpers.rs) Parses log_level from Python to Rust

Summary

  • Log levels flow uniformly from CLI/SDK → spec → runtime flags → tracing output
  • --log-level debug is the primary switch for troubleshooting guest execution
  • msb logs -f and log_stream() provide live, resumable access to sandbox output
  • Three log sources exist: exec.log (guest), runtime.log (host), and kernel.log (VMM)
  • Cursor-based streaming lets you resume log consumption across disconnections

Frequently Asked Questions

What's the difference between host logging and sandbox logging?

Host logging (controlled by --log-level on the CLI) traces the microsandbox tooling itself — command parsing, spec building, and runtime coordination. Sandbox logging (set via builder.log_level()) controls what the guest VM and its hypervisor emit. Both use the same verbosity levels but target different components. You typically want host-level Debug for troubleshooting connection issues, and sandbox-level Debug for guest code problems.

Can I change the log level of a running sandbox?

No — the log level is fixed at sandbox creation time when the spec is built. This is enforced by the spawn module at crates/runtime/lib/spawn.rs#L2423-L2425. To change verbosity, you must stop the sandbox and recreate it with a different log_level setting.

How do I debug a sandbox that crashes immediately?

Use the cursor-based log streaming with include_rotated=True. Even if the sandbox terminates before you attach, the logs are preserved in the logs/ directory. Retrieve them with msb logs <sandbox> or resume from the start with LogStreamStart::Beginning in the SDK. Also check the exit status via msb status — non-zero codes indicate guest process failure versus runtime errors.

What do I do if trace output is too noisy?

Filter by log source after collection. The LogEntry struct returned by log_stream() includes a source field ("exec", "runtime", or "kernel"). You can programmatically filter these, or simply use LogLevel::Debug instead of Trace — Debug omits the most verbose kernel VMM messages while preserving guest stdout/stderr and runtime events.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →