Skip to main content
Start with the Runloop Quickstart to use the examples below.

Overview

When executing commands on a Devbox, you have two primary ways to access logs:
  1. Log Streaming: Receive stdout and stderr output in real-time as the command runs
  2. Completed Execution Logs: Retrieve the full output after a command has finished

Real-Time Log Streaming

For long-running commands like builds, tests, or servers, you can stream output as it’s generated using callback functions.

Streaming Combined Output

Use the output callback to receive both stdout and stderr combined:
command = await devbox.cmd.exec_async(
    "npm run build",
    output=lambda line: print(f"[LOG] {line}")
)

Streaming Stdout and Stderr Separately

For more control, use separate callbacks for stdout and stderr:
command = await devbox.cmd.exec_async(
    "npm run build",
    stdout=lambda line: print(f"[STDOUT] {line}"),
    stderr=lambda line: print(f"[STDERR] {line}")
)
Streaming is ideal for commands where you want immediate feedback, such as build processes, test suites, or log tailing.
By default, commands execute in the /home/user directory. Use Named Shells to maintain a working directory across multiple commands.

Streaming with Named Shells

Named shells also support log streaming:
shell = devbox.shell("build-session")

result = await shell.exec(
    "npm install && npm run build",
    stdout=lambda line: print(f"[BUILD] {line}"),
    stderr=lambda line: print(f"[ERROR] {line}")
)

Getting Logs from Completed Executions

After a command finishes, you can retrieve the complete stdout and stderr output, or just the last N lines.

Retrieving Full Output

result = await devbox.cmd.exec("npm run test")

# Get full stdout
stdout = await result.stdout()
print(f"Test output:\n{stdout}")

# Get full stderr
stderr = await result.stderr()
if stderr:
    print(f"Errors:\n{stderr}")

# Check exit code
exit_code = await result.exit_code()
print(f"Exit code: {exit_code}")

Retrieving Last N Lines

When you only need the most recent logs, you can specify the number of lines to retrieve. This is more efficient as it avoids fetching the entire output:
result = await devbox.cmd.exec("npm run test")

# Get last 10 lines of stdout
last_lines = await result.stdout(10)
print(f"Last 10 lines:\n{last_lines}")

# Get last 5 lines of stderr
recent_errors = await result.stderr(5)
if recent_errors:
    print(f"Recent errors:\n{recent_errors}")
Specifying numLines resolves faster than fetching all logs, especially for commands with verbose output. Use this when you only need to check the final status or recent errors.

Retrieving Logs from Async Executions

For commands started with exec_async, you can wait for completion and then retrieve logs:
# Start a long-running command
command = await devbox.cmd.exec_async("npm run build")

# Wait for the command to complete
result = await command.wait()

# Get the complete output
stdout = await result.stdout()
stderr = await result.stderr()
exit_code = await result.exit_code()

print(f"Build completed with exit code: {exit_code}")
print(f"Output:\n{stdout}")
if stderr:
    print(f"Errors:\n{stderr}")

Combining Streaming and Final Logs

You can stream logs in real-time while still having access to the complete output after execution:
# Stream logs while the command runs
result = await devbox.cmd.exec(
    "npm run test",
    stdout=lambda line: print(f"[LIVE] {line}")
)

# After completion, access the full output
full_stdout = await result.stdout()
exit_code = await result.exit_code()

# Process or store the complete logs
if exit_code != 0:
    save_failure_logs(full_stdout)

Best Practices

Use Streaming for Long-Running Commands

For commands that take more than a few seconds, streaming provides immediate feedback:
# Good: Stream output for long builds
await devbox.cmd.exec(
    "npm install",
    output=lambda line: print(line)
)

# Less ideal: Wait for all output at once
result = await devbox.cmd.exec("npm install")
print(await result.stdout())  # No feedback until complete

Store Logs for Debugging

When running automated workflows, store logs for later analysis:
import json
from datetime import datetime

async def run_with_logging(devbox, command):
    logs = []
    
    result = await devbox.cmd.exec(
        command,
        stdout=lambda line: logs.append({"stream": "stdout", "line": line}),
        stderr=lambda line: logs.append({"stream": "stderr", "line": line})
    )
    
    return {
        "command": command,
        "exit_code": await result.exit_code(),
        "logs": logs,
        "timestamp": datetime.now().isoformat()
    }

Handle Errors Gracefully

Use the success or failed properties to check execution status, or check the exit code directly:
result = await devbox.cmd.exec("npm run build")

exit_code = await result.exit_code()
if exit_code != 0:
    stderr = await result.stderr()
    stdout = await result.stdout()
    raise BuildError(
        f"Build failed with exit code {exit_code}\n"
        f"stderr: {stderr}\n"
        f"stdout: {stdout}"
    )
For more information about command execution, see the Execute Commands documentation. For stateful shell sessions, see Named Shells.