Skip to content

Log Streaming

MCP Hangar captures stderr output from subprocess and Docker providers and makes it available via REST API and WebSocket for real-time log viewing.

Architecture

Provider Process (stderr)
        |
   stderr-reader thread
        |
   ProviderLogBuffer (ring buffer, 1000 lines)
        |
   +----+----+
   |         |
GET /logs  WebSocket /ws/providers/{id}/logs
   |         |
 REST API  LogStreamBroadcaster

Each provider gets a dedicated ProviderLogBuffer -- a thread-safe ring buffer holding the most recent 1000 log lines. A background reader thread continuously reads the provider's stderr and appends lines to the buffer.

Log Line Format

Each log line is stored as a LogLine value object:

json
{
  "timestamp": "2026-03-23T10:15:30.123456",
  "line": "INFO: Server started on port 8080",
  "provider_id": "math",
  "stream": "stderr"
}

REST API

Get buffered logs

bash
GET /api/providers/{provider_id}/logs?lines=100

Returns the most recent lines entries from the ring buffer (default 100, max 1000).

Response:

json
{
  "logs": [
    {"timestamp": "...", "line": "...", "provider_id": "math", "stream": "stderr"},
    {"timestamp": "...", "line": "...", "provider_id": "math", "stream": "stderr"}
  ],
  "provider_id": "math",
  "count": 2
}

Returns an empty list if the provider exists but has no log buffer yet (provider not started). Returns 404 if the provider is not registered.

WebSocket

Live log stream

ws://localhost:8000/api/ws/providers/{provider_id}/logs

Connects to a live stream of log lines for a specific provider. New lines are pushed as they arrive from stderr.

Each message is a JSON object with the same LogLine format:

json
{"timestamp": "...", "line": "...", "provider_id": "math", "stream": "stderr"}

The WebSocket connection uses LogStreamBroadcaster, which registers an on_append callback on the provider's log buffer. When the buffer receives a new line, the broadcaster pushes it to all connected WebSocket clients.

Connection lifecycle

  1. Client connects to /api/ws/providers/{provider_id}/logs.
  2. Server validates that the provider exists.
  3. Server registers a broadcast callback on the log buffer.
  4. Log lines are pushed to the client as they arrive.
  5. On disconnect, the callback is deregistered.

Configuration

Log capture is automatic for subprocess and Docker providers. No configuration is required.

BehaviorValue
Buffer size1000 lines per provider
Captured streamstderr only
Buffer typeThread-safe ring buffer
PersistenceIn-memory only (lost on restart)

Dashboard Integration

The Dashboard UI includes a LogViewer component on the Provider Detail page. It:

  1. Fetches the initial buffer contents via GET /api/providers/{id}/logs.
  2. Opens a WebSocket connection for live updates.
  3. Displays logs with auto-scroll and timestamp formatting.
  4. Reconnects automatically on WebSocket disconnect.

The useProviderLogs hook manages the REST + WebSocket lifecycle:

typescript
const { logs, isConnected } = useProviderLogs(providerId);

Supported Provider Modes

ModeLog CaptureNotes
subprocessstderr via PIPEReader thread reads process.stderr
dockerstderr via PIPEDockerLauncher attaches to container stderr
remoteNot capturedRemote providers manage their own logs

Released under the MIT License.