Observability
Three edge-safe primitives, built on the existing middleware + hook system (no dependency):
- Trace id per request (
traceId()) — propagated or generated, readable in loaders/actions. - Structured request logs (
requestLogging()) — method, path, status, duration, trace id. - Metrics (
createMetrics()) — counters/latencies from app hooks, exposed as JSON.
Wiring
// src/app.ts
import { createMetrics, createMetricsRoute, requestLogging, traceId } from "@ramonyx/server";
import { mergeHooks } from "ramonyx";
const metrics = createMetrics();
const handler = createHandler({
routes: [
...apiRoutes,
createMetricsRoute(metrics), // GET /metrics
...createPageRoutes({ pages, renderer, hooks: mergeHooks(appHooks, metrics.hooks) }),
],
middleware: [traceId(), requestLogging()],
hooks: mergeHooks(appHooks, metrics.hooks),
});Order matters: traceId() must run before requestLogging() so the log line carries the id.
Trace id
- Uses the incoming
x-request-idheader when present (proxy/client propagation), otherwise generates one (UUID, with agetRandomValuesfallback). - Rewrites the request with the header set, so loaders/actions can read it; sets the same header on the response.
// inside a loader
const id = request.headers.get("x-request-id");Custom header name: traceId({ header: "x-trace-id" }).
Structured logs
requestLogging(); // message "request"
requestLogging({ message: "http" }); // custom messagerequestLogging() emits one info line per request through the app logger:
{"level":"info","message":"request","method":"GET","path":"/","status":200,"ms":12,"traceId":"…"}Ship it to any collector (Fluent Bit, Vector, OTLP log exporter). Secrets/PII must not be logged — the framework only emits method/path/status/duration/trace id.
Metrics
createMetrics() returns { hooks, snapshot() }:
{
"requests": { "total": 120, "byStatusClass": { "2xx": 118, "5xx": 2 },
"durationMs": { "sum": 1840, "count": 120, "max": 210 } },
"loaders": { "count": 80, "cached": 42, "durationMs": { "sum": 620, "max": 90 } },
"renders": { "count": 118, "streaming": 110, "durationMs": { "sum": 980, "max": 140 } },
"invalidations": 3
}createMetricsRoute(metrics, { path: "/metrics" })serves the snapshot as JSON.- Schedule
metrics.snapshot()exports to OTLP/Prometheus from your platform, or pull/metrics. - Counters are cumulative since process start (per instance).
Notes & limitations
traceIdis a correlation id, not a full W3Ctraceparent; native OpenTelemetry spans are not built in (candidate if there is demand).- Metrics are process-local: multi-instance deployments should scrape each instance or aggregate in your platform.
- The hooks are zero-cost when unused; the helpers are edge-safe (no
node:*).