Proxy Logs
Section titled “Proxy Logs”The Proxy Logs API provides centralized access to request/response and event logs captured by the Hoody proxy layer. Use these endpoints to query historical log entries, retrieve aggregate statistics, and live-tail log activity as it happens via Server-Sent Events.
All log endpoints are scoped to the container that hosts the proxy logs service. Authentication is performed with a logs token supplied by the platform; requests that omit a valid token or violate the gateway policy are rejected.
Query centralized logs
Section titled “Query centralized logs”GET /_logs
Search and filter through the stored request, response, and event logs. Results can be scoped by project, container, service, severity, kind, HTTP method, and source. Use the kind parameter to distinguish proxy traffic (request, response) from internal events (event), and source to differentiate backend logs from edge logs.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit | query | integer | No | Maximum number of entries to return. Default: 200. |
offset | query | integer | No | Number of entries to skip before returning results. Default: 0. |
projectId | query | string | No | Restrict results to a single project. |
containerId | query | string | No | Restrict results to a single container. |
serviceName | query | string | No | Restrict results to a single service. |
level | query | string | No | Comma-separated levels (debug,info,warn,error). |
includeRequestBody | query | boolean | No | Include the captured request body in each entry. Default: false. |
includeResponseBody | query | boolean | No | Include the captured response body in each entry. 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 | Pagination cursor (signed opaque base64). |
kind | query | string | No | Filter by entry kind. Allowed values: request, response, event. |
method | query | string | No | Filter by HTTP method. |
source | query | string | No | Filter by log source. Allowed values: backend, edge. |
curl -G "https://{projectId}-{containerId}-logs-1.{serverName}.containers.hoody.com/_logs" \ -H "Authorization: Bearer <token>" \ --data-urlencode "level=info,warn" \ --data-urlencode "kind=request,response" \ --data-urlencode "limit=50"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-logs-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.proxyLogs.logs.listIterator({ limit: 50, level: 'info,warn', kind: 'request,response'});{ "entries": [ { "id": 87421, "traceId": "7f4e3a8c-2b9d-4ad1-9c0e-1a2b3c4d5e6f", "tsMs": 1718225400123, "tsIso": "2024-06-12T14:50:00.123Z", "kind": "request", "level": "info", "projectId": "67e89abc123def456789abcd", "containerId": "890abcdef12345678901cdef", "serviceName": "api-gateway", "method": "POST", "url": "/v1/checkout", "clientIp": "203.0.113.42", "status": 200, "data": {}, "source": "backend" }, { "id": 87420, "traceId": "7f4e3a8c-2b9d-4ad1-9c0e-1a2b3c4d5e6f", "tsMs": 1718225400119, "tsIso": "2024-06-12T14:50:00.119Z", "kind": "response", "level": "info", "projectId": "67e89abc123def456789abcd", "containerId": "890abcdef12345678901cdef", "serviceName": "api-gateway", "method": "POST", "url": "/v1/checkout", "clientIp": "203.0.113.42", "status": 200, "data": {}, "source": "backend" } ], "total": 1342, "limit": 50, "offset": 0}{ "statusCode": 403, "error": "Forbidden", "message": "Logs token missing or invalid"}{"__trailer": true, "error": "snapshot_expired", "status": 410}Get log statistics
Section titled “Get log statistics”GET /_logs/stats
Return aggregate counts of stored log entries, broken down by level, project, container, and service.
Parameters
Section titled “Parameters”This endpoint takes no parameters.
curl "https://{projectId}-{containerId}-logs-1.{serverName}.containers.hoody.com/_logs/stats" \ -H "Authorization: Bearer <token>"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-logs-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.proxyLogs.logs.getStats();{ "total": 1342, "byLevel": { "debug": 412, "info": 802, "warn": 94, "error": 34 }, "byProject": { "67e89abc123def456789abcd": 1342 }, "byContainer": { "890abcdef12345678901cdef": 1342 }, "byService": { "api-gateway": 911, "auth": 431 }}Live-tail logs over Server-Sent Events
Section titled “Live-tail logs over Server-Sent Events”GET /_logs/stream
Open a persistent Server-Sent Events connection that streams new log entries as they arrive. Every frame includes an id: <ringSeq> line that clients can use to resume after a disconnect.
Framing - each frame carries an id: line followed by a data: line containing the log entry as JSON:
id: 12345data: {"id": 12345, "tsMs": 1718225400123, ...}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 / approximately 50 seconds replay window at 100 entries/s).
Named events (v8 contract):
event: scope-destroyed- the container was destroyed; the stream closes immediately after. Clients should exit cleanly.event: reset- the server restarted and theringSeqcounter has been reset with a>= 10000safety margin. Clients must discard theirlastSeenIdand reconnect fresh.
Periodic :\n\n heartbeats are emitted every 15 seconds to keep the connection alive.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
projectId | query | string | No | Filter to a single project. |
containerId | query | string | No | Filter to a single container. |
kind | query | string | No | Filter by entry kind. Allowed values: request, response, event. |
level | query | string | No | Filter by severity. Allowed values: debug, info, warn, error. |
Last-Event-ID | header | string | No | Numeric ringSeq of the last event received. Server skips entries with ringSeq <= Last-Event-ID from the ring buffer on reconnect. |
curl -N "https://{projectId}-{containerId}-logs-1.{serverName}.containers.hoody.com/_logs/stream?level=error" \ -H "Authorization: Bearer <token>" \ -H "Last-Event-ID: 12340"import { HoodyClient } from 'hoody-sdk';
const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-logs-1.{serverName}.containers.hoody.com', token: process.env.HOODY_TOKEN });
await client.proxyLogs.logs.streamLogs({ level: 'error', Last-Event-ID: '12340'});id: 12346data: {"id":12346,"traceId":"7f4e3a8c-2b9d-4ad1-9c0e-1a2b3c4d5e6f","tsMs":1718225400450,"tsIso":"2024-06-12T14:50:00.450Z","kind":"event","level":"error","projectId":"67e89abc123def456789abcd","containerId":"890abcdef12345678901cdef","serviceName":"api-gateway","method":"GET","url":"/v1/health","clientIp":"10.0.0.7","status":503,"data":{},"source":"backend"}
:{ "statusCode": 403, "error": "Forbidden", "message": "Logs token missing or invalid"}{ "statusCode": 429, "error": "Too Many Requests", "message": "Rate limit exceeded; retry after the period indicated by the Retry-After header"}