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:
-
Enable debug logging at build time
- CLI:
msb --log-level debug run <spec> - SDK:
builder.log_level(LogLevel::Debug)before callingbuild()
- CLI:
-
Start the sandbox and capture initial output
- The first lines of
exec.logshow environment setup and command execution
- The first lines of
-
Stream logs during execution
- CLI:
msb logs -f <sandbox>in a separate terminal - SDK: Use
log_stream()withfollow=Truefor programmatic monitoring
- CLI:
-
Inspect exit status and rotated logs
- Check
msb status <sandbox>or the API return value - Use
include_rotated=Truein log options to see earlier iterations
- Check
-
Escalate to trace level if needed
LogLevel::Traceincludes kernel-level VMM messages (very verbose, use sparingly)
Key Source Files for Debugging Reference
Summary
- Log levels flow uniformly from CLI/SDK → spec → runtime flags → tracing output
--log-level debugis the primary switch for troubleshooting guest executionmsb logs -fandlog_stream()provide live, resumable access to sandbox output- Three log sources exist:
exec.log(guest),runtime.log(host), andkernel.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →