feat: implement togglable debug log streaming for server stdout/stderr and client console logs

- Created server/utils/logCapture.js to intercept and buffer server output, stripping ANSI escape codes.
- Created server/middleware/logStreamAuth.js enforcing subnet IP filtering (LOG_ALLOW_SUBNETS), Emby session cookie, Basic Auth fallback, and X-Webhook-Secret header bypass.
- Created server/routes/debug.js with SSE streams /api/debug/server-logs, /api/debug/client-logs and batched POST /api/debug/client-logs. Exposes public configuration status at /api/debug/status.
- Integrated log capture and mounted debug routes in server/app.js and server/index.js.
- Implemented client/src/utils/clientLogCapture.js in the frontend SPA to hook console log/warn/error and flush batched console events.
- Documented all endpoints in OpenAPI server/openapi.yaml, ARCHITECTURE.md, and README.md.
- Wrote route integration tests and frontend console capture tests, with full validation in swagger-coverage.
This commit is contained in:
2026-05-24 11:31:36 +01:00
parent afc940aba7
commit 3c6791658c
12 changed files with 1127 additions and 0 deletions
+48
View File
@@ -994,6 +994,42 @@ For AI agents and automated tooling, every endpoint includes:
- Lints `server/openapi.yaml` with `@stoplight/spectral-cli`
- Runs the coverage test suite on every push
### 7.5 Real-Time Debug Log Streaming Subsystem
sofarr provides a togglable, real-time log capturing and streaming engine allowing developer-administrators to view server standard output stream activity and browser console log activity for easy debugging in production.
#### Architecture
```mermaid
flowchart LR
subgraph Browser (SPA)
console["console.log/warn/error"] --> queue["logQueue (batched)"]
queue --> |POST /api/debug/client-logs| ingestionRoute["POST router handler"]
end
subgraph Node.js (Server)
stdout["process.stdout.write"] --> capture["processStreamData()"]
stderr["process.stderr.write"] --> capture
capture --> |stripAnsi()| serverBuffer["logBuffer (rolling 1000 lines)"]
capture --> |emit('server-log')| serverSse["GET /api/debug/server-logs (SSE)"]
ingestionRoute --> clientBuffer["clientLogBuffer (rolling 1000 lines)"]
ingestionRoute --> |emit('client-log')| clientSse["GET /api/debug/client-logs (SSE)"]
end
```
#### In-Process Interceptor (Stdout & Stderr)
To guarantee 100% logging completeness without requiring complex and insecure external Docker daemon sockets, the server hooks directly into standard output stream writers `process.stdout.write` and `process.stderr.write` during the process boot cycle.
- Custom accumulators process streams, strip ANSI terminal colors (colors are stripped using a standard regex matching all terminal escape codes), and split incoming chunks into structured lines.
- Historical lines are appended to a rolling in-memory array `logBuffer` capped at 1,000 entries.
- Real-time logging events are broadcasted via a central `EventEmitter` allowing connected SSE stream clients to receive updates instantly.
#### Client Console Log Capture
To assist developers in troubleshooting client-side runtime errors without access to developer consoles (e.g., in embedded WebViews, smart TVs, or custom mobile browser instances), the SPA implements an automatic logging interceptor.
- If `/api/debug/status` indicates log streaming is active, the bootstrap process replaces global `console.log`, `console.warn`, and `console.error` methods.
- Logs are added to an in-memory queue and flushed in batches using a stateless `POST /api/debug/client-logs` request every 2,000ms (or when the queue size reaches 20 items) to prevent browser thread blocking.
- A synchronous cleanup check is registered via standard `beforeunload` to flush any remaining logs using `navigator.sendBeacon()` or `keepalive: true` fetch on page refresh or navigate.
---
## 8. Directory Structure
@@ -1203,6 +1239,18 @@ Each instance receives an `id` derived from `name` (or index if unnamed), used a
| **Authorisation matrix** | Regular users see only their own downloads. Admins can view all users, see paths and *arr links, and blocklist any download. Non-admins can only blocklist when import issues exist or (for qBittorrent) the torrent is >1 h old with <100% availability. |
| **Container security** | Docker image runs as the non-root `node` user (UID 1000). `/app/data` is owned by `node`. |
### 10.4 Debug Log Streaming Security
Access to the debug log stream endpoints (`/api/debug/server-logs` and `/api/debug/client-logs`) is secured via a strict multi-layered policy:
| Layer | Mechanism |
|-------|-----------|
| **Feature lock toggle** | Strictly disabled by default; only enableable when environment variable `ENABLE_LOG_STREAM=true` is set. |
| **Subnet filtering** | If environment variable `LOG_ALLOW_SUBNETS` is configured (using standard comma-separated CIDR notation), incoming client IPs (`req.ip`) are parsed and validated via the `ipaddr.js` library. Unmatched subnets receive a `403 Forbidden` response immediately. |
| **Fast-path webhook bypass** | A valid webhook secret bypasses the active Emby authentication layer when provided on the `X-Webhook-Secret` header. |
| **Active Emby session** | Validates that an active `emby_user` session cookie is present and that the authenticated Emby user is an administrator. |
| **Emby basic auth fallback** | Allows Basic Authentication headers by performing an on-demand asynchronous credentials check against Emby's `/Users/authenticatebyname` and `/Users/{id}` endpoints to assert active policy administrator status. |
---
## 11. Technology Stack