Observability#

@sigx/actors-otel turns the host's own metrics into something your monitoring stack already speaks — without making OpenTelemetry a dependency of anything that does not want it.

Terminal
pnpm add @sigx/actors-otel

Prometheus#

The /prometheus entry imports no OpenTelemetry at all:

TypeScript
import { prometheusOps } from '@sigx/actors-otel/prometheus';

app.use(metrics()).use(prometheusOps({ secret: process.env.SIGX_OPS_SECRET }));

Mounts GET /_sigx/metrics beside ops(), with the same bearer posture — the secret is mandatory outside dev.

YAML
scrape_configs:
  - job_name: sigx-actors
    metrics_path: /_sigx/metrics
    authorization: { credentials_file: /etc/prometheus/sigx-ops-secret }
    static_configs: [{ targets: ['actors-host:7311'] }]

Options: path (default /_sigx/metrics), secret, prefix (default sigx_actors_), and bucketsSeconds — a per-octave grid from 1µs to ~134s, about 28 bounds.

renderPrometheus(digest, stats, options) is the pure function underneath, if you would rather serve it yourself.

The cardinality rule: labels are type and method — never actor keys. An actor key is unbounded by construction, and a key-labelled metric will take your Prometheus down. This is enforced, not advisory.

metrics().reset() breaks monotonicity. Prometheus counters are expected only ever to increase; a reset() looks like a counter restart. That is survivable — rate() handles restarts — but do not wire reset() to anything periodic.

OpenTelemetry traces#

TypeScript
import { otelTraces } from '@sigx/actors-otel';

app.use(otelTraces({ turnSpans: true }));

One CLIENT span per dispatch and one SERVER span per turn, joined across hosts by the W3C traceparent the runtime propagates: captured from the public endpoint header and carried on the cluster envelope.

Span attributes use a key hash, not the key, for the same reason the routing token does — and with the same caveat that a hash is log hygiene, not privacy.

@opentelemetry/api is an optional peer dependency. With no tracer provider registered the plugin is inert and costs nothing.

The metrics bridge#

TypeScript
import { otelMetricsBridge } from '@sigx/actors-otel';

app.use(otelMetricsBridge({ percentileGauges: true }));

Feeds the host's counters and histograms into whatever OTel meter provider you have configured, for stacks that collect metrics over OTLP rather than by scraping.

Choosing#

You haveUse
Prometheus / VictoriaMetrics / Grafana AgentprometheusOps() — no OTel dependency
An OTLP collectorotelMetricsBridge() + otelTraces()
Neither, and a terminalthe CLI dashboard
A custom stackread ops() directly

Core plumbing this rides on: ActorCallContext.traceparent, the trailing call parameter on ActorTurnObserver, and bucketUpperBoundsUs() / timingSafeEquals from @sigx/actors/host.

Next steps#