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
+169
View File
@@ -1752,3 +1752,172 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/debug/status:
get:
tags: [Debug]
summary: Check if log streaming is enabled
description: Returns whether the log streaming feature is enabled at runtime. No authentication required.
security: []
responses:
'200':
description: Feature status returned successfully
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
example: true
/api/debug/server-logs:
get:
tags: [Debug]
summary: Stream server logs in real-time
description: |
Streams server-side standard output (stdout/stderr) logs via Server-Sent Events (SSE).
**Security Policies:**
- **Subnet IP Filtering**: IP must match subnet ranges configured in `LOG_ALLOW_SUBNETS` (if set).
- **Bypass Header**: Direct bypass if `X-Webhook-Secret` header matches the configured `SOFARR_WEBHOOK_SECRET`.
- **Session Auth**: Emby session cookie `emby_user` where user is an administrator.
- **Basic Auth Fallback**: `Authorization` header containing credentials of a valid Emby administrator.
security:
- CookieAuth: []
parameters:
- name: X-Webhook-Secret
in: header
required: false
schema:
type: string
description: Fast-track webhook secret bypass token
responses:
'200':
description: Event stream established
content:
text/event-stream:
schema:
type: string
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden (feature disabled or IP not in subnet allowlist)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/api/debug/client-logs:
get:
tags: [Debug]
summary: Stream client console logs in real-time
description: |
Streams client-side console logs via Server-Sent Events (SSE).
**Security Policies:**
- **Subnet IP Filtering**: IP must match subnet ranges configured in `LOG_ALLOW_SUBNETS` (if set).
- **Bypass Header**: Direct bypass if `X-Webhook-Secret` header matches the configured `SOFARR_WEBHOOK_SECRET`.
- **Session Auth**: Emby session cookie `emby_user` where user is an administrator.
- **Basic Auth Fallback**: `Authorization` header containing credentials of a valid Emby administrator.
security:
- CookieAuth: []
parameters:
- name: X-Webhook-Secret
in: header
required: false
schema:
type: string
description: Fast-track webhook secret bypass token
responses:
'200':
description: Event stream established
content:
text/event-stream:
schema:
type: string
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden (feature disabled or IP not in subnet allowlist)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
post:
tags: [Debug]
summary: Ingest client console logs
description: |
Ingests a batch of client-side console logs into the server-side rolling clientLogBuffer.
**Security Policies:**
- **Subnet IP Filtering**: IP must match subnet ranges configured in `LOG_ALLOW_SUBNETS` (if set).
- **Bypass Header**: Direct bypass if `X-Webhook-Secret` header matches the configured `SOFARR_WEBHOOK_SECRET`.
- **Session Auth**: Emby session cookie `emby_user` where user is an administrator.
- **Basic Auth Fallback**: `Authorization` header containing credentials of a valid Emby administrator.
security:
- CookieAuth: []
parameters:
- name: X-Webhook-Secret
in: header
required: false
schema:
type: string
description: Fast-track webhook secret bypass token
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
type: object
required: [level, message]
properties:
timestamp:
type: string
format: date-time
level:
type: string
enum: [info, warn, error]
message:
type: string
responses:
'200':
description: Logs ingested successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
count:
type: integer
'400':
description: Invalid JSON body
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'401':
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'403':
description: Forbidden (feature disabled or IP not in subnet allowlist)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'