Skip to content

Observability

Observability policies provide request logging, metrics collection, and health endpoints.

PolicyPriorityPurpose
requestLog0 (Priority.OBSERVABILITY)Structured request/response logs.
assignMetrics0Attach extra metric tags onto request context.
metricsReporter1 (Priority.METRICS)Record request counters/histograms/errors.
healthroute factory (not policy)Liveness/readiness endpoint route.

Structured JSON logs for each request.

import { requestLog } from "@homegrower-club/stoma";
interface RequestLogConfig {
extractFields?: (c: Context) => Record<string, unknown>;
sink?: (entry: LogEntry) => void | Promise<void>; // default: console.log(JSON)
ipHeaders?: string[];
logRequestBody?: boolean; // default: false
logResponseBody?: boolean; // default: false
maxBodyLength?: number; // default: 8192
redactPaths?: string[];
skip?: (c: Context) => boolean | Promise<boolean>;
}

timestamp, requestId, method, path, statusCode, durationMs, clientIp, userAgent, gatewayName, routePath, upstream, optional traceId, spanId, requestBody, responseBody, and extra.


Resolve static/dynamic metric tags and store them as c.get("_metricsTags").

import { assignMetrics } from "@homegrower-club/stoma";
interface AssignMetricsConfig {
tags: Record<string, string | ((c: Context) => string | Promise<string>)>;
skip?: (c: Context) => boolean | Promise<boolean>;
}

Record request-level metrics through a MetricsCollector.

import { metricsReporter } from "@homegrower-club/stoma";
interface MetricsReporterConfig {
collector: MetricsCollector;
skip?: (c: Context) => boolean | Promise<boolean>;
}
  • gateway_requests_total (counter)
  • gateway_request_duration_ms (histogram)
  • gateway_request_errors_total (counter for status >= 400)
  • gateway_policy_duration_ms (histogram, when _policyTimings exists)

Tags include method, path, status, and gateway, plus custom tags from assignMetrics.

interface MetricsCollector {
increment(name: string, value?: number, tags?: Record<string, string>): void;
histogram(name: string, value: number, tags?: Record<string, string>): void;
gauge(name: string, value: number, tags?: Record<string, string>): void;
snapshot(): MetricsSnapshot;
reset(): void;
}

health() returns a RouteConfig, not a policy. Add it directly to routes.

import { createGateway, health } from "@homegrower-club/stoma";
createGateway({
routes: [
health(),
health({
path: "/healthz",
upstreamProbes: ["https://api.example.com/health"],
includeUpstreamStatus: true,
probeTimeoutMs: 5000,
probeMethod: "HEAD",
}),
],
});
interface HealthConfig {
path?: string; // default: "/health"
upstreamProbes?: string[];
includeUpstreamStatus?: boolean; // default: false
probeTimeoutMs?: number; // default: 5000
probeMethod?: string; // default: "HEAD"
unhealthyStatusCode?: number; // default: 503
}

Probe status values:

  • healthy (all probes healthy)
  • degraded (mixed)
  • unhealthy (all failed, returns unhealthyStatusCode)