<!--
hoody-proxyLogs Subskill (sdk)
Auto-generated by Hoody Skills Generator
Generated: 2026-06-20T00:02:44.883Z
Model: mimo-v2.5-pro + fixer:mimo-v2.5-pro
Mode: sdk


Tokens: 4690

DO NOT EDIT MANUALLY - Changes will be overwritten on next generation
-->

# hoody-proxyLogs Subskill

## Overview

The `hoody-proxyLogs` service provides centralized logging infrastructure for all Hoody Kit container services. It captures, stores, and exposes request/response logs and event data generated by proxy-routed traffic to your containers.

### When to Use

- **Debugging** traffic flowing through Hoody Proxy to your containers
- **Monitoring** request/response patterns across services in real time
- **Auditing** API calls with full payload inspection
- **Performance analysis** via log statistics and filtering
- **Live-tailing** logs during deployments or incident response

### How It Fits Into Hoody Philosophy

Every Hoody Kit container automatically emits log entries through the proxy layer. The `proxyLogs` service centralizes this data so you never need to configure log shipping, manage a logging stack, or SSH into containers to read stdout. Authentication is handled by the Hoody Proxy routing layer — your SDK token grants access automatically.

### Service Address Pattern

```
https://{projectId}-{containerId}-proxy-logs-{serviceId}.{node}.containers.hoody.com
```

The SDK `baseURL` should point to the API gateway at `https://api.hoody.com`; the per-container host shown above is the underlying proxy address that the SDK routes to automatically. Do **not** prepend `/api/v1` or any other prefix to the paths documented below.

---

## Common Workflows

### 1. Query Recent Logs with Filtering

Retrieve logs for a specific project, container, and severity level.

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const result = await client.proxyLogs.logs.list({
  projectId: 'proj_abc123',
  containerId: 'ctr_xyz789',
  level: 'error',
  limit: 50
})

console.log(`Found ${result.entries.length} error logs`)
for (const entry of result.entries) {
  console.log(`[${entry.timestamp}] ${entry.method} ${entry.path} → ${entry.statusCode}`)
}
```

**Verify state:** Check `result.entries.length` matches expectations. If zero, widen filters (remove `level` or increase `limit`).

### 2. Paginate Through All Logs

Collect every log entry matching your criteria by auto-paginating.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const allPages = await client.proxyLogs.logs.listAll({
  projectId: 'proj_abc123',
  kind: 'request',
  method: 'POST',
  limit: 100
})

const totalEntries = allPages.reduce((sum, page) => sum + page.entries.length, 0)
console.log(`Collected ${totalEntries} POST request logs across ${allPages.length} pages`)
```

**Verify state:** Confirm `allPages.length > 0` and that each page contains an `entries` array.

### 3. Async Iterator for Large Log Sets

Stream through results without loading everything into memory at once.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const iterator = client.proxyLogs.logs.listIterator({
  projectId: 'proj_abc123',
  serviceName: 'my-api-service',
  source: 'proxy'
})

for await (const page of iterator) {
  for (const entry of page.entries) {
    process.stdout.write(`${entry.id} ${entry.path}\n`)
  }
}
```

**Verify state:** The iterator yields page objects identical to `list()`. Exhaustion terminates the loop automatically.

### 4. Retrieve Log Statistics

Get aggregate counts and metadata about your log store.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const stats = await client.proxyLogs.logs.getStats()

console.log(`Total logs: ${stats.total}`)
console.log(`By level:`, stats.byLevel)
console.log(`Oldest entry: ${stats.oldestTimestamp}`)
```

**Verify state:** `stats.total` should be a non-negative integer. Use this before querying to gauge dataset size.

### 5. Live-Tail Logs with SSE

Open a persistent Server-Sent Events connection to receive new log entries in real time.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const stream = await client.proxyLogs.logs.streamLogs({
  projectId: 'proj_abc123',
  level: 'error'
})

// Each message follows the SSE contract:
//   id: <ringSeq>
//   data: <LogEntry JSON>
stream.on('message', (event) => {
  console.log(`[SSE id=${event.id}]`, JSON.parse(event.data))
})

stream.on('error', (err) => {
  console.error('Stream disconnected:', err.message)
  // Implement reconnect with Last-Event-ID for resume
})
```

**Verify state:** A successful call returns an open event stream. If the connection drops, reconnect using the last received `id` as `Last-Event-ID`.

### 6. Resume a Disconnected SSE Stream

If the live-tail connection drops, resume from the last received sequence number.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

let lastEventId: string | undefined

function startStream() {
  return client.proxyLogs.logs.streamLogs({
    projectId: 'proj_abc123',
    'Last-Event-ID': lastEventId
  })
}

async function connectWithResume() {
  const stream = await startStream()

  stream.on('message', (event) => {
    lastEventId = String(event.id)
    console.log(`[${event.id}]`, JSON.parse(event.data))
  })

  stream.on('error', async () => {
    console.log(`Reconnecting from id=${lastEventId}`)
    await new Promise(r => setTimeout(r, 2000))
    connectWithResume()
  })
}

connectWithResume()
```

**Verify state:** After reconnect, new messages should have `id` values strictly greater than `lastEventId`. No gaps indicate successful resume.

---

## Advanced Operations

### Multi-Service Debugging Workflow

When an issue spans multiple containers, correlate logs across services.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const services = ['auth-service', 'api-gateway', 'payment-service']

for (const serviceName of services) {
  const errors = await client.proxyLogs.logs.list({
    projectId: 'proj_abc123',
    serviceName,
    level: 'error',
    last: 20
  })

  console.log(`\n--- ${serviceName}: ${errors.entries.length} recent errors ---`)
  for (const entry of errors.entries) {
    console.log(`  ${entry.timestamp} | ${entry.method} ${entry.path} → ${entry.statusCode}`)
  }
}
```

### Include Full Request/Response Bodies

When debugging payload issues, opt into body capture.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const detailed = await client.proxyLogs.logs.list({
  projectId: 'proj_abc123',
  method: 'POST',
  kind: 'request',
  includeRequestBody: true,
  includeResponseBody: true,
  limit: 10
})

for (const entry of detailed.entries) {
  console.log('Request body:', JSON.stringify(entry.requestBody, null, 2))
  console.log('Response body:', JSON.stringify(entry.responseBody, null, 2))
}
```

**Performance note:** Including bodies increases response size significantly. Always pair with a low `limit` value.

### Cursor-Based Pagination for Stable Iteration

Use `cursor` instead of `offset` for consistent results on a changing log store.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

let cursor: string | undefined
let batch = 0

do {
  const page = await client.proxyLogs.logs.list({
    projectId: 'proj_abc123',
    limit: 200,
    cursor
  })

  batch++
  console.log(`Batch ${batch}: ${page.entries.length} entries`)
  cursor = page.nextCursor
} while (cursor)
```

**Verify state:** When `page.nextCursor` is falsy, all results have been consumed.

### Use `afterId` for Incremental Consumption

Track the highest log ID you have processed and only fetch newer entries on subsequent runs.

```
const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const LAST_PROCESSED_ID = 50000

const fresh = await client.proxyLogs.logs.list({
  projectId: 'proj_abc123',
  afterId: LAST_PROCESSED_ID,
  limit: 500
})

if (fresh.entries.length > 0) {
  const newLastId = fresh.entries[fresh.entries.length - 1].id
  console.log(`Processed ${fresh.entries.length} new logs, last id=${newLastId}`)
}
```

### Error Recovery Pattern

Wrap SDK calls with retry logic for transient failures.

```
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (err) {
      if (attempt === maxAttempts) throw err
      console.warn(`Attempt ${attempt} failed, retrying in ${attempt * 1000}ms`)
      await new Promise(r => setTimeout(r, attempt * 1000))
    }
  }
  throw new Error('Unreachable')
}

const client = new HoodyClient({ baseURL: 'https://api.hoody.com', token: 'TOKEN' })

const stats = await withRetry(() => client.proxyLogs.logs.getStats())
console.log(`Total logs: ${stats.total}`)
```

---

## Quick Reference

### Endpoints

| Method | Path | SDK Method | Description |
|--------|------|------------|-------------|
| GET | `/_logs` | `client.proxyLogs.logs.list()` | Search and filter stored logs |
| GET | `/_logs/stats` | `client.proxyLogs.logs.getStats()` | Aggregate log statistics |
| GET | `/_logs/stream` | `client.proxyLogs.logs.streamLogs()` | Live SSE log tail |

### Key Parameters for `/_logs`

| Parameter | Type | Purpose |
|-----------|------|---------|
| `limit` | integer | Max entries per page |
| `offset` | integer | Skip N entries |
| `cursor` | string | Stable pagination token |
| `afterId` | integer | Fetch entries after this ID |
| `projectId` | string | Filter by project |
| `containerId` | string | Filter by container |
| `serviceName` | string | Filter by service |
| `level` | string | Filter by severity (e.g. `error`, `info`) |
| `kind` | string | Filter by entry kind (e.g. `request`) |
| `method` | string | Filter by HTTP method |
| `source` | string | Filter by source (e.g. `proxy`) |
| `includeRequestBody` | boolean | Include request payload |
| `includeResponseBody` | boolean | Include response payload |
| `last` | integer | Return only the N most recent entries |

### Key Parameters for `/_logs/stream`

| Parameter | Type | Purpose |
|-----------|------|---------|
| `projectId` | string | Filter stream by project |
| `containerId` | string | Filter stream by container |
| `kind` | string | Filter stream by entry kind |
| `level` | string | Filter stream by severity |
| `Last-Event-ID` | string | Resume from this SSE sequence ID |

### SSE Frame Format

```
id: 12345
data: {"id":12345,"timestamp":"2025-01-15T10:30:00Z","method":"GET","path":"/users","statusCode":200,"level":"info","source":"proxy"}
```

Every frame carries an `id: <ringSeq>` line. Use the last received `id` as `Last-Event-ID` on reconnect to avoid gaps.