Skip to content

Observability

Production systems need to answer three questions: what happened, how long did it take, and how is the system behaving over time. Effect answers all three out of the box, with no third-party instrumentation library required:

  • Logging — structured, level-filtered log records carrying contextual annotations and timing spans.
  • Tracing — spans that nest automatically along your effect’s call graph, ready to export to any OpenTelemetry backend.
  • Metrics — counters, gauges, histograms, and frequencies that aggregate in a process-wide registry.

Because all three are part of the Effect runtime, they share the same machinery. A logger can emit log records as span events, a span’s timing feeds your traces, and metrics see the same fiber context as everything else. You instrument your code once and choose where the data goes by providing a Layer at the edge of your application.

import { Effect, Layer, Logger, Metric } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { OtlpLogger, OtlpSerialization, OtlpTracer } from "effect/unstable/observability"
// A counter, a span, and a structured log line — all from the same effect.
const requests = Metric.counter("http_requests_total")
const handleRequest = Effect.fn("handleRequest")(
function*(route: string) {
yield* Metric.update(requests, 1)
yield* Effect.logInfo("handling request", { route })
},
// Pass cross-cutting combinators as trailing args to `Effect.fn` rather
// than chaining them with `.pipe`.
Effect.withSpan("handle-request")
)
// Choose destinations by providing layers at the edge of the app.
const Observability = Layer.mergeAll(
Logger.layer([Logger.consoleJson]),
OtlpTracer.layer({ url: "http://localhost:4318/v1/traces" }),
OtlpLogger.layer({ url: "http://localhost:4318/v1/logs" })
).pipe(
Layer.provide(OtlpSerialization.layerJson),
Layer.provide(FetchHttpClient.layer)
)

The same handleRequest effect produces a metric, a trace span, and a log line. Swapping Observability for a different layer (or omitting it) changes where that telemetry goes without touching the business logic.

  • Logging — emit structured log records, filter by level, annotate with context, and plug in custom or batched loggers.
  • Tracing — create spans that nest along your call graph, annotate them, and export traces over OTLP.
  • Metrics — track counters, gauges, histograms, and frequencies with the built-in Metric registry.

For new projects, the lightweight modules under effect/unstable/observability (OtlpTracer, OtlpLogger, OtlpMetrics, PrometheusMetrics) speak OTLP and Prometheus directly with no extra runtime dependencies. If you already run an OpenTelemetry SDK, the @effect/opentelemetry integration wires Effect’s tracer, logger, and metrics into your existing pipeline instead.