• English
  • Observability

    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.

    Logs

    One JSON line per request on stdout, tagged HTTP_REQUEST and carried on the app.request target. The payload:

    FieldMeaning
    method, path, routeThe request, and the path template it matched
    status, duration_msThe HTTP status, and time to answer in milliseconds
    logid, client_ipThe request id, and the caller's address
    success, error_kind, error_messageThe outcome, and why it failed
    query, path_params, request, responseWhat came in and what went back
    trace_id, span_idAdded 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:

    {
      "path": "/v1/bash/exec",
      "status": 200,
      "request": {
        "captured": true,
        "size": 42,
        "sha256": "9833e0d76731",
        "params": {
          "command": "echo hi",
          "password": {
            "redacted": true,
            "len": 7,
            "sha256": "f52fbd32b2b3"
          }
        }
      }
    }

    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:

    curl -sD - -o /dev/null "$BASE_URL/v1/ping" -H "x-tt-logid: my-trace-1" | grep logid
    # x-tt-logid: my-trace-1
    VariableDefaultEffect
    AIO_RUST_LOG_LEVELinfoLog level, or any tracing filter directive such as aio_daemon=debug,info

    Metrics

    curl "$BASE_URL/metrics" -H "Authorization: Bearer <key>"

    Prometheus text format, rendered on request. /metrics requires the API key when one is set. Four series:

    SeriesTypeLabels
    http_requests_totalCountermethod, route, status
    http_request_duration_secondsHistogramNone; one latency distribution for the process
    command_exec_totalCounterNone
    command_exec_duration_secondsHistogramNone

    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.

    OpenTelemetry traces

    Off by default. Enable with one variable:

    OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317 aiod start
    VariableDefaultEffect
    OTEL_EXPORTER_OTLP_ENDPOINTunsetSet to enable trace export
    OTEL_EXPORTER_OTLP_PROTOCOLgrpcgrpc or http/protobuf
    OTEL_EXPORTER_OTLP_HEADERSunsetHeaders the collector authenticates with
    OTEL_EXPORTER_OTLP_TIMEOUT10000Export timeout, in milliseconds
    OTEL_SERVICE_NAMEaiod (computer-use on the worker)Service name on spans
    OTEL_RESOURCE_ATTRIBUTESunsetExtra resource attributes
    OTEL_SDK_DISABLEDunsettrue 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.

    One span per request (otel.kind server, with http.method and http.route). Trace context propagates: a caller's trace continues through the daemon into computer-use.

    Only plaintext http:// collectors are supported: no TLS backend is compiled in.