mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
7.2 KiB
7.2 KiB
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_eventstable via GORM modelErrorEvent - 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 RequestsandRetry-Afterheader (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, filtersoccurred_at >= from)to: string (RFC3339, filtersoccurred_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
ErrorEventJSON - 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/errorsby 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
- Global handlers:
- Axios 5xx interceptor (src/services/api.ts) reports server errors
- Sends errors to
-
Backend reporter (internal/services/error_reporter.go)
- Panic recovery middleware and 5xx status reporter send events
- Destination:
- If
ERROR_INGEST_URLconfigured, report to that URL withERROR_INGEST_TOKENif set - Otherwise, fallback to this server’s public API base +
/errors
- If
Configuration
- Backend env:
ERROR_INGEST_URL(optional): external collector URLERROR_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-IDheader is exposed; frontend interceptor forwards it in payload asrequest_id.occurred_atdenotes when the error happened;created_atis ingestion time.tagsis a flat string map;contextallows 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 RequestswithRetry-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