Logs
This page explains how ctrl, node, the E2B gateway, and the Exposure gateway write logs, how to collect and query them, and where the guest console and VMM logs are kept.
Overview
Each service writes its log to stderr, one JSON record per line. The packaged systemd units hand stderr to journald, and containers hand it to the container runtime. Kibotos does not collect or export logs itself: the deployer runs a collector on each host that sends the records to its own log storage.
The node fetches the records of the Kibotos agent inside the guest and writes them to its own log. The guest console and VMM logs do not enter the log stream. They stay only on the node host. See Console and VMM logs.
Record format
Every record has the following four fields:
| Field | Value |
|---|---|
time | UTC time with millisecond precision, for example 2026-10-06T08:12:03.418Z |
level | DEBUG, INFO, WARN, ERROR |
msg | Event name, for example workspace command settled. Event names are fixed, so you can use them directly in queries and alerts |
component | ctrl, node, guest, e2b, gw |
The remaining fields depend on the event. An example node record:
{"time":"2026-10-06T08:12:03.418Z","level":"WARN","msg":"instance cleanup pending","component":"node","instance_id":"inst-06gfvmpnqsv7xcsfgz7ybx77pg","step":"volume_attachments","attempt":3,"error":"volume is still attached","suppressed":5,"failing_since":"2026-10-06T08:11:02.004Z","node_id":"node-a","incarnation":12}What the levels mean:
| Level | Meaning |
|---|---|
ERROR | Does not recover on its own and needs a person, for example when a command's retry budget is exhausted |
WARN | A failure or degradation that the service retries or has already worked around |
INFO | State changes and completed operations |
DEBUG | Troubleshooting detail, not written by default |
Errors made by the caller (invalid arguments, objects that do not exist) are not logged as WARN or ERROR.
Correlation fields
The same object uses the same field name in the records of every service. Query by that field to connect the services that one operation passed through:
| Field | Object |
|---|---|
workspace_id | Workspace. The E2B gateway also writes the sandbox ID here |
instance_id | The Instance on a node for one execution of a Workspace |
node_id, incarnation | The node and its current run. Every record from the node process carries both fields |
data_plane_generation | A generation of the node's data-plane process. Records from the data-plane process carry it and node_id, but not incarnation: one generation can span several runs of the node |
egress_generation | A generation of the node's egress process. Records from the egress process carry it and node_id, but not incarnation, as with the data-plane process |
egress_id | The Egress that a guest connection is forwarded through |
command_id | A ctrl command. In node records, it is the ID of the command that ctrl sent to the node, for example ctrl/workspace-create-fc0e90a06bf735372522aa4e/create, which contains the ctrl command ID |
control_session_id | One control connection between ctrl and node |
volume_id, transfer_id, exposure_id, template_id, storage_pool | The object of the same name |
principal, serial | The principal URI and certificate serial number of an identity |
guest_boot_id | One boot of the guest |
trace_id, span_id | The trace and span in which the record was written, in lowercase hexadecimal. For when records carry them, see Log fields in Traces |
Duration fields end in _ms and are in milliseconds. error is the error text, and code is the RPC error code. States, reasons, and phases are lowercase, for example running and unavailable.
To trace a Workspace down to its guest:
- In the ctrl records, find
workspace command settledbyworkspace_id. It carriesinstance_id,node_id, andcommand_id. - In the node records, query by
instance_idto see what that node did. A phrase query on the ctrl command ID also matches the command IDs derived from it in node records. - Records under the same
instance_idwhosecomponentisguestcome from the agent inside the guest.
Repeated failures
When the same thing fails repeatedly, the log does not get a line for every failure:
- The first failure is logged immediately.
- After that, the same event for the same object is logged at most once per minute, with
suppressed(the number of failures omitted in that period) andfailing_since(when this round of failures started). - When a retried operation recovers, an
INFOrecord with the same name is logged, withrecovered: trueandfailed_ms(how long it kept failing).
So one line per minute does not mean only one failure. Check suppressed.
Log levels
An environment variable sets the level. The default is info, and the values are debug, info, warn, and error. With an invalid value, the service refuses to start.
| Service | Environment variable |
|---|---|
| ctrl | KIBOTOS_CTRL_LOG_LEVEL |
| node | KIBOTOS_NODE_LOG_LEVEL |
| E2B gateway | KIBOTOS_E2B_LOG_LEVEL |
| Exposure gateway | KIBOTOS_GW_LOG_LEVEL |
For ctrl and node, set it in the service unit. The following command opens the override file of the node unit:
sudo systemctl edit kibotos-node.serviceWrite the following two lines in the file that opens, save it, and restart the node:
[Service]
Environment=KIBOTOS_NODE_LOG_LEVEL=debugThe two gateways set it in their own environment files. See e2b.example.env and gw.example.env.
Viewing on the host
Because journald keeps recent logs, you can query them directly on the host even when the collector is unavailable. The following command shows the node's WARN and ERROR records from the last hour:
journalctl -u kibotos-node --since -1h -o cat | jq -c 'select(.level == "WARN" or .level == "ERROR")'The following command finds all records of one Instance on this node, including those of the guest agent:
journalctl -u kibotos-node -o cat | jq -c 'select(.instance_id == "inst-06gfvmpnqsv7xcsfgz7ybx77pg")'In container deployments, read the same records with kubectl logs or docker logs.
Console and VMM logs
The guest console and the VMM's own logs do not enter the log stream. When an Instance ends, the node first retains them and then cleans up the Instance's runtime directory:
- Location:
<storage.root>/machine-logs/<instance_id>/, for example/var/lib/kibotos/node/machine-logs/inst-06gfvmpnqsv7xcsfgz7ybx77pg/. - They are retained however the Instance ends: a requested stop, a forced stop, the VM exiting on its own, or the VMM failing to start. When the same Instance ends again, its files replace the previous ones.
- Only the last 1 MiB of each file is kept. When the retained files exceed 1 GiB in total or 1000 Instances, the oldest Instance directories are deleted.
- The node log has a single
instance machine exitedrecord, with the exit code or signal, whether the process was killed by OOM, the uptime, andretained(the path of the retained directory). If systemd has already reclaimed the unit after a requested stop and the exit status cannot be found,exit_status: unobservedreplaces the exit code. When the guest resets itself, the node restarts the VMM, and on success writes another recordinstance machine restarted after guest resetwithrestart_countand the newguest_boot_id.
Notes:
- The console may contain output from programs inside the Workspace. Only root can read the retained files. Do not hand them to a log collector.
- These files can be read only on the host. Neither ctrl nor the gateways have an interface to read them.
What logs never contain
The following never appears in a log at any level:
- Process arguments, environment variables, input and output, file contents, paths and file names given by users, and clipboard and screen contents.
- Tokens, private keys, passwords, enrollment keys, API keys, and session credentials. Where a record needs to tell them apart, it logs only the key ID, the certificate serial number, or the first 8 hexadecimal characters of a digest.
- The random part of an Exposure host name. That part is the credential for accessing the Exposure. Logs record
exposure_id, and before the Exposure is resolved, the zone and a prefix of the label digest. - Full URLs with query strings. The gateways log the route pattern, for example
GET /sandboxes/{id}.
Reference deployment: Vector, VictoriaLogs, and Grafana
The reference deployment collects with Vector on each host, writes to single-node VictoriaLogs, and queries with Grafana. Any collector and storage that can read journald or container output and parse it as JSON can replace them.
VictoriaLogs
VictoriaLogs flags:
| Flag | Example | Description |
|---|---|---|
-storageDataPath | /var/lib/victoria-logs | Data directory, on a local disk |
-retentionPeriod | 30d | Retention |
-httpListenAddr | 10.0.0.6:9428 | Listen only on an internal address that the collectors and Grafana can reach |
-httpAuth.username, -httpAuth.password | kibotos, a password | Authentication for writes and queries. If you need separate write and query permissions, put vmauth in front |
Vector
Each host runs one Vector that reads the journald records of the Kibotos services and parses them into fields. The reference configuration is deploy/logging/vector.toml. Change endpoints and it is ready to use.
- Starting with Vector 0.58, environment variables in the configuration are not expanded by default. Put the password in a file that only Vector can read and reference it through Vector's secret file backend, as the reference configuration does.
- Each generation of the node data-plane process is a unit named after its generation number, and all of them are in
kibotos-dataplane.slice. The reference configuration selects them by slice and recordsunitaskibotos-dataplanefor all of them. The generation number is in the record'sdata_plane_generationfield. - The egress process also has one unit per generation, all in
kibotos-egress.slice.unitis recorded askibotos-egressfor all of them, and the generation number is in theegress_generationfield. - The stream fields are
host,component, andunit. Fields such asinstance_idandcommand_idhave very many values, so they are written as ordinary fields. VictoriaLogs can still query by them. - For container deployments, replace the
journaldsource with Vector'skubernetes_logsordocker_logssource. The parsing part stays the same.
Grafana
Install the VictoriaLogs data source plugin victoriametrics-logs-datasource in Grafana, and enter the VictoriaLogs address and password when you add the data source. deploy/logging/grafana-dashboard.json is a dashboard that counts records by component and level and lists the most frequent WARN and ERROR records. You can import it directly.
The dashboard's variable filter takes a LogsQL filter that applies to every panel, * by default. The link from the traces dashboard opens this dashboard by its uid kibotos-logs with filter set to trace_id: plus the Trace ID, so keep that uid when you import it.
Common queries (LogsQL):
| To find | Query |
|---|---|
| All records of a Workspace | workspace_id:"ws-06gcx267w1zffcb3w06zhv5z9c" |
| Records of an Instance on a node, including the guest agent | instance_id:"inst-06gfvmpnqsv7xcsfgz7ybx77pg" |
| Commands that settled with a failure | msg:"workspace command settled" level:WARN |
The most frequent WARN and ERROR records in the last hour | _time:1h level:in(WARN,ERROR) | stats by (component, msg) count() n | sort by (n desc) |
Notes
- The collectors and storage must be reachable from the Kibotos hosts, and tenants must not reach them: the logs contain object IDs and error messages.
- Log volume grows with the number of Workspace operations. Observe one day of writes on one node first, then estimate disk space from the retention period.
Instance metrics storage
This page shows how to connect instance metrics storage so that the E2B SDK can read the CPU, memory, and disk readings of a sandbox, and the native API and kibo workspace metrics can read a Workspace's.
Traces
This page shows how to connect trace storage so that one Workspace command appears as a single timeline in Grafana, and how to configure and troubleshoot each service.