The Logs endpoints let you search, aggregate, and live-tail the centralized request, response, and event logs captured by the proxy. Use these when you need to investigate traffic, build dashboards, or stream new entries into an external system.
Query centralized logs
Section titled “Query centralized logs”Search and filter the stored request/response and event logs. Supports pagination, level filtering, cross-tenant fanout (admin only), and row-cursor streaming.
GET /api/proxy-logs/_logs
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Max entries to return (default: 200) |
offset | query | integer | No | Entries to skip (default: 0) |
projectId | query | string | No | Restrict to a single project |
containerId | query | string | No | Restrict to a single container |
serviceName | query | string | No | Restrict to a single service name |
level | query | string | No | Comma-separated levels (debug,info,warn,error) |
includeRequestBody | query | boolean | No | Include captured request bodies (default: false) |
includeResponseBody | query | boolean | No | Include captured response bodies (default: false) |
last | query | integer | No | Return only the last N entries |
afterId | query | integer | No | Return entries with SQLite row ID greater than this (ASC cursor) |
cursor | query | string | No | v8 §5.2 — cross-tenant fanout pagination cursor (signed opaque base64). Only honored when LOGS_ADMIN_FANOUT=true |
kind | query | string | No | One of: request, response, event |
method | query | string | No | HTTP method filter (e.g. GET, POST) |
source | query | string | No | One of: backend, edge |
Response
Section titled “Response”{ "entries": [ { "id": 84321, "traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "tsMs": 1718201234000, "tsIso": "2024-06-12T14:33:54.000Z", "kind": "request", "level": "info", "projectId": "proj_a1b2c3d4", "containerId": "cnt_x9y8z7w6", "serviceName": "user-api", "method": "POST", "url": "/v1/users", "clientIp": "203.0.113.42", "status": 201, "data": {"bytesIn": 412, "userAgent": "Hoody-CLI/1.4.2"}, "source": "backend" }, { "id": 84322, "traceId": "f47ac10b-58cc-4372-a567-0e02b2c3d480", "tsMs": 1718201234102, "tsIso": "2024-06-12T14:33:54.102Z", "kind": "response", "level": "info", "projectId": "proj_a1b2c3d4", "containerId": "cnt_x9y8z7w6", "serviceName": "user-api", "method": "POST", "url": "/v1/users", "clientIp": "203.0.113.42", "status": 201, "data": {"bytesOut": 318, "durationMs": 102}, "source": "backend" } ], "total": 1284, "limit": 200, "offset": 0}{ "statusCode": 403, "error": "Forbidden", "message": "Caller is not permitted to read logs for this scope"}{"__trailer": true, "error": "snapshot_expired", "status": 410}SDK usage
Section titled “SDK usage”const page = await client.proxyLogs.logs.listIterator({ projectId: "proj_a1b2c3d4", level: "warn,error", limit: 100,});for await (const entry of page) { console.log(entry.traceId, entry.level, entry.url);}Get log statistics
Section titled “Get log statistics”Returns aggregate counts across level, project, container, and service dimensions. Useful for building dashboards or sizing retention.
GET /api/proxy-logs/_logs/stats
This endpoint takes no parameters.
Response
Section titled “Response”{ "total": 8421, "byLevel": {"info": 6200, "warn": 1500, "error": 521, "debug": 200}, "byProject": {"proj_a1b2c3d4": 5000, "proj_e5f6g7h8": 3421}, "byContainer": {"cnt_x9y8z7w6": 3000, "cnt_m5n6o7p8": 5421}, "byService": {"user-api": 4200, "billing-api": 2100, "webhook-svc": 2121}}SDK usage
Section titled “SDK usage”const stats = await client.proxyLogs.logs.getStats();console.log("Total entries:", stats.total);console.log("Errors:", stats.byLevel.error);Live-tail logs over SSE
Section titled “Live-tail logs over SSE”Opens a persistent Server-Sent Events connection streaming new log entries as they are written. Each frame carries an id: <ringSeq> line for resumable reconnect.
GET /api/proxy-logs/_logs/stream
v8 §6.4 framing — every frame carries an id: <ringSeq> line:
id: 12345data: {"id":84321,"kind":"request","level":"info",...}Reconnect resume — clients MAY send Last-Event-ID: <ringSeq> on reconnect; the server skips any frame with ringSeq <= Last-Event-ID from the ring buffer (5000 entries / ~50 s replay window at 100 entries/s).
Named events (v8):
event: scope-destroyed— container destroyed; stream closes immediately after. Clients should exit cleanly.event: reset— server restart;ringSeqcounter reset with a ≥10 000 safety margin. Clients MUST discard theirlastSeenIdand reconnect fresh.
Periodic :\n\n heartbeats every 15s keep the connection alive.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
projectId | query | string | No | Filter to a single project (admin-port only; SNI clients are auto-scoped) |
containerId | query | string | No | Filter to a single container |
kind | query | string | No | One of: request, response, event |
level | query | string | No | One of: debug, info, warn, error |
Last-Event-ID | header | string | No | v8 §6.4 — numeric ringSeq of the last event received. Server skips entries ≤ this value from the ring buffer on reconnect. |
Response
Section titled “Response”HTTP/1.1 200 OKContent-Type: text/event-streamCache-Control: no-cacheConnection: keep-alive
id: 12345data: {"id":84321,"traceId":"f47ac10b-58cc-4372-a567-0e02b2c3d479","tsMs":1718201234000,"kind":"request","level":"info","method":"POST","url":"/v1/users","status":201,"source":"backend"}
id: 12346data: {"id":84322,"traceId":"f47ac10b-58cc-4372-a567-0e02b2c3d480","tsMs":1718201234102,"kind":"response","level":"info","method":"POST","url":"/v1/users","status":201,"source":"backend"}
:{ "statusCode": 403, "error": "Forbidden", "message": "Logs token missing or invalid / SNI gate denied"}{ "statusCode": 429, "error": "Too Many Requests", "message": "Rate limit exceeded for log stream"}SDK usage
Section titled “SDK usage”const stream = await client.proxyLogs.logs.streamLogs({ projectId: "proj_a1b2c3d4", level: "error",});
for await (const frame of stream) { if (frame.event === "scope-destroyed") break; if (frame.event === "reset") { // discard lastSeenId and reconnect fresh continue; } console.log(frame.id, frame.data);}