This commit is contained in:
Tomas Dvorak
2025-11-11 10:29:30 +01:00
parent d5b4faea61
commit 8762bde4bf
139 changed files with 7240 additions and 2870 deletions
+210
View File
@@ -0,0 +1,210 @@
# Error Center API
Centralized error reporting across frontend (TS/TSX), backend (Go), and infra (future: Docker). This document specifies the request/response schema and endpoints implemented in this repository so you can integrate additional producers and build the admin UI.
## Overview
- Public ingest endpoint: POST /api/v1/errors
- Admin browsing: GET /api/v1/admin/errors, GET /api/v1/admin/errors/:id
- Storage model: `error_events` table via GORM model `ErrorEvent`
- Capture hooks in-app:
- Backend: panic recovery + 5xx response reporter
- Frontend: window.onerror, unhandledrejection, React ErrorBoundary, Axios 5xx interceptor
- Rate limiting: per-IP+path, applied to ingest endpoint
## Data Model: ErrorEvent
Returned by admin endpoints. JSON field names match below.
```
{
id: number,
created_at: string, // ISO timestamp (ingestion time)
updated_at: string, // ISO timestamp
origin: string, // e.g. "frontend" | "backend" | "docker"
language: string, // e.g. "ts" | "tsx" | "go"
severity: string, // e.g. "error" | "warn" | "fatal"
message: string,
stack: string,
component: string,
file: string,
line: number,
column: number,
url: string,
method: string, // HTTP method when applicable
status: number, // HTTP status when applicable
request_id: string, // correlates backend <-> frontend
user_id: number | null,
session_token: string,
tags: object, // JSON map (string -> string)
context: object, // JSON map (string -> any)
env: string, // e.g. "development" | "production"
version: string,
hostname: string,
occurred_at: string // ISO timestamp of when error actually happened
}
```
## Endpoint: POST /api/v1/errors (Public ingest)
Ingests a single error event.
- Method: POST
- Path: `/api/v1/errors`
- Headers:
- `Content-Type: application/json`
- Optional: `Authorization: Bearer <token>` (not required by default; used if you proxy to external collectors)
- Rate limit: 120 requests per minute per IP+path. On limit exceeded: `429 Too Many Requests` and `Retry-After` header (seconds).
### Request body (JSON)
```
{
origin: string, // required, e.g. "frontend" | "backend" | "docker"
language?: string, // e.g. "ts" | "tsx" | "go"
severity?: string, // "error" | "warn" | "fatal" (default depends on sender)
message: string, // required
stack?: string,
component?: string,
file?: string,
line?: number,
column?: number,
url?: string,
method?: string,
status?: number,
request_id?: string,
user_id?: number,
session_token?: string,
tags?: { [k: string]: string },
context?: { [k: string]: any },
env?: string,
version?: string,
hostname?: string,
occurred_at?: string // ISO (RFC3339). Defaults to server time if omitted
}
```
### Responses
- 201 Created
```
{ "id": <number> }
```
- 400 Bad Request (invalid JSON/payload)
```
{ "error": "invalid payload" }
```
- 429 Too Many Requests (rate limited)
```
{ "error": "Příliš mnoho požadavků, zkuste to prosím později." }
```
- 500 Internal Server Error (storage issue)
```
{ "error": "failed to store" }
```
### cURL example
```
curl -X POST http://localhost:8080/api/v1/errors \
-H 'Content-Type: application/json' \
-d '{
"origin":"frontend",
"language":"tsx",
"severity":"error",
"message":"TypeError: cannot read properties of undefined",
"stack":"at Component (App.tsx:42:7)",
"url":"/admin/errors?filter=today",
"method":"GET",
"status":500,
"request_id":"b28a8a2b-2f3d-452a-9a5d-1ec74e53a2d4",
"context": {"userAgent":"...","viewport":{"w":1440,"h":900}},
"env":"development",
"occurred_at":"2025-11-06T12:00:00Z"
}'
```
## Endpoint: GET /api/v1/admin/errors (Admin list)
Lists error events with filtering and pagination.
- Method: GET
- Path: `/api/v1/admin/errors`
- Auth: Admin role required (standard admin authentication of this app)
- Query params:
- `origin`: string (exact match: frontend | backend | docker)
- `severity`: string (exact match: fatal | error | warn)
- `method`: string (exact match: GET | POST | PUT | PATCH | DELETE)
- `status`: number (exact match HTTP status)
- `search`: string (substring, ILIKE on message, stack, url)
- `from`: string (RFC3339, filters `occurred_at >= from`)
- `to`: string (RFC3339, filters `occurred_at <= to`)
- `page`: number (default 1, min 1)
- `limit`: number (default 20, min 1, max 200)
### Response
- 200 OK
```
{
"items": [ ErrorEvent, ... ],
"total": <number>
}
```
### Example
```
curl -b cookies.txt \
'http://localhost:8080/api/v1/admin/errors?origin=frontend&severity=error&search=TypeError&page=1&limit=50'
```
## Endpoint: GET /api/v1/admin/errors/:id (Admin detail)
Returns a single error event by ID.
- Method: GET
- Path: `/api/v1/admin/errors/:id`
- Auth: Admin role required
### Responses
- 200 OK — returns the `ErrorEvent` JSON
- 404 Not Found — `{ "error": "not found" }`
- 500 Internal Server Error — `{ "error": "failed to load" }`
## Built-in Producers (in this repo)
- Frontend reporter (src/services/errorReporter.ts)
- Sends errors to `/api/v1/errors` by default
- Uses localStorage overrides when present:
- `fc_error_ingest_url`, `fc_error_ingest_token`
- Or build-time env vars:
- `REACT_APP_ERROR_INGEST_URL`, `REACT_APP_ERROR_INGEST_TOKEN`
- Installed in index.tsx:
- Global handlers: `window.onerror`, `unhandledrejection`
- ErrorBoundary calls reporter on render errors
- Axios 5xx interceptor (src/services/api.ts) reports server errors
- Backend reporter (internal/services/error_reporter.go)
- Panic recovery middleware and 5xx status reporter send events
- Destination:
- If `ERROR_INGEST_URL` configured, report to that URL with `ERROR_INGEST_TOKEN` if set
- Otherwise, fallback to this servers public API base + `/errors`
## Configuration
- Backend env:
- `ERROR_INGEST_URL` (optional): external collector URL
- `ERROR_INGEST_TOKEN` (optional): bearer token for external collector
- Frontend runtime overrides (optional):
- `localStorage.setItem('fc_error_ingest_url', 'https://example.com/ingest')`
- `localStorage.setItem('fc_error_ingest_token', 'YOUR_TOKEN')`
- Defaults require no configuration; all errors post to this app at `/api/v1/errors`.
## Correlation and Metadata
- `X-Request-ID` header is exposed; frontend interceptor forwards it in payload as `request_id`.
- `occurred_at` denotes when the error happened; `created_at` is ingestion time.
- `tags` is a flat string map; `context` allows arbitrary structured metadata.
## Rate Limiting Details (Ingest)
- Scope: per client IP and request path
- Window: 60 seconds
- Max: 120 requests per window
- Exceeding limit: `429 Too Many Requests` with `Retry-After` (seconds remaining)
## Notes
- CORS: The ingest endpoint follows the apps global CORS configuration.
- PII: Consider redacting sensitive data before sending in `message`, `stack`, `context`.
- Deduplication: The frontend reporter applies a basic burst deduplication (1.5s window) client-side; server stores all events.
---
Last updated: 2025-11-06