Skip to main content
Glama
omarqx
by omarqx
README.md
# logsafe

Run it: `npx @coglet/logsafe` → http://127.0.0.1:4600

## What is this

logsafe is a local debugging log server: point any app (or any HTTP client)
at it and it collects log events into named **sessions**, stored in a local
SQLite database. It groups related events (across multiple processes or
sources — a browser tab and its backend, say) so you can filter, search, and
tail them while you debug. A web UI for browsing sessions is included (see
below); everything is also available over plain HTTP (see `API.md`).

## Quickstart

```bash
npm install
npm start
# [logsafe] listening on http://127.0.0.1:4600  (db: ~/.logsafe/logsafe.db, retention: 7d)
```

Send it a log line:

```bash
curl -s localhost:4600/v1/log -d '{"msg":"hello world"}'
# {"accepted":1,"rejected":0}

curl -s localhost:4600/api/sessions | jq
```

Or run the bundled demo, which emits a realistic multi-source session and
verifies it back over the API:

```bash
npm run demo -- --keep   # leaves the server running so you can explore
```

## Web UI

Build once, then the server serves the app at its own port:

```bash
npm run build:ui
npm start
# open http://127.0.0.1:4600
```

Session list → click into a session for the dense log stream: composable
filters (`ns:payment.*`, `level:warn,error`, `trace:…`, bare text = full-text
search) typed into the command bar as `key:value` tokens, an error/density
minimap on the right edge (click to jump), per-row expandable ctx JSON, and a
live tail over SSE that pauses when you scroll up (buffered events are counted;
`G` resumes). Every piece of view state — filters, timestamp mode, pinned rows,
selection — lives in the URL, so copying the address bar shares the exact view.
Filter changes are history entries: the back button undoes them.

Keyboard map:

| Key | Action |
|---|---|
| `j` / `k` | move selection down / up (pauses live tail) |
| `Enter` / `o` | expand/collapse the selected row's ctx |
| `/` | focus text search |
| `f` | focus the filter input |
| `e` | toggle `level:warn,error` |
| `p` | pin/unpin the selected row (pins survive filter changes) |
| `t` | cycle timestamps: absolute → relative → Δ from previous |
| `g` / `G` | jump to top / bottom (`G` at bottom resumes the live tail) |
| `x` | (session list) delete the selected session |
| `Esc` | leave the focused input |

Timestamps show client-reported time; ordering is always the server's arrival
order (`seq`), so interleaved multi-source sessions read in the order the
server actually saw.

Dev loop for UI work: `npm run dev:ui` (Vite on 5173, proxying to the server
on 4600).

## Logging from your app

### JavaScript/TypeScript: `@coglet/logsafe-client`

Zero-dependency helper for browser or Node apps. It batches events and
sends them over `POST /v1/log`.

```ts
import { initLogsafe, createLog } from '@coglet/logsafe-client'

const { sessionId } = initLogsafe({
  source: 'webapp',              // required: identifies this process/app
  sessionLabel: 'checkout flow', // optional, human-readable
  // url: 'http://127.0.0.1:4600' (default)
})

const log = createLog('cart')    // ns: a dotted/colon namespace for this logger
log.info('cart hydrated', { items: 3, total_cents: 8497 })
log.error('payment failed', { status: 502 })

// Follow one request/operation across sources by sharing a trace id:
const reqLog = createLog('cart:payment').withTrace(`req-${sessionId.slice(0, 6)}`)
reqLog.info('submitting payment', { provider: 'stripe' })
```

Events are buffered and flushed automatically (every 250ms, or immediately
at 64 buffered events), and flushed on page unload via `sendBeacon`. Call
`flush()` to force-send (useful before a script exits, e.g. in tests or a
CLI tool).

### Any other language: it's just HTTP POST

There's no SDK requirement — anything that can make an HTTP request can log
to logsafe. Send a JSON object or a JSON array of objects to `POST /v1/log`:

```bash
curl -s localhost:4600/v1/log -d '[
  {"session_id": "s1", "source": "api", "ns": "http", "level": "info",  "msg": "GET /api/cart 200", "ctx": {"ms": 12}},
  {"session_id": "s1", "source": "api", "ns": "http", "level": "error", "msg": "POST /api/checkout 500", "ctx": {"ms": 340}}
]'
# {"accepted":2,"rejected":0}
```

Only `msg` is required — everything else has a sane default. See `API.md`
for the full field table, coercion rules, and status codes.

## For AI coding agents

If you're an agent debugging an app that logs to logsafe, this is the fast
path to finding and reading its logs. Full field/param reference is in
`API.md`.

- **Server:** runs locally at `http://127.0.0.1:4600` by default. Check it's
  up with `curl -s localhost:4600/api/health` → `{"ok":true}`.
- **Find the relevant session:**
  ```bash
  curl -s localhost:4600/api/sessions | jq
  ```
  Sessions are returned **newest first**. Look at `label` (human-readable
  hint), `sources` (which processes logged to it), `error_count`/
  `warn_count` (is something obviously wrong), and `status` — `"active"`
  means it received an event in the last 60 seconds, i.e. the app is
  probably still running right now.
- **Read it, narrowest filter first:**
  ```bash
  curl -s 'localhost:4600/api/sessions/<id>/events?level=error' | jq
  ```
  Then widen as needed:
  - `level=warn,error` — multiple levels, comma-OR'd.
  - `ns=auth:*` — namespace wildcard (`*` only; matches any run of chars).
  - `q=timeout` — case-insensitive text search across `msg` and `ctx`.
  - `trace=req-abc123` — follow one request/operation across every source
    that tagged it with the same trace id (e.g. frontend + backend for one
    HTTP call).
  These all AND together, e.g.
  `?level=error&source=api&trace=req-abc123` narrows to error-level events
  from the `api` source within one traced request.
- **Bulk analysis:** `GET /api/sessions/<id>/export.ndjson` streams every
  matching event (same filters as above) as one JSON object per line —
  pipe-friendly:
  ```bash
  curl -s 'localhost:4600/api/sessions/<id>/export.ndjson' | jq -c 'select(.level=="error")'
  ```
- **Pagination and ordering:** responses are always `seq ASC` (server
  insertion order). If `next_after_seq` is non-null, pass it back as
  `after_seq` on the next request to keep paging. Events carry both `ts`
  (the client's own clock, which can be skewed or backdated) and
  `received_at`/`seq` (server-assigned) — **trust `seq` for ordering**,
  not `ts`.
- **Live tail:** `GET /api/sessions/<id>/stream` is an SSE endpoint that
  replays history then streams new events as they arrive — useful for
  watching a session while reproducing a bug interactively.

### Hooking up an AI agent

**MCP (Cursor, Claude Code, any MCP client)** — logsafe ships an MCP server:

```jsonc
// Cursor: ~/.cursor/mcp.json
{ "mcpServers": { "logsafe": { "command": "npx", "args": ["@coglet/logsafe", "mcp"] } } }
```

```bash
# Claude Code:
claude mcp add logsafe -- npx @coglet/logsafe mcp
```

Tools: `list_sessions`, `get_session`, `query_events`, `tail_session` —
read-only, talking to your local server (override with `--url` or `LOGSAFE_URL`).

**MCP over HTTP (no subprocess)** — a running logsafe server hosts MCP at `/mcp`:

```bash
claude mcp add --transport http logsafe http://127.0.0.1:4600/mcp
```

```jsonc
// Cursor ~/.cursor/mcp.json
{ "mcpServers": { "logsafe": { "url": "http://127.0.0.1:4600/mcp" } } }
```

The stdio form (`npx @coglet/logsafe mcp`) still works for stdio-only clients.

**Skill (Claude Code)** — a debugging workflow skill ships in this repo/package:

```bash
# installed via npm:
cp -r node_modules/logsafe/skills/debugging-with-logsafe ~/.claude/skills/

# from source (this repo):
cp -r packages/server/skills/debugging-with-logsafe ~/.claude/skills/
```

(For Cursor, paste the SKILL.md body into a project rule instead.)

## Plugins

logsafe can be extended with plugins that add new log types, server-side
hooks, and a custom UI for the sessions that carry them.

- **[Writing plugins](docs/PLUGINS.md)** — extend logsafe with your own log types, server hooks, and custom UI (see `examples/plugin-http` and `templates/plugin-starter`).

## Configuration

Environment variables, read at server startup (`npm start`):

| Var | Default | Notes |
|---|---|---|
| `PORT` | `4600` | Server listens on `127.0.0.1:<PORT>` (local only, not exposed on the network). An unset/empty value uses the default; a non-numeric value logs a warning and falls back to the default rather than failing to start. |
| `LOGSAFE_DB` | `~/.logsafe/logsafe.db` | Path to the SQLite database file. Parent directories are created automatically. Use a throwaway path (e.g. `/tmp/logsafe-test.db`) for scratch/test servers so you don't pollute your real log history. |
| `RETENTION_DAYS` | `7` | Sessions whose most recent event (`last_ts`) is older than this many days are deleted (session + all its events) automatically, at startup and then hourly. Same validation as `PORT`: non-numeric falls back to the default with a warning. `0` or negative disables pruning entirely. |

```bash
LOGSAFE_DB=/tmp/logsafe-scratch.db PORT=4601 npm start
```