> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tensorlake.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox Process Logs

> View, search, and filter stdout and stderr from sandbox processes in the Tensorlake Console

Sandbox process logs give you a searchable history of the output produced by processes in a sandbox. Use them when you need to debug a long-running agent, inspect a failed setup command, compare logs across process restarts, or share what happened in a sandbox with your team.

Process logs are different from the per-process output APIs in [Commands & Processes](/sandboxes/commands):

* **Process logs** are retained telemetry for the sandbox. They are best for browsing, filtering, searching, and debugging after the fact.
* **Process output** is the buffered stdout, stderr, or combined stream for one tracked process. It is best when your application needs to programmatically wait for or stream a specific process.

## Open Process Logs

1. Open the Tensorlake Console.
2. Go to your project.
3. Open **Sandboxes**.
4. Select the sandbox you want to inspect.
5. Open the **Logs** tab.

The Logs tab shows output from sandbox processes as rows with timestamps, log levels, process context, and the log message. New logs appear automatically while live refresh is enabled.

<Note>
  Logs appear after a process writes to stdout or stderr and the log pipeline has ingested the output. Very recent output can take a short moment to show up in the Console.
</Note>

## Filter Logs

Use the left sidebar or the search bar to narrow the log stream.

Common filters:

| Filter              | How to use it                                                                                             |
| ------------------- | --------------------------------------------------------------------------------------------------------- |
| Log level           | Select levels in the sidebar, or search with `level:error`, `level:warn`, `level:info`, or `level:debug`. |
| Process             | Select a process in the sidebar, or search with `processId:<process-id>`.                                 |
| Text in the message | Search with quoted text, such as `"connection refused"` or `"pip install"`.                               |

The process filter uses the stable `processId` shown by the sandbox process log metadata, not necessarily the operating-system PID. This matters for managed or restarted processes, where a logical process can restart with a new PID while keeping a stable process identifier for log filtering.

## Structured JSON Logs

You can write structured logs by printing one JSON object per line to stdout or stderr. Tensorlake parses JSON log lines, extracts a display message, and stores the remaining JSON fields as structured log attributes.

Use this pattern when you want logs that are still readable in the Console, but also carry machine-readable context such as job IDs, tool names, retry counts, model names, durations, or application-specific status.

```python theme={null}
import json

print(json.dumps({
    "timestamp": "2026-06-29T12:00:00Z",
    "level": "info",
    "message": "started document extraction",
    "document_id": "doc_123",
    "stage": "extract",
    "attempt": 1,
    "duration_ms": 42,
    "metadata": {
        "source": "upload",
        "mime_type": "application/pdf",
    },
}))
```

For the log above:

* `message` becomes the log message shown in the Console and returned as `body`.
* `level` sets the log severity when it is one of `trace`, `debug`, `info`, `warn`, or `error`.
* `timestamp` sets the log timestamp when it is parseable.
* Other JSON keys are preserved in `logAttributes` on the returned log record.
* Nested objects and arrays are preserved as structured attributes. `null` values are omitted.

If there is no `message`, Tensorlake uses `event` as the display message when it is present. Event names with underscores are shown with spaces, so `"event": "tool_call_started"` appears as `tool call started`.

```python theme={null}
print(json.dumps({
    "level": "warn",
    "event": "tool_retry",
    "tool": "browser",
    "attempt": 2,
    "reason": "timeout",
}))
```

Malformed JSON is treated as plain text. To make sure a line is parsed as structured data, emit exactly one complete JSON object per line.

You can inspect structured attributes from the SDK responses:

```python theme={null}
logs = sandbox.get_logs(body="document extraction", tail=50).value

for log in logs.logs:
    print(log.body)
    print(log.log_attributes)
```

```typescript theme={null}
const logs = await sandbox.getLogs({
  body: "document extraction",
  tail: 50,
});

for (const log of logs.logs) {
  console.log(log.body);
  console.log(JSON.parse(log.logAttributes));
}
```

<Note>
  Today, sandbox log search filters by log level, stable process ID, and text in the extracted `body`. Structured attributes are returned with each log record for inspection and downstream processing, but they are not yet separate sandbox log query filters.
</Note>

## CLI

Use `tl sbx logs` to read retained process logs from a sandbox.

```bash theme={null}
# Print the newest 100 retained log lines
tl sbx logs <sandbox-id-or-name> --tail 100

# Filter by severity
tl sbx logs <sandbox-id-or-name> --level error --tail 100

# Search the log message body
tl sbx logs <sandbox-id-or-name> --body "connection refused" --tail 100
```

By default, the CLI prints only each log message body. Add `--json` when you need timestamps, levels, resource attributes, pagination tokens, or other metadata:

```bash theme={null}
tl sbx logs <sandbox-id-or-name> --level warn --tail 25 --json
```

To find stable process IDs for filtering, list the available log streams:

```bash theme={null}
tl sbx logs streams <sandbox-id-or-name>
```

Then pass one or more process IDs back to `tl sbx logs`:

```bash theme={null}
tl sbx logs <sandbox-id-or-name> --process-id <process-id> --tail 100
```

If the JSON response includes `nextToken`, pass it to read the next page:

```bash theme={null}
tl sbx logs <sandbox-id-or-name> --tail 100 --next-token "<next-token>"
```

## Python SDK

Use `Sandbox.get_logs()` when you already have a sandbox object.

```python theme={null}
from tensorlake.sandbox import Sandbox

sandbox = Sandbox.create()
sandbox.run("python", ["-c", "import sys; print('ready'); print('warning', file=sys.stderr)"])

logs = sandbox.get_logs(tail=100, levels=["info", "warn"]).value

for log in logs.logs:
    print(log.body)
```

Filter by retained process stream:

```python theme={null}
processes = sandbox.list_log_processes().value.processes

for process in processes:
    print(process.process_id, process.process_pid, process.process_command)

if processes:
    logs = sandbox.get_logs(
        process_ids=[processes[0].process_id],
        tail=100,
    ).value
```

Search message text and paginate:

```python theme={null}
page = sandbox.get_logs(body="connection refused", tail=100).value

if page.next_token:
    next_page = sandbox.get_logs(
        body="connection refused",
        tail=100,
        next_token=page.next_token,
    ).value
```

If you are using `SandboxClient` directly, pass the sandbox ID:

```python theme={null}
from tensorlake.sandbox import SandboxClient

client = SandboxClient.for_cloud()
logs = client.get_logs("<sandbox-id>", levels=["error"], tail=50).value
```

## TypeScript SDK

Use `sandbox.getLogs()` when you already have a sandbox object.

```typescript theme={null}
import { Sandbox } from "tensorlake";

const sandbox = await Sandbox.create();
await sandbox.run("python", {
  args: ["-c", "import sys; print('ready'); print('warning', file=sys.stderr)"],
});

const logs = await sandbox.getLogs({
  levels: ["info", "warn"],
  tail: 100,
});

for (const log of logs.logs) {
  console.log(log.body);
}
```

Filter by retained process stream:

```typescript theme={null}
const processes = await sandbox.listLogProcesses();

for (const process of processes.processes) {
  console.log(process.processId, process.processPid, process.processCommand);
}

if (processes.processes.length > 0) {
  const processLogs = await sandbox.getLogs({
    processIds: [processes.processes[0].processId],
    tail: 100,
  });
}
```

Search message text and paginate:

```typescript theme={null}
const page = await sandbox.getLogs({
  body: "connection refused",
  tail: 100,
});

if (page.nextToken) {
  const nextPage = await sandbox.getLogs({
    body: "connection refused",
    tail: 100,
    nextToken: page.nextToken,
  });
}
```

## Debug a Failed Process

A typical debugging flow is:

1. Open the sandbox and go to **Processes** to find the command, status, PID, and exit code.
2. Go to **Logs**.
3. Select the matching process in the sidebar.
4. Add a text search such as `"Traceback"`, `"error"`, or the package, file, or command you were investigating.
5. Open a log row to inspect the full message and resource attributes.

For one-off commands, you can also use the SDK or process API to read stdout and stderr directly:

```python theme={null}
from tensorlake.sandbox import Sandbox

sandbox = Sandbox.create()
result = sandbox.run("python", ["-c", "import sys; print('hello'); print('oops', file=sys.stderr)"])

print(result.stdout)
print(result.stderr)
print(result.exit_code)
```

Use the Console Logs tab when you want to keep searching after the command has finished, filter across many processes, or inspect output from a process that was started outside your current SDK call.

## Query Logs with HTTP

The Console uses the sandbox logs endpoint behind the scenes. You can query the same retained logs over HTTP:

```bash theme={null}
curl "https://api.tensorlake.ai/v1/namespaces/$PROJECT_ID/sandboxes/$SANDBOX_ID/logs?tail=100" \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
```

Filter by level:

```bash theme={null}
curl "https://api.tensorlake.ai/v1/namespaces/$PROJECT_ID/sandboxes/$SANDBOX_ID/logs?level=5&tail=100" \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
```

Filter by process:

```bash theme={null}
curl "https://api.tensorlake.ai/v1/namespaces/$PROJECT_ID/sandboxes/$SANDBOX_ID/logs?processId=$PROCESS_ID&tail=100" \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
```

Search message text:

```bash theme={null}
curl "https://api.tensorlake.ai/v1/namespaces/$PROJECT_ID/sandboxes/$SANDBOX_ID/logs?body=connection%20refused&tail=100" \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
```

Log levels are numeric in the HTTP API: `1` trace, `2` debug, `3` info, `4` warn, `5` error, and `6` fatal.

## List Process Filter Values

To build your own process picker, list the processes that have retained logs for a sandbox:

```bash theme={null}
curl "https://api.tensorlake.ai/v1/namespaces/$PROJECT_ID/sandboxes/$SANDBOX_ID/processes" \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
```

The response includes stable process IDs, the last observed PID, command, managed process ID or name when available, first and last observed log times, and the retained log count.

```json theme={null}
{
  "processes": [
    {
      "processId": "proc-a",
      "processPid": "101",
      "processCommand": "/usr/bin/python worker.py",
      "processManagedId": "managed-a",
      "processManagedName": "worker-a",
      "firstSeen": 1773950042728,
      "lastSeen": 1773950049123,
      "logCount": 42
    }
  ]
}
```

Use the returned `processId` with the `processId` logs query parameter.

## When Logs Are Empty

If the Logs tab is empty:

* Confirm the sandbox has started a process that writes to stdout or stderr.
* Check that you are viewing the correct project and sandbox ID.
* Wait a short moment for ingestion if the process just wrote output.
* Use the **Processes** tab or the process output API if you need immediate output from a currently running process.

Logs are retained according to the log retention policy for your environment.
