Skip to content

Commit 0b6907c

Browse files
committed
docs: Add logging guide and update references
Add docs/guides/logging.md covering stream_logs() and the aviato logs CLI command. Add interactive_streaming_sandbox.py example for log streaming. Update execution.md, sync-vs-async.md, troubleshooting.md, mkdocs.yml nav, and AGENTS.md files with new entries.
1 parent 2084092 commit 0b6907c

File tree

10 files changed

+131
-3
lines changed

10 files changed

+131
-3
lines changed

AGENTS.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ with Sandbox.run("sleep", "infinity") as sb:
3535
print(line, end="")
3636
result = process.result() # Get final ProcessResult
3737

38+
# Streaming logs
39+
with Sandbox.run("sleep", "infinity") as sb:
40+
for line in sb.stream_logs(follow=True, tail_lines=100):
41+
print(line, end="")
42+
3843
# Async context manager
3944
async with Sandbox.run("sleep", "infinity") as sb:
4045
result = await sb.exec(["echo", "hello"])
@@ -46,6 +51,7 @@ Key methods:
4651
- `wait()`: Block until RUNNING status, returns self for chaining
4752
- `wait_until_complete(timeout=None, raise_on_termination=True)`: Wait until terminal state (COMPLETED, FAILED, TERMINATED), return `OperationRef[Sandbox]`. Call `.result()` to block or `await` in async contexts. Set `raise_on_termination=False` to handle externally-terminated sandboxes without raising `SandboxTerminatedError`.
4853
- `exec(command, cwd=None, check=False, timeout_seconds=None, stdin=False)`: Execute command, return `Process`. Call `.result()` to block for `ProcessResult`. Iterate `process.stdout` before `.result()` for real-time streaming. Set `check=True` to raise `SandboxExecutionError` on non-zero returncode. Set `cwd` to an absolute path to run the command in a specific working directory (implemented via shell wrapping, requires /bin/sh in container). Set `stdin=True` to enable stdin streaming via `process.stdin`.
54+
- `stream_logs(follow=False, tail_lines=None, since_time=None, timestamps=False)`: Stream logs from sandbox, return `StreamReader` directly. Iterate synchronously with `for line in reader` or asynchronously with `async for line in reader`. Set `follow=True` for continuous streaming (like `tail -f`). Set `timestamps=True` to prefix lines with ISO 8601 timestamps.
4955
- `read_file(path)`: Return `OperationRef[bytes]`
5056
- `write_file(path, content)`: Return `OperationRef[None]`
5157
- `stop(snapshot_on_stop=False, graceful_shutdown_seconds=10.0, missing_ok=False)`: Stop sandbox and return `OperationRef[None]`. Raises `SandboxError` on failure. Set `snapshot_on_stop=True` to capture sandbox state before shutdown. Set `missing_ok=True` to suppress `SandboxNotFoundError`.
@@ -140,7 +146,7 @@ data = await ref
140146
**Exec Types** (`_types.py`): Types for command execution, returned by `Sandbox.exec()`:
141147

142148
- `Process`: Handle for running process with `stdout`/`stderr` StreamReaders and optional `stdin` StreamWriter. Properties: `returncode` (exit code or None), `command` (list executed), `stdin` (StreamWriter when `stdin=True`, or None). Methods: `poll()`, `wait(timeout)`, `result(timeout)`, `cancel()`. Awaitable in async contexts.
143-
- `StreamReader`: Dual sync/async iterable wrapping asyncio.Queue. Supports both `for line in reader` and `async for line in reader`.
149+
- `StreamReader`: Dual sync/async iterable wrapping asyncio.Queue. Supports both `for line in reader` and `async for line in reader`. Exception instances in the queue are re-raised to the consumer (used by `stream_logs()` to propagate errors).
144150
- `StreamWriter`: Writable stream for stdin. Methods: `write(data: bytes)`, `writeline(text: str)`, `close()`. All return `OperationRef[None]`. Property: `closed` (bool). Uses bounded queue (16 items, ~1MB with 64KB chunks) for backpressure.
145151
- `ProcessResult`: Dataclass with `stdout`, `stderr`, `returncode`, `command`, plus raw byte variants (`stdout_bytes`, `stderr_bytes`).
146152

@@ -298,12 +304,15 @@ Commands:
298304
|---------|------|-------------|
299305
| `aviato exec` | `cli/exec.py` | Execute a command in a sandbox (`--cwd`, `--timeout`) |
300306
| `aviato list` | `cli/list.py` | List sandboxes with optional filters (`--status`, `--tag`, `--runway-id`, `--tower-id`) |
307+
| `aviato logs` | `cli/logs.py` | Stream container logs (`--follow`, `--tail`, `--since`, `--timestamps`) |
301308

302309
```bash
303310
aviato exec <sandbox-id> echo hello # Run a command
304311
aviato exec <sandbox-id> --cwd /app ls -la # Run with working directory
305312
aviato list # List all sandboxes
306313
aviato list --status running --tag my-project # Filter by status and tag
314+
aviato logs <sandbox-id> --follow # Stream logs
315+
aviato logs <sandbox-id> --tail 50 --timestamps
307316
```
308317

309318
Adding new CLI commands:
@@ -377,6 +386,7 @@ AviatoError
377386

378387
The `examples/` directory contains runnable scripts demonstrating common patterns:
379388
- `quick_start.py`, `basic_execution.py`, `streaming_exec.py`, `stdin_streaming.py` - Sandbox creation and execution
389+
- `interactive_streaming_sandbox.py` - Log streaming with exec interaction
380390
- `function_decorator.py` - Remote function execution with `@session.function()`
381391
- `multiple_sandboxes.py` - Session-based parallel execution
382392
- `reconnect_to_sandbox.py`, `async_patterns.py` - Discovery and reconnection

DEVELOPMENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ aviato --version # Show SDK version
151151
aviato list # List sandboxes
152152
aviato list --status running --tag dev # Filter sandboxes
153153
aviato exec <sandbox-id> echo hello # Run a command in a sandbox
154+
aviato logs <sandbox-id> --follow # Stream container logs
154155
```
155156

156157
You can also invoke it as a Python module:

docs/guides/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Task-oriented guides for common operations. Each guide answers "How do I...?"
1818
| `environment-variables.md` | Use environment variables in sandboxes |
1919
| `swebench.md` | Run SWE-bench evaluations with parallel Aviato sandboxes |
2020
| `rl-training.md` | RL training with code execution rewards, TRL integration |
21+
| `logging.md` | Stream sandbox container logs with `stream_logs()` and `aviato logs` CLI |
2122

2223
## Key Patterns
2324

docs/guides/execution.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ aviato exec <sandbox-id> --timeout 30 python script.py
313313

314314
Exits with the command's return code.
315315

316+
!!! note "exec stdout vs container logs"
317+
`exec()` streams output from a specific command. For container-level logs (PID 1 stdout/stderr),
318+
use [`stream_logs()`](logging.md) instead.
319+
316320
## Process Control
317321

318322
The `Process` object provides methods for monitoring and control:

docs/guides/logging.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2025 CoreWeave, Inc.
3+
SPDX-License-Identifier: Apache-2.0
4+
SPDX-PackageName: aviato-client
5+
-->
6+
7+
# Sandbox Logging
8+
9+
Stream logs from a running sandbox with `stream_logs()`. Returns a `StreamReader` that yields log lines — iterate synchronously or asynchronously.
10+
11+
## Retrieve recent logs
12+
13+
```python
14+
for line in sandbox.stream_logs(tail_lines=100):
15+
print(line, end="")
16+
```
17+
18+
## Follow mode
19+
20+
Stream logs continuously, like `tail -f`. The iterator blocks until new data arrives.
21+
22+
```python
23+
for line in sandbox.stream_logs(follow=True):
24+
print(line, end="")
25+
```
26+
27+
Press Ctrl-C to stop when iterating in follow mode.
28+
29+
## Filter by time
30+
31+
Only retrieve logs after a specific timestamp.
32+
33+
```python
34+
from datetime import datetime, timezone
35+
36+
since = datetime(2026, 2, 20, 14, 0, 0, tzinfo=timezone.utc)
37+
for line in sandbox.stream_logs(since_time=since):
38+
print(line, end="")
39+
```
40+
41+
## Timestamps
42+
43+
Prefix each line with an ISO 8601 timestamp from the server.
44+
45+
```python
46+
for line in sandbox.stream_logs(tail_lines=10, timestamps=True):
47+
print(line, end="")
48+
# Output: 2026-02-20T14:30:00Z some log line
49+
```
50+
51+
## Async iteration
52+
53+
```python
54+
async for line in sandbox.stream_logs(follow=True):
55+
print(line, end="")
56+
```
57+
58+
## CLI
59+
60+
The `aviato logs` command wraps `stream_logs()` for terminal use:
61+
62+
```bash
63+
aviato logs <sandbox-id> # Recent logs
64+
aviato logs <sandbox-id> --follow # Follow mode
65+
aviato logs <sandbox-id> --tail 50 # Last 50 lines
66+
aviato logs <sandbox-id> --timestamps # With timestamps
67+
aviato logs <sandbox-id> --since "2026-02-20 14:00:00"
68+
```
69+
70+
## What are container logs?
71+
72+
Container logs capture stdout and stderr from the sandbox's main process (PID 1). Commands run via `exec()` produce output on the exec stream, not container logs. To generate logs visible to `stream_logs()`, your sandbox command must write to stdout or stderr.
73+
74+
## See also
75+
76+
- [Command Execution](execution.md) — running commands with `exec()`
77+
- [Sync vs Async](sync-vs-async.md) — iteration patterns

docs/guides/sync-vs-async.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ and stdin.
4242
`session.sandbox()` returns an unstarted sandbox. The sandbox auto-starts on the first operation
4343
that needs it:
4444

45-
**Triggers auto-start**: `exec()`, `read_file()`, `write_file()`, `wait()`, `wait_until_complete()`
45+
**Triggers auto-start**: `exec()`, `read_file()`, `write_file()`, `wait()`, `wait_until_complete()`, `stream_logs()`
4646

4747
**Does not auto-start**: `get_status()` (raises `SandboxNotRunningError`), `stop()` (no-op if never started)
4848

@@ -307,6 +307,22 @@ print(f"Sandbox is {status}") # e.g. SandboxStatus.RUNNING
307307
result = await sb.exec(["echo", "adopted"])
308308
```
309309

310+
### stream_logs()
311+
312+
=== "Sync"
313+
314+
```python
315+
for line in sandbox.stream_logs(follow=True, tail_lines=100):
316+
print(line, end="")
317+
```
318+
319+
=== "Async"
320+
321+
```python
322+
async for line in sandbox.stream_logs(follow=True, tail_lines=100):
323+
print(line, end="")
324+
```
325+
310326
### Streaming stdout
311327

312328
=== "Sync"

docs/guides/troubleshooting.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,22 @@ See [Cleanup Patterns - Orphan Management](cleanup-patterns.md#orphan-management
179179

180180
---
181181

182+
## CLI Issues
183+
184+
### aviato logs: no output
185+
186+
**Issue**: `aviato logs` shows nothing.
187+
188+
**Causes and solutions**:
189+
190+
| Cause | Solution |
191+
|-------|----------|
192+
| Sandbox not yet running | Wait for RUNNING status before streaming logs |
193+
| Container writes to files, not stdout | Logs only capture PID 1 stdout/stderr. Use `exec()` to read files instead |
194+
| Log data not yet flushed | Use `--follow` to wait for new output |
195+
196+
---
197+
182198
## Common Error Messages
183199

184200
| Error | Cause | Solution |

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ nav:
8181
- Sessions: guides/sessions.md
8282
- Remote Functions: guides/remote-functions.md
8383
- Sandbox Configuration: guides/sandbox-configuration.md
84+
- Sandbox Logging: guides/logging.md
8485
- Cleanup Patterns: guides/cleanup-patterns.md
8586
- Sync vs Async: guides/sync-vs-async.md
8687
- Troubleshooting: guides/troubleshooting.md

src/aviato/AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ aviato/
2525
├── exceptions.py # Exception hierarchy
2626
├── py.typed # PEP 561 type information marker
2727
└── cli/ # CLI subpackage (only loaded when `aviato` command is invoked)
28-
└── __init__.py # Click group, registers commands
28+
├── __init__.py # Click group, registers commands
29+
└── logs.py # aviato logs command
2930
```
3031

3132
## Naming Conventions

tests/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ Tests skip gracefully with clear messages when no auth is configured. The `requi
112112
| `test_cli_main.py` | `__main__` entry point, --help, --version, ImportError fallback |
113113
| `test_cli_list.py` | CLI list command, filters, empty state, API errors |
114114
| `test_cli_exec.py` | CLI exec command, stdout, stderr, returncode, cwd, timeout, not-found error |
115+
| `test_cli_logs.py` | CLI logs command, follow, tail, since, timestamps, error handling |
115116

116117
### Integration Test Files (`tests/integration/aviato/`)
117118

0 commit comments

Comments
 (0)