reqlog 0.2.0
HTTP logging middleware for Python with an MCP server — give your AI coding agent a Network Tab
License: MIT
Requires Python: >=3.10
Keywords: ai-agent, asgi, claude-code, cursor, django, fastapi, http, logging, mcp, middleware

# reqlog

**HTTP logging middleware for Python with Rich formatting, SQLite persistence, and an MCP server for AI coding agents.**

[![PyPI version](https://img.shields.io/pypi/v/reqlog)](https://pypi.org/project/reqlog/)
[![Python 3.10+](https://img.shields.io/pypi/pyversions/reqlog)](https://pypi.org/project/reqlog/)
[![Tests](https://img.shields.io/github/actions/workflow/status/ankitksr/reqlog/tests.yml?label=tests)](https://github.com/ankitksr/reqlog/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

![panel format](docs/images/panel.svg)

Drop-in middleware for **FastAPI**, **Django**, and any ASGI framework.

---

## Quick Start

```bash
pip install reqlog
```

```python
from fastapi import FastAPI
from reqlog import ReqlogMiddleware

app = FastAPI()
app.add_middleware(ReqlogMiddleware)
```

```
  ● GET     /api/users                                     200     8ms   14:23:46
  ● POST    /api/users                                     201   145ms   14:23:47
  ● GET     /api/users/999                                 404    15ms   14:23:48
  ✗ GET     /api/crash                                     500    89ms   14:23:49
```

Three lines of code. Bodies, headers, and richer formats are one kwarg away:

```python
app.add_middleware(ReqlogMiddleware, capture_body=True, format="panel")
```

Inside Claude Code, Cursor, or Aider, reqlog auto-selects a token-efficient format — no configuration needed.

For Django, see [Django Support](#django-support).

---

## Give Your AI Agent a Network Tab

> Terminal output is a firehose. MCP is a faucet.

When an AI agent debugs your backend through terminal output, it sees *everything* — uvicorn startup messages, health checks, OPTIONS preflights, successful requests — all burning context tokens. It can't go back and ask "what was the request body for that 422?"

reqlog flips this. Persist requests to SQLite, point the MCP server at it, and your agent pulls exactly what it needs:

**Without reqlog MCP:**
1. Something breaks
2. Agent reads terminal — 200 lines of mixed output
3. "Can you reproduce the error?"
4. You curl the endpoint, paste the output
5. Agent guesses at the request body

**With reqlog MCP:**
1. Something breaks
2. Agent calls `get_error_summary()` — sees "422 on POST /api/users, missing field 'email'"
3. Agent calls `get_request_detail(id)` — sees the exact request body and validation error
4. Agent writes the fix

No manual debugging. No pasting terminal output into chat.

### Setup

**Step 1:** Persist requests to SQLite.

```python
from reqlog import ReqlogMiddleware, ConsoleBackend, SQLiteBackend

app.add_middleware(
    ReqlogMiddleware,
    capture_body=True,
    backends=[ConsoleBackend(), SQLiteBackend("reqlog.db")],
)
```

**Step 2:** Add to your editor's MCP config.

<details>
<summary><b>Claude Code</b> — <code>.mcp.json</code> in project root or <code>~/.claude/mcp.json</code></summary>

```json
{
  "mcpServers": {
    "reqlog": {
      "command": "uvx",
      "args": ["reqlog", "mcp", "--db", "reqlog.db"]
    }
  }
}
```
</details>

<details>
<summary><b>Cursor</b> — <code>.cursor/mcp.json</code></summary>

```json
{
  "mcpServers": {
    "reqlog": {
      "command": "uvx",
      "args": ["reqlog", "mcp", "--db", "reqlog.db"]
    }
  }
}
```
</details>

<details>
<summary><b>Local dev</b> — using <code>uv run</code> instead of <code>uvx</code></summary>

```json
{
  "mcpServers": {
    "reqlog": {
      "command": "uv",
      "args": ["run", "reqlog", "mcp", "--db", "reqlog.db"]
    }
  }
}
```
</details>

Install the MCP extra with `pip install reqlog[mcp]` (not needed when using `uvx`).

### MCP Tools

| Tool | What the agent asks | What it gets |
|------|-------------------|--------------|
| `get_recent_requests` | "What just happened?" | Recent HTTP logs with filters for status, path, method, duration, time window |
| `get_request_detail` | "Show me that 500." | Full headers + bodies for a specific request ID |
| `get_error_summary` | "What's broken?" | Aggregated 4xx/5xx report grouped by status code |
| `search_requests` | "Find requests containing 'user not found'" | Full-text search across paths, bodies, and headers |
| `get_endpoint_stats` | "Is /api/users slow?" | Per-endpoint p50/p95/p99 latency and status breakdown |

Two MCP resources provide ambient context: [`reqlog://status`](#) (session summary, error rate) and [`reqlog://recent-errors`](#) (last 5 errors in compact format).

The `--redact-bodies` flag strips all bodies from MCP responses, serving only metadata. Standard sanitization (header masking, body field redaction) always applies.

---

## Output Formats

Five built-in formats. Set via `format=` or the `REQLOG_FORMAT` env var. reqlog auto-detects your environment — `ai` format inside Claude Code/Cursor/Aider, `compact` in TTY, `json` otherwise.

### `panel` — two-column request/response

Side-by-side layout with aligned headers and syntax-highlighted JSON bodies.

![panel format](docs/images/panel.svg)

### `ai` — token-efficient for LLMs

Automatically selected inside Claude Code, Cursor, and Aider. Compact JSON, no Rich overhead.

![ai format](docs/images/ai.svg)

### `compact` — default for terminals

Morgan-style one-liner with color-coded status and method.

![compact format](docs/images/compact.svg)

### `verbose` — vertical panels

Stacked Rich panels with full headers and response details.

![verbose format](docs/images/verbose.svg)

### `json` — JSON-Lines

One object per request. Default when stderr is not a TTY.

```
{"method":"GET","path":"/api/users","status_code":200,"duration_ms":8.2,"timestamp":"2026-02-14T14:23:46"}
```

---

## CLI

A built-in command-line interface for querying SQLite-persisted logs.

```bash
reqlog tail --db reqlog.db                        # Live-tail with color
reqlog tail --status 500 --slow 100               # Only slow errors
reqlog tail --method POST --path /api/users       # Filter by method + path
reqlog stats --db reqlog.db --minutes 60          # Aggregated stats with p50/p95/p99
reqlog export --db reqlog.db --format json        # Export as JSON-Lines
reqlog export --format csv --since "1 hour ago"   # Time-filtered CSV export
```

<details>
<summary>Example <code>reqlog stats</code> output</summary>

```
Request Stats (last 60 min)

  Total requests: 847
  Avg duration:   45.2ms
  P50:            12.3ms
  P95:            234.5ms
  P99:            890.1ms

  Status Codes        HTTP Methods       Top 10 Slowest Endpoints
  Class  Count        Method  Count      Method  Path               Avg (ms)
  2xx    723          GET     612        GET     /api/reports        520.1
  4xx    98           POST    187        POST    /api/auth/login     230.4
  5xx    26           PUT     32         POST    /api/users          145.2
                      DELETE  16
```
</details>

---

## Backends

reqlog fans out to multiple backends in parallel. When no `backends=` list is provided, it defaults to a single `ConsoleBackend` with the auto-detected format.

```python
from reqlog import ReqlogMiddleware, ConsoleBackend, SQLiteBackend
from reqlog.backends.file import FileBackend

app.add_middleware(
    ReqlogMiddleware,
    capture_body=True,
    backends=[
        ConsoleBackend(format="compact"),        # Terminal output
        SQLiteBackend(db_path="reqlog.db"),       # Queryable storage (CLI + MCP)
        FileBackend(path="requests.jsonl"),       # Rotating log files
    ],
)
```

| Backend | Use case | Key options |
|---------|----------|-------------|
| **ConsoleBackend** | Terminal output via Rich | `format`, `max_body_display` |
| **SQLiteBackend** | Persistent storage for CLI + MCP | `db_path`, `max_rows` (auto-prune), `wal_mode` |
| **FileBackend** | Rotating log files (JSON/text/AI) | `path`, `format`, `max_size_mb`, `max_files` |
| **MemoryBackend** | Testing and debugging | `max_size` (ring buffer capacity) |

SQLiteBackend uses a background writer thread — non-blocking, queue-bounded, with graceful shutdown via `atexit`. See [Backends Guide](docs/backends.md) for the full API and custom backend protocol.

---

## Configuration

All options work as keyword arguments to `ReqlogMiddleware`, as fields on `ReqlogConfig`, or as `REQLOG_*` environment variables.

```python
app.add_middleware(
    ReqlogMiddleware,
    capture_body=True,           # Log request/response bodies (default: False)
    capture_headers=True,        # Log headers (default: False)
    format="compact",            # "compact" | "verbose" | "panel" | "ai" | "json"
    exclude_paths=["/health"],   # Paths to skip
    sample_rate=1.0,             # 1.0 = all, 0.1 = 10%
)
```

<details>
<summary><b>Full configuration reference</b></summary>

```python
from reqlog import ReqlogMiddleware, ReqlogConfig

config = ReqlogConfig(
    capture_body=True,
    capture_headers=True,
    format="compact",
    max_body_size=64_000,                        # Truncate bodies larger than this (bytes)
    exclude_paths=["/health", "/healthz", "/ready", "/metrics", "/favicon.ico"],
    exclude_methods=["OPTIONS"],
    include_paths=None,                          # If set, only log these paths
    sample_rate=1.0,
    sanitize_headers=[                           # Glob patterns supported
        "Authorization", "Cookie", "Set-Cookie", "X-API-Key", "X-Auth-Token",
    ],
    sanitize_body_fields=[                       # Recursive JSON walk
        "password", "secret", "token", "access_token", "credit_card",
    ],
    generate_request_id=True,
    request_id_header="X-Request-ID",
)

app.add_middleware(ReqlogMiddleware, config=config)
```

**Environment variable overrides** — take precedence over programmatic defaults:

| Variable | Type | Example |
|----------|------|---------|
| `REQLOG_FORMAT` | str | `compact`, `panel`, `ai`, `json` |
| `REQLOG_CAPTURE_BODY` | bool | `true`, `1`, `yes` |
| `REQLOG_CAPTURE_HEADERS` | bool | `true` |
| `REQLOG_SAMPLE_RATE` | float | `0.1` |
| `REQLOG_MAX_BODY_SIZE` | int | `128000` |
| `REQLOG_EXCLUDE_PATHS` | list | `/health,/metrics,/ready` |
| `REQLOG_EXCLUDE_METHODS` | list | `OPTIONS,HEAD` |
| `REQLOG_REQUEST_ID_HEADER` | str | `X-Trace-ID` |

</details>

**Sanitization** is on by default — Authorization, Cookie, and API key headers are redacted, along with password/token/secret body fields. Redaction uses glob matching on headers and recursive JSON walks on bodies.

---

## Django Support

```python
# settings.py

MIDDLEWARE = [
    "reqlog.middleware.django.ReqlogMiddleware",
    # ... other middleware
]

REQLOG = {
    "CAPTURE_BODY": True,
    "CAPTURE_HEADERS": True,
    "FORMAT": "compact",
    "EXCLUDE_PATHS": ["/health", "/admin/jsi18n/"],
}
```

The Django middleware auto-detects sync (WSGI) vs. async (ASGI) and handles both. In WSGI mode, logging runs in a background thread — zero latency impact on the request cycle.

<details>
<summary>Django with persistent backends</summary>

```python
from reqlog import ConsoleBackend, SQLiteBackend

REQLOG = {
    "CAPTURE_BODY": True,
    "FORMAT": "compact",
    "BACKENDS": [
        ConsoleBackend(format="compact"),
        SQLiteBackend(db_path="reqlog.db"),
    ],
}
```
</details>

---

## Documentation

Detailed guides for each component:

- **[Backends](docs/backends.md)** — Console, SQLite, File, Memory backends and custom backend protocol
- **[CLI](docs/cli.md)** — `tail`, `stats`, `export`, `mcp` commands with examples
- **[Configuration](docs/configuration.md)** — All options, env vars, and Django settings
- **[Formatters](docs/formatters.md)** — Compact, verbose, panel, AI, and JSON formats
- **[Middleware](docs/middleware.md)** — FastAPI/ASGI and Django integration details

---

## Development

```bash
git clone https://github.com/ankitksr/reqlog.git
cd reqlog && uv sync

uv run pytest tests/ -v          # 184 tests
uv run mypy src/                 # Type checking (strict)
uv run ruff check src/ tests/    # Linting
uv build                         # Build package
```

```bash
uv run uvicorn examples.demo:app --reload    # FastAPI demo
uv run python examples/preview_cli.py        # All formatters + CLI features
uv run python examples/demo_mcp.py           # MCP demo (sample DB + SSE server)
```

<details>
<summary>Project structure</summary>

```
src/reqlog/
  core/         models.py · config.py · pipeline.py · buffer.py
  middleware/   asgi.py (pure ASGI) · django.py (sync + async)
  backends/     console.py · sqlite.py · file.py · memory.py · protocols.py
  formatters/   compact · verbose · panel · ai · json · text_format (shared)
  sanitize/     redact.py (header glob + recursive JSON walk)
  mcp/          server.py (5 tools + 2 resources) · formatting.py
  cli/          main.py (tail · stats · export · mcp · demo)
```
</details>

## License

MIT. See [LICENSE](LICENSE).