Log capture
app.CaptureLogs() installs an in-memory ring buffer for recent log records. The buffer is surfaced at GET /_logs over plain HTTP — independently of MCP — so you can diagnose issues even when the MCP subsystem itself is the thing that is broken.
Setup
Call app.CaptureLogs() after any application-side slog.SetDefault:
// If you configure a custom slog handler, do it first:
slog.SetDefault(slog.New(myHandler))
// Then enable capture — wraps whatever handler is currently the default.
app.CaptureLogs()
If you have not configured a custom handler, call app.CaptureLogs() directly. The call is chainable:
app := smeldr.New(cfg).CaptureLogs()
Options
| Option | Default | Description |
|---|---|---|
smeldr.WithLogCapacity(n) | 500 | Maximum entries retained. When the ring is full, the oldest entry is overwritten. |
smeldr.WithLogLevel(level) | slog.LevelWarn | Minimum level captured into the ring. |
app.CaptureLogs(
smeldr.WithLogCapacity(1000),
smeldr.WithLogLevel(slog.LevelError),
)
What it does
CaptureLogs wraps the existing slog default handler with a teeing handler:
- Every log record is still forwarded to the previous handler (typically stderr).
- Records at or above the ring level are also captured into the in-memory ring.
- When the ring is full, the oldest entry is overwritten.
Zero-config apps: if no slog.SetDefault has been called before CaptureLogs, the standard library's built-in handler is replaced with a direct os.Stderr text handler before wrapping. This avoids a re-entrancy deadlock. The devlog has the full story.
GET /_logs
The endpoint is mounted automatically when app.CaptureLogs() has been called. It is absent (404) without that call.
Authentication: Admin bearer token.
Response shape:
{
"capacity": 500,
"count": 12,
"dropped": 0,
"entries": [
{
"time": "2026-06-08T14:00:00Z",
"level": "WARN",
"msg": "slow query detected",
"attrs": { "duration_ms": 840 },
"seq": 12
}
]
}
| Field | Description |
|---|---|
capacity | Ring size |
count | Number of entries in this response |
dropped | Entries evicted since startup |
entries | Newest-first; always an array |
entries[].seq | Monotonic sequence number — gaps indicate dropped entries |
Query parameters:
| Parameter | Format | Description |
|---|---|---|
level | debug / info / warn / error | Minimum level, inclusive |
limit | integer | Return at most N most-recent matching entries |
since | RFC 3339 | Return only entries after this timestamp |
When to use
/_logs is a live debugging endpoint, not a log storage system. The ring is ephemeral — it is lost on server restart. Stderr is the durable path.
Query via CLI:
smeldr-cli logs
smeldr-cli logs --level error --limit 20
smeldr-cli logs --since 2026-06-01T00:00:00Z
Or directly via HTTP:
curl -H "Authorization: Bearer <token>" https://your-site/_logs