logsafe
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
```
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessUnresponsive