Files
MyClub/DOCS/ERROR_CENTER_API.md
Tomas Dvorak 8762bde4bf dev day #89
2025-11-11 10:29:30 +01:00

211 lines
7.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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