Effect layers, Prometheus exposition, and OpenTelemetry export for a Smithers control plane.
Observability ships as a set of Effect layers. One call
to createSmithersObservabilityLayer installs the logger, the metrics registry,
the tracing service, and (when enabled) an OTLP exporter. Metrics are always
collected in-process and read back as a Prometheus exposition string; traces and
logs are exported over OTLP only when enabled is set.
Metric collection is global. renderPrometheusMetrics() and trackSmithersEvent
read and write the process-wide Effect registry, so they work whether or not you
have built a layer. Building a layer adds logging, tracing, and OTLP export on
top.
An Effect Context.Tag for the resolved observability service. Provided by
createSmithersObservabilityLayer; depend on it to read the resolved options or
to open a span without importing withSmithersSpan directly.
import { Effect } from "effect";import { SmithersObservability } from "smithers-orchestrator";const program = Effect.gen(function* () { const obs = yield* SmithersObservability; yield* obs.withSpan("my-step", Effect.log("inside the span"));});
Builds the full observability layer: logger, MetricsService, TracingService,
the SmithersObservability service, and BunContext. The OTLP exporter is
folded in only when the resolved options have enabled: true.
Alias of createSmithersObservabilityLayer, exported under the name used by the
runtime. Same signature, same return type. Prefer it in runtime/server wiring so
the intent reads clearly.
The OTLP-export slice on its own. Returns Layer.empty unless enabled resolves
to true, so it is safe to merge unconditionally. Use it when you already have a
metrics/logging layer and only want to add OpenTelemetry traces.
function createSmithersOtelLayer( options?: SmithersObservabilityOptions,): Layer.Layer<never>;
Merge partial options with environment variables and defaults. The layer
factories call this for you; call it directly to inspect what a deployment
resolves to.
function resolveSmithersObservabilityOptions( options?: SmithersObservabilityOptions,): ResolvedSmithersObservabilityOptions;
Minimum log level. Accepts an Effect LogLevel or a string
(none/trace/debug/info/warning/error/fatal/all). Defaults
from SMITHERS_LOG_LEVEL, otherwise Info.
Map a SmithersEvent onto the metric registry. Returns
an Effect<void> that increments the relevant counters, updates gauges, and
records durations for that event type. Every event also bumps the
smithers_events_emitted_total counter. The runtime calls this on each emitted
event; call it yourself only when feeding events from outside the engine.
function trackSmithersEvent(event: SmithersEvent): Effect.Effect<void>;
Render the entire metric registry as a Prometheus exposition string, with
# HELP/# TYPE headers and every catalog metric present at zero even before it
fires. Refreshes the process gauges (uptime, RSS, heap) on each call. This is
what the server’s GET /metrics route returns.
The exact Content-Type for a Prometheus response:
text/plain; version=0.0.4; charset=utf-8. Pair it with the rendered body.
import { renderPrometheusMetrics, prometheusContentType } from "smithers-orchestrator";// Inside any HTTP handler:function handleMetrics() { return new Response(renderPrometheusMetrics(), { headers: { "Content-Type": prometheusContentType }, });}
The built-in server already exposes this at GET /metrics. See
Server integration. Use these directly only when wiring
metrics into your own HTTP layer.
smithersMetrics is the registry of every metric, an object keyed by the
camelCase names below whose values are the underlying Effect Metrics. Each
metric renders under its Prometheus name; histograms also emit _bucket,
_sum, and _count series. The table groups the core metrics by area; the
registry holds more (gateway, devtools, agent, alert, supervisor families).
import { smithersMetrics } from "smithers-orchestrator";const everyMetric = Object.values(smithersMetrics); // all Effect Metrics