Observability

Request Logs

A request log is the primitive every other observability surface is built on. Vectoralix records exactly one row per MCP request — successful, failed, rate-limited, or rejected before tool resolution. This page is a data dictionary plus a how-to-read narrative for that row.

When a row is written

Every branch of the MCP protocol endpoint writes one row before returning to the client. There is no sampling and no dropped log on the success path or any error path: success, rate limit, missing tool, server not found, and generic failure each produce exactly one record.

  • Success — request reached a tool, the tool returned, and the response was sent.
  • Rate limited — the per-server monthly bucket was exhausted, response is 429.
  • Tool not found — the tool name in the JSON-RPC payload did not resolve to a live tool on the active version.
  • Server not found — the URL's serverUid did not match any MCP server (private servers return 401 to avoid enumeration; public return 404).
  • Generic failure — anything else that bubbled out, including 5xx exceptions.

Fields captured

The same row shape is logged for both public and private servers. Visibility does not change which fields are captured; for public requests, the token id is simply null because there is no token to attach.

Field Meaning
mcp_server_id The MCP server the URL resolved to.
organization_id The owning organization, denormalized so dashboards can scope without an extra join.
version_id Active version at request time. Null when the failure fired before version resolution.
tool_name The tool the request invoked. Null when the failure fired before tool resolution; rolled up under a synthetic "__total__" bucket downstream.
passport_token_id The endpoint token that authenticated a private-server request. Always null for public servers.
ip_address Client IP. Captured for debugging and abuse review only — see Privacy below.
response_status HTTP status returned to the client.
duration_ms Wall-clock duration in milliseconds. Includes outbound HTTP for API URL tools, so it reflects user-perceived latency rather than internal-only time.
created_at Timestamp the row was written. The table is append-only — no updated_at column.

Why some rows have no tool_name

tool_name is populated whenever the request identified a tool before failing; otherwise it is null. A 200 success row always has a tool name. A 429 row often does not — the rate-limit decision happens before the JSON-RPC body is parsed. A "tool not found" row will have a name (the one the client asked for, even though it did not match anything live). Downstream rollups bucket null tool_name into a synthetic "__total__" tool so dashboards can sum without per-tool gymnastics.

Row shape across status codes

Status tool_name duration_ms passport_token_id
200 Set to the resolved tool Set, end-to-end Set on private servers; null on public
401 Null — auth fails before tool parse Set, but small Null — there was no valid token
404 Null — server lookup failed Set, small Null
429 Usually null — quota check is upstream Set, small Set on private servers when the token was valid
5xx Set if the failure fired after tool resolution; otherwise null Set, end-to-end Set on private servers when the token was valid

Retention

Raw request_logs rows are pruned 30 days after they were written. The rolled-up tables that aggregate them — hourly, daily, monthly — are retained indefinitely, so historical dashboards keep working long after the underlying rows have been deleted.

day 0   →  row written
day 30  →  row pruned from request_logs
            (already counted in hourly, daily, and monthly rollups)
forever →  rollup buckets remain

Viewing logs (roadmap)

A per-row log viewer with filters by MCP server, tool, status, and time range is on the roadmap. Today the rows exist in the database and feed the rollup pipeline, but there is no end-user filter-and-browse interface in the dashboard. The schema and retention story above describe current behavior; the viewing narrative describes planned behavior.

Privacy and what is NOT stored

  • No request bodies — the JSON-RPC payload is not persisted.
  • No headers beyond the auth token id — the raw bearer token is never written; only the token's identifier is stored.
  • No response bodies — only the HTTP status code.
  • IP addresses are stored for debugging and abuse review and aged out with the rest of the row at day 30.

Known limitations

  • No request-body size capture yet.
  • duration_ms includes outbound HTTP for API URL tools, so cross-tool comparisons mix internal and external time.
  • Token attribution only populates for private servers; public requests do not carry a token to attribute.

Next: Read Usage Metrics to see how these rows become the rolled-up numbers on the dashboard, and Dashboards for the widgets that render them.