Skip to main content
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:
  • 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.
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.

Filter Logs

Use the left sidebar or the search bar to narrow the log stream. Common filters:
FilterHow to use it
Log levelSelect levels in the sidebar, or search with level:error, level:warn, level:info, or level:debug.
ProcessSelect a process in the sidebar, or search with processId:<process-id>.
Text in the messageSearch 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.
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.
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:
logs = sandbox.get_logs(body="document extraction", tail=50).value

for log in logs.logs:
    print(log.body)
    print(log.log_attributes)
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));
}
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.

CLI

Use tl sbx logs to read retained process logs from a sandbox.
# 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:
tl sbx logs <sandbox-id-or-name> --level warn --tail 25 --json
To find stable process IDs for filtering, list the available log streams:
tl sbx logs streams <sandbox-id-or-name>
Then pass one or more process IDs back to tl sbx logs:
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:
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.
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:
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:
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:
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.
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:
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:
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:
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:
curl "https://api.tensorlake.ai/v1/namespaces/$PROJECT_ID/sandboxes/$SANDBOX_ID/logs?tail=100" \
  -H "Authorization: Bearer $TENSORLAKE_API_KEY"
Filter by level:
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:
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:
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:
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.
{
  "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.