mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
dev day #89
This commit is contained in:
@@ -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 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
|
||||
Reference in New Issue
Block a user