API · Observability

Production-readiness in one backend — Prometheus metrics (GET /metrics + a custom counter), Sentry error tracking (inert without a DSN), distributed tracing, and a @voltro/testing unit test (makeTestContext + mockStore, run with voltro test). Zero-infra boot.

The production-readiness slice in one backend: Prometheus metrics, Sentry error tracking, distributed tracing, and a @voltro/testing unit test — so a real service is observable AND covered from day one. Boots zero-infra (store: 'memory'). Template id: api-observability.

Scaffold

voltro create-project acme --api=api-observability

What ships

apps/acme/api/
├── app.config.ts                       # prometheusPlugin() + sentryPlugin()
├── package.json                        # + the plugins; devDeps: @voltro/testing, vitest
├── database/schema.ts                  # a tenant-scoped `notes` table
├── queries/notes.list.query.ts         # reactive list (+ .server.ts)
├── mutations/notes.create.mutation.ts  # create + a custom metric (+ .server.ts)
└── tests/notes.create.test.ts          # makeTestContext + mockStore

Metrics — GET /metrics + your own counters

// app.config.ts
import { prometheusPlugin } from '@voltro/plugin-prometheus'
import { sentryPlugin } from '@voltro/plugin-sentry'

plugins: [ prometheusPlugin(), sentryPlugin() ]

The framework already records voltro_rpc_* / voltro_http_* / subscription / cache metrics into Effect's global MetricRegistry; prometheusPlugin() exports them at /metrics. A custom counter shows up there automatically — declare it at module level, bump it in a handler:

// mutations/notes.create.mutation.server.ts
import { Effect, Metric } from 'effect'
import { counter } from '@voltro/runtime'

const notesCreated = counter('app_notes_created_total', 'Notes created.')
Effect.runSync(Metric.increment(notesCreated))   // → /metrics + the DevTools Metrics panel
curl -s http://localhost:4000/metrics | grep -E 'app_notes_created_total|voltro_rpc_requests_total'
# app_notes_created_total 1
# voltro_rpc_requests_total{status="ok",tag="mutation.notes.create"} 1

Point Prometheus / Grafana at /metrics; gate a public deploy with prometheusPlugin({ token }) / PROMETHEUS_TOKEN.

Errors — Sentry, inert until you set a DSN

sentryPlugin() reports rpc / REST / workflow / schedule / subscriber / render errors, correlated to the active traceId — and is inert without SENTRY_DSN, so it ships wired. Set the env to turn it on; no code change. The plugin subscribes to the framework's server-error bus once, so every primitive is covered. For the full browser→backend waterfall, add the sentry field to a paired web app's app.config.ts.

Tracing — on by default

voltro dev runs a buffer-only tracer that powers the DevTools Traces panel and stamps fields.traceId on every log line (voltro logs --trace <id>). Set OTEL_EXPORTER_OTLP_ENDPOINT to ship spans to Jaeger / Tempo / Honeycomb — no code change.

Testing — makeTestContext, no DB, no server

// tests/notes.create.test.ts
import { makeTestContext, mockStore } from '@voltro/testing'
import createNote from '../mutations/notes.create.mutation.server'

const ctx = makeTestContext({
  subject: { type: 'user', id: 'user_1', tenantId: 'acme' },
  store:   mockStore({ notes: [] }),
})
const row = await createNote({ title: 'Hello', body: 'World' }, ctx)
expect(row.tenantId).toBe('acme')   // tenant() stamped it — the REAL store, in-memory

ctx.store is the same mixin-wrapped store production uses, so tenant auto-scoping, soft-delete filtering, and audit auto-fill behave identically — in milliseconds, no docker. ctx.withTenant('t2', (c) => c.store.query(…)) re-scopes the same data to prove cross-tenant isolation. Run it:

voltro test

The package also ships MockClock / MockEmail / mockAi / makeWorkflowRunner and runDialectParity (the @voltro/testing/dialect subpath).

Going to production

Want… Do
Metrics on a public deploy prometheusPlugin({ token }) + scrape over TLS
Errors in Sentry set SENTRY_DSN; add sentryPlugin({ traces: true }) for performance traces
Spans in your tracer OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318
Vendor-native (Datadog) add @voltro/plugin-datadog — agentless metrics + correlated logs/traces
A CI gate run voltro test; add runDialectParity for hand-written SQL

Anti-patterns

  • Re-creating the counter per call. Declare counter(...) at MODULE level — a per-call counter() muddies the series.
  • Wiring Sentry per primitive. The plugin covers them all via the server-error bus — you just set the DSN.
  • A real DB in unit tests. makeTestContext + mockStore give real store behaviour in-memory; save a live database for voltro e2e.

See also