aiod writes JSON logs to stdout, serves Prometheus metrics on GET /metrics, and exports OpenTelemetry traces when an endpoint is configured. Nothing is pushed by default.
One JSON line per request on stdout, tagged HTTP_REQUEST and carried on the app.request target. The payload:
| Field | Meaning |
|---|---|
method, path, route | The request, and the path template it matched |
status, duration_ms | The HTTP status, and time to answer in milliseconds |
logid, client_ip | The request id, and the caller's address |
success, error_kind, error_message | The outcome, and why it failed |
query, path_params, request, response | What came in and what went back |
trace_id, span_id | Added while trace export is on |
Credentials are redacted before the line is written. A query parameter or body field whose name contains a known marker — api_key, authorization, token, password, secret, cookie, ticket, and the rest of that family — is replaced by its length and a short SHA-256, enough to tell two values apart without carrying either:
Only a bounded JSON body is summarized like that: application/json, an explicit Content-Length, and at most 8 KiB. An upload, a stream, a WebSocket handshake, and anything larger pass through untouched, and the entry says so with captured: false and a reason. The body still reaches the handler byte for byte.
aiod takes the request id from x-tt-logid, x-logid, x-log-id, or x-request-id, and generates one when the request carries none. It answers with the id in x-tt-logid and forwards it to computer-use, so one id spans both processes:
| Variable | Default | Effect |
|---|---|---|
AIO_RUST_LOG_LEVEL | info | Log level, or any tracing filter directive such as aio_daemon=debug,info |
Prometheus text format, rendered on request. /metrics requires the API key when one is set. Four series:
| Series | Type | Labels |
|---|---|---|
http_requests_total | Counter | method, route, status |
http_request_duration_seconds | Histogram | None; one latency distribution for the process |
command_exec_total | Counter | None |
command_exec_duration_seconds | Histogram | None |
Both histograms share the same buckets, 1 ms to 30 s. A request that matched no route folds into a single <unmatched> label, so scanning for endpoints cannot blow up the series count.
Off by default. Enable with one variable:
| Variable | Default | Effect |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | unset | Set to enable trace export |
OTEL_EXPORTER_OTLP_PROTOCOL | grpc | grpc or http/protobuf |
OTEL_EXPORTER_OTLP_HEADERS | unset | Headers the collector authenticates with |
OTEL_EXPORTER_OTLP_TIMEOUT | 10000 | Export timeout, in milliseconds |
OTEL_SERVICE_NAME | aiod (computer-use on the worker) | Service name on spans |
OTEL_RESOURCE_ATTRIBUTES | unset | Extra resource attributes |
OTEL_SDK_DISABLED | unset | true keeps export off with an endpoint set |
The signal-specific _TRACES_ spelling of a variable wins over the generic one. Under http/protobuf the generic endpoint is a base URL that gets /v1/traces appended, while OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is used as it stands. These variables are the only ones read: nothing is inferred, and there is no default endpoint.
One span per request, otel.kind server, carrying http.method and http.route. W3C trace context is extracted from the request and propagated onward, so a caller's trace continues through the daemon, and trace_id and span_id appear in the JSON logs while export is on. GUI requests forwarded to computer-use carry the context too, and one trace shows the whole aiod → computer-use call.
Only plaintext http:// collectors are supported: no TLS backend is compiled in.
/metrics asks forerror_kind reports