Documentation

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

OptionDefaultDescription
smeldr.WithLogCapacity(n)500Maximum entries retained. When the ring is full, the oldest entry is overwritten.
smeldr.WithLogLevel(level)slog.LevelWarnMinimum 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
    }
  ]
}
FieldDescription
capacityRing size
countNumber of entries in this response
droppedEntries evicted since startup
entriesNewest-first; always an array
entries[].seqMonotonic sequence number — gaps indicate dropped entries

Query parameters:

ParameterFormatDescription
leveldebug / info / warn / errorMinimum level, inclusive
limitintegerReturn at most N most-recent matching entries
sinceRFC 3339Return 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