Proxy Logs
Section titled “Proxy Logs”The Proxy Logs API provides centralized access to request, response, and event logs produced by the Hoody proxy. Use these endpoints to search historical logs, retrieve aggregate statistics, and stream new entries in real time over Server-Sent Events.
Query Centralized Logs
Section titled “Query Centralized Logs”GET /_logs
Search and filter through the stored request/response and event logs. Results are scoped to the authenticated project by default; admin callers can opt into cross-tenant fanout using the cursor parameter when LOGS_ADMIN_FANOUT=true.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Maximum entries to return. Default: 200. |
offset | query | integer | No | Number of entries to skip. Default: 0. |
projectId | query | string | No | Filter to a single project. |
containerId | query | string | No | Filter to a single container. |
serviceName | query | string | No | Filter to a single service. |
level | query | string | No | Comma-separated levels (debug,info,warn,error). |
includeRequestBody | query | boolean | No | Include the request body in entries. Default: false. |
includeResponseBody | query | boolean | No | Include the response body in entries. 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 | Filter by HTTP method. |
source | query | string | No | One of: backend, edge. |
Example Request
Section titled “Example Request”curl -X GET "https://api.hoody.com/_logs?limit=200&containerId=cnt_def456&level=info,warn&kind=request" \ -H "Authorization: Bearer <token>"const iterator = await client.proxyLogs.logs.listIterator({ limit: 200, containerId: "cnt_def456", level: "info,warn", kind: "request"});
for await (const entry of iterator) { console.log(entry.tsIso, entry.level, entry.method, entry.url);}Response
Section titled “Response”{ "entries": [ { "id": 12345, "traceId": "550e8400-e29b-41d4-a716-446655440000", "tsMs": 1705312200000, "tsIso": "2024-01-15T12:30:00.000Z", "kind": "request", "level": "info", "projectId": "proj_abc123", "containerId": "cnt_def456", "serviceName": "api-gateway", "method": "GET", "url": "/v1/users", "clientIp": "192.168.1.100", "status": 200, "data": {}, "source": "edge" } ], "total": 1500, "limit": 200, "offset": 0}{ "statusCode": 403, "error": "Forbidden", "message": "Insufficient permissions to access logs"}Fanout snapshot expired. The client must restart the scan without a cursor.
{ "__trailer": true, "error": "snapshot_expired", "status": 410}Get Log Statistics
Section titled “Get Log Statistics”GET /_logs/stats
Retrieve aggregate counts for stored logs, broken down by level, project, container, and service.
This endpoint takes no parameters.
Example Request
Section titled “Example Request”curl -X GET "https://api.hoody.com/_logs/stats" \ -H "Authorization: Bearer <token>"const stats = await client.proxyLogs.logs.getStats();console.log(stats.total, stats.byLevel);Response
Section titled “Response”{ "total": 125000, "byLevel": { "debug": 45000, "info": 60000, "warn": 15000, "error": 5000 }, "byProject": { "proj_abc123": 75000, "proj_def456": 50000 }, "byContainer": { "cnt_111": 40000, "cnt_222": 35000, "cnt_333": 50000 }, "byService": { "api-gateway": 80000, "auth-service": 25000, "db-proxy": 20000 }}Stream Live Logs
Section titled “Stream Live Logs”GET /_logs/stream
Opens a persistent Server-Sent Events connection that streams new log entries as they are recorded by the proxy.
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. |
Example Request
Section titled “Example Request”curl -X GET "https://api.hoody.com/_logs/stream?containerId=cnt_def456&level=info" \ -H "Authorization: Bearer <token>" \ -H "Last-Event-ID: 12345" \ -H "Accept: text/event-stream"const stream = await client.proxyLogs.logs.streamLogs({ containerId: "cnt_def456", level: "info"});
stream.on("event", (event) => { if (event.event === "scope-destroyed") { stream.close(); return; } if (event.event === "reset") { // Discard lastSeenId and reconnect fresh. stream.close(); return; } console.log("id:", event.id, "data:", event.data);});Response
Section titled “Response”SSE stream of live log entries. Each frame carries an id: line with the ringSeq and a data: line with a JSON-serialized LogEntry.
id: 12346data: {"id":12346,"traceId":"550e8400-e29b-41d4-a716-446655440000","tsMs":1705312201000,"tsIso":"2024-01-15T12:30:01.000Z","kind":"event","level":"info","projectId":"proj_abc123","containerId":"cnt_def456","serviceName":"api-gateway","method":"POST","url":"/v1/orders","clientIp":"192.168.1.100","status":201,"data":{},"source":"backend"}
:Logs token missing or invalid / SNI gate denied.
{ "statusCode": 403, "error": "Forbidden", "message": "Logs token missing or invalid"}Rate limited.
{ "statusCode": 429, "error": "Too Many Requests", "message": "Rate limit exceeded"}