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

7.2 KiB
Raw Blame History

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