# Logs — Locus Build

> Companion guide to [`SKILL.md`](./SKILL.md). Covers log streaming, search, and best practices.

## When To Load

Load this file only for deployment log streaming, log search, or log-driven debugging.

## Table of Contents

- [Stream Logs](#stream-logs)
- [Search Logs](#search-logs)
- [Failed Deployment Logs](#failed-deployment-logs)
- [Logging Best Practices](#logging-best-practices)

## Stream Logs

Logs are **phase-aware**: the same endpoint automatically serves build logs during the `queued`/`building` phase and runtime logs during the `deploying`/`healthy`/`failed` phase.

```bash
# Follow logs in real-time (SSE stream)
curl -N \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs?follow=true"

# Get recent historical logs
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs"

# SSE with token in query string (useful for EventSource which can't set headers)
curl -N "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs?follow=true&token=$TOKEN"
```

SSE events arrive as `data: {...}\n\n`. Event types:
- `{"type":"connected","deploymentId":"..."}` — stream opened
- `{"type":"log","timestamp":1234567890,"message":"..."}` — log line
- `{"type":"complete"}` — deployment finished
- `{"type":"error","message":"..."}` — stream error

**Phase behavior:**
- **Build phase** (`queued`/`building`): Streams build output. If the build hasn't started yet, shows "Build queued, waiting to start..." and auto-polls until build logs appear.
- **Runtime phase** (`deploying`/`healthy`/`failed`): Streams runtime container logs.
- The endpoint handles the phase detection automatically — no extra parameters needed.

**Non-streaming response metadata:**
- `phase` — `build` or `runtime`
- `reason` — why logs are empty: `build_not_started`, `build_in_progress`, `deploying_no_logs_yet`, `no_logs_available`
- `deploymentStatus` — current deployment status
- Completed deployment logs are retained for up to 14 days for investigation, even after the live stream window passes

**Stream runtime logs for a service** (not tied to a specific deployment):

```bash
curl -N \
  -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/services/$SERVICE_ID/logs?follow=true"
```

## Search Logs

Search for specific patterns in logs using filter pattern syntax.

**Search service runtime logs:**

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/services/$SERVICE_ID/logs/search?pattern=ERROR&since=1h&limit=100"
```

**Search deployment logs (phase-aware — searches build or runtime logs based on deployment status):**

```bash
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs/search?pattern=timeout&since=6h"
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `pattern` | string (required) | Filter pattern. Simple text (`ERROR`), multi-term (`?ERROR ?FATAL ?Exception`), or JSON (`{ $.level = "error" }`) |
| `since` | string | Relative time (`15m`, `1h`, `6h`, `24h`, `7d`) or ISO timestamp |
| `until` | string | End time (same format as `since`) |
| `limit` | number or numeric string | Max results (default 100, max 1000). Accepts `?limit=100` as number or string |

Response:
```json
{
  "logs": [
    {"timestamp": "2026-02-26T12:00:00.000Z", "message": "ERROR: connection timeout"},
    {"timestamp": "2026-02-26T12:01:00.000Z", "message": "ERROR: retry failed"}
  ],
  "matchCount": 2,
  "truncated": false
}
```

**Common search patterns:**
- `ERROR` — any line containing "ERROR"
- `?ERROR ?FATAL ?Exception` — lines with any of these terms
- `"connection refused"` — exact phrase match
- `{ $.level = "error" }` — JSON structured log field match

## Failed Deployment Logs

When fetching a failed deployment via `GET /v1/deployments/:id`, the response includes
`lastLogs` — an array of up to 20 log lines from the build or runtime phase.

```json
{
  "id": "deploy_xxx",
  "status": "failed",
  "lastLogs": [
    "Step 3/5: RUN npm install",
    "npm ERR! code ERESOLVE",
    "npm ERR! ERESOLVE unable to resolve dependency tree",
    "..."
  ]
}
```

- **Phase-aware**: Shows build logs if a build ID exists, otherwise runtime logs
- **Stored for 14 days**: Completed deployment logs are persisted so failed deploys remain inspectable after the live AWS window
- **Max 20 lines**: The last 20 lines from the relevant log stream
- **Non-fatal**: If logs can't be retrieved, the field is omitted (GET still succeeds)

### When `lastLogs` Doesn't Show the Failure Reason

`lastLogs` returns the last 20 lines, which is usually sufficient. But if the failure happened earlier in the build (e.g., a dependency error 200 lines before the build aborted), use the full log endpoints:

**Fetch full build/runtime logs:**

```bash
# Full log output (non-streaming) — returns all available logs
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs"
```

**Search for error patterns:**

```bash
# Search for common error patterns in the deployment logs
curl -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs/search?pattern=?ERROR%20?FATAL%20?Exception%20?error&since=1h"
```

**Stream logs in real-time (for active builds):**

```bash
# Follow build output as it happens
curl -N -H "Authorization: Bearer $TOKEN" \
  "https://api.buildwithlocus.com/v1/deployments/$DEPLOYMENT_ID/logs?follow=true"
```

## Logging Best Practices

For best results with Locus log search and error monitoring:

- **Use stdout/stderr** — all container output is captured automatically and sent to the log system
- **Structured JSON logging recommended** — enables field-level search with filter patterns (e.g., `{ $.level = "error" }`)
- **Include stack traces** for unexpected errors — they're captured in full
- **Error patterns detected automatically:** `ERROR`, `FATAL`, `Exception`, `error`, `fatal`, `panic`
- **Avoid logging secrets** — logs are stored and visible via the API
