Monitoring

Radiant's monitoring system captures runtime events — request lifecycle, errors, health checks, traces — and exposes them through HTTP endpoints for external tools and Radiant Desktop to observe live backends.

Enabling Monitoring

Monitoring is disabled by default. Enable it in config.radiant:

config {
  monitoring: {
    enabled: true
    apiKey: env("MONITORING_API_KEY")
    healthCheck: {
      enabled: true
      path: "/health"
      requiresAuth: false
    }
    requestId: { enabled: true }
  }
}

The apiKey secures the monitoring endpoints with Bearer token authentication. If unset, the runtime logs a warning and endpoints are accessible without authentication.

Endpoints

When enabled, four endpoints are available under {apiPrefix}/monitor (default: /api/monitor):

GET /api/monitor/events

Query buffered monitoring events. Supports query filters:

ParameterTypeDescription
typeString or String[]Filter by event type (e.g., request.completed, request.error).
severityString or String[]Filter by severity (debug, info, warn, error, fatal).
sinceString (ISO timestamp)Only events after this timestamp.
limitNumberMax events to return (default: 100, max: 1000).
requestIdStringFilter by request ID.
collectionStringFilter by collection slug.
sourceStringFilter by event source.
statusNumberFilter by HTTP status code.
curl -H "Authorization: Bearer $MONITORING_API_KEY" \
  "http://localhost:3000/api/monitor/events?type=request.error&limit=50"

GET /api/monitor/metrics

Aggregate summary of buffered events:

curl -H "Authorization: Bearer $MONITORING_API_KEY" \
  http://localhost:3000/api/monitor/metrics
{
  "total": 1523,
  "byType": {
    "request.completed": 1400,
    "request.error": 23,
    "trace": 100,
    "health.checked": 5
  },
  "bySeverity": {
    "info": 1405,
    "error": 23,
    "warn": 5
  },
  "requests": {
    "total": 1400,
    "errors": 23,
    "averageDurationMs": 12.4
  },
  "lastEvent": { "type": "request.completed", "status": 200, "durationMs": 8 }
}

GET /api/monitor/metrics/prometheus

Prometheus-compatible plain-text metrics endpoint, aggregated from the buffer summary. Useful for scraping with Prometheus servers.

curl -H "Authorization: Bearer $MONITORING_API_KEY" \
  http://localhost:3000/api/monitor/metrics/prometheus
# HELP radiant_events_total Total monitoring events recorded
# TYPE radiant_events_total counter
radiant_events_total 1523
# HELP radiant_requests_total Total number of HTTP requests
# TYPE radiant_requests_total counter
radiant_requests_total 1400
# HELP radiant_requests_errors_total Total number of HTTP request errors
# TYPE radiant_requests_errors_total counter
radiant_requests_errors_total 23

GET /api/monitor/health

Database and cache health check. Returns 200 for ok/degraded, 503 for error:

curl http://localhost:3000/health
{
  "status": "ok",
  "timestamp": "2026-07-01T12:00:00.000Z",
  "uptime": 3600,
  "checks": {
    "database": { "status": "ok", "latencyMs": 2 },
    "cache": { "status": "ok", "latencyMs": 0 }
  }
}

The health endpoint does not require authentication by default. Set healthCheck.requiresAuth: true to require the monitoring API key.

GET /api/monitor/stream

Server-Sent Events (SSE) live stream. Replays filtered backlog, then streams new events as they occur. Supports the same query filters as /events. A : heartbeat comment frame is sent every 30 seconds to keep the connection alive through proxies and load balancers.

curl -N -H "Authorization: Bearer $MONITORING_API_KEY" \
  "http://localhost:3000/api/monitor/stream?type=request.error"
event: request.error
data: {"id":"...","type":"request.error","status":500,"durationMs":45,"severity":"error",...}

: heartbeat

event: request.error
data: {"id":"...","type":"request.error","status":503,"durationMs":12,"severity":"error",...}

Event Types

TypeWhen emittedKey fields
request.completedAfter every successful requestmethod, path, status, durationMs, userId
request.errorAfter a request that returned 5xx, or threwmethod, path, status, durationMs, severity, message
runtime.errorUncaught error in the request pipelinemethod, path, status, severity, message, metadata.route
traceAfter every request (regardless of status)method, path, status, durationMs, metadata.route
health.checkedAfter each health check executionseverity, metadata (full health result)
request.idWhen request ID tracking is enabledrequestId

Exporters

Exporters batch events and forward them to external destinations — log aggregators, observability platforms, webhooks, or Radiant Desktop. Add them programmatically:

// src/app.ts
import { app } from "./app";

app.monitoring?.addExporter({
  name: "log-console",
  kind: "log",
  batchSize: 50,
  flushIntervalMs: 5000,
  export(batch) {
    for (const event of batch.events) {
      console.log(`[${event.severity}] ${event.type}: ${event.method} ${event.path} ${event.status} ${event.durationMs}ms`);
    }
  },
  onError({ error, exporterName }) {
    console.error(`Exporter ${exporterName} failed:`, error);
  },
});

Exporter Properties

PropertyTypeDescription
nameStringUnique identifier for the exporter.
kindStringExporter type: log, opentelemetry, webhook, dashboard, codesordinate-pro, custom.
batchSizeNumberEvents per batch before auto-flush (default: 25).
flushIntervalMsNumberAuto-flush interval in milliseconds.
filterQuery object or functionFilter which events this exporter receives.
export(batch)FunctionCalled with a batch of events. Can be async.
onError(error)FunctionCalled when export() throws.

Filtering

Exporters accept either a query filter object or a predicate function:

// Query filter — only errors
app.monitoring?.addExporter({
  name: "error-webhook",
  kind: "webhook",
  filter: { type: "request.error", severity: "error" },
  batchSize: 10,
  async export(batch) {
    await fetch("https://my-webhook.example.com/radiant-errors", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(batch.events),
    });
  },
});

// Function filter — only slow requests
app.monitoring?.addExporter({
  name: "slow-requests",
  kind: "log",
  filter: (event) => event.type === "request.completed" && (event.durationMs ?? 0) > 1000,
  batchSize: 20,
  export(batch) {
    console.warn(`${batch.events.length} slow requests detected`);
  },
});

Event Buffer

Events are stored in an in-memory ring buffer (default capacity: 1000 events). The buffer is not persisted — it's intended for live observation, not long-term storage. Use exporters to forward events to persistent stores.

Request ID Tracking

When requestId.enabled is true, every request gets a unique ID. The runtime:

  1. Checks for an incoming X-Request-ID header and reuses it if present.
  2. Otherwise generates a UUID.
  3. Adds X-Request-ID to the response headers.
  4. Includes the requestId in all monitoring events for that request.

This lets you trace a single request across logs, metrics, and external systems.

External Tool Integration

Radiant Desktop

Radiant Desktop can connect to any running Radiant backend that has monitoring enabled. Point it at the /api/monitor/stream endpoint to get a live event feed, or poll /api/monitor/metrics for periodic summaries.

OpenTelemetry

Radiant provides a native, zero-dependency OpenTelemetry exporter that pushes standard OTLP JSON Logs directly to your collector.

import { app } from "./app";
import { OpenTelemetryExporter } from "@codesordinatestudio/radiant";

app.monitoring?.addExporter(new OpenTelemetryExporter({
  endpoint: "http://localhost:4318/v1/logs",
  serviceName: "my-radiant-app",
  batchSize: 100,
  flushIntervalMs: 5000,
  headers: {
    // Optional auth headers if your collector requires them
    // "Authorization": "Bearer token"
  }
}));

Webhook

Forward events to any HTTP endpoint:

app.monitoring?.addExporter({
  name: "slack-alerts",
  kind: "webhook",
  filter: { severity: ["error", "fatal"] },
  batchSize: 1,
  async export(batch) {
    for (const event of batch.events) {
      await fetch(process.env.SLACK_WEBHOOK_URL!, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ text: `Radiant alert: ${event.type}${event.message}` }),
      });
    }
  },
});