mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
211 lines
7.2 KiB
Markdown
211 lines
7.2 KiB
Markdown
# 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 server’s 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 app’s global CORS configuration.
|
||
- PII: Consider redacting sensitive data before sending in `message`, `stack`, `context`.
|
||
- Deduplication: The frontend reporter applies a basic burst de‑duplication (1.5s window) client-side; server stores all events.
|
||
|
||
---
|
||
|
||
Last updated: 2025-11-06
|