# Tracing

A trace records the path a request takes through your system as a tree of
**spans**, each measuring one unit of work. Effect builds this tree
automatically: when you wrap an effect with `Effect.withSpan`, any span created
*inside* that effect becomes a child, because parent/child relationships follow
the fiber's context. You annotate spans with attributes, and at the edge of the
app you provide an exporter layer to ship the whole tree to a tracing backend.

```ts
import { Context, Effect, Layer } from "effect"

export class Checkout extends Context.Service<Checkout, {
  processCheckout(orderId: string): Effect.Effect<void>
}>()("acme/Checkout") {
  static readonly layer = Layer.effect(
    Checkout,
    Effect.gen(function*() {
      return Checkout.of({
        // Effect.fn names the function and opens a span around each call.
        processCheckout: Effect.fn("Checkout.processCheckout")(function*(orderId) {
          yield* Effect.logInfo("starting checkout", { orderId })

          // A child span for the card charge, with attributes describing it.
          yield* Effect.sleep("50 millis").pipe(
            Effect.withSpan("checkout.charge-card"),
            Effect.annotateSpans({
              "checkout.order_id": orderId,
              "checkout.provider": "acme-pay"
            })
          )

          // A sibling child span for persistence.
          yield* Effect.sleep("20 millis").pipe(
            Effect.withSpan("checkout.persist-order")
          )

          yield* Effect.logInfo("checkout completed", { orderId })
        })
      })
    })
  )
}
```

Calling `processCheckout` produces a `Checkout.processCheckout` span with two
children, `checkout.charge-card` and `checkout.persist-order`. Each carries its
own duration, and the attributes you attach show up on the corresponding span in
your backend.

## Creating spans

`Effect.withSpan(name, options)` wraps an effect in a span that starts when the
effect begins and ends when it completes (including on failure or interruption).
The options mirror OpenTelemetry: `attributes`, `kind`
(`"server" | "client" | "internal" | "producer" | "consumer"`), `links`,
`root`, and more.

```ts
import { Effect } from "effect"

const handler = Effect.gen(function*() {
  yield* Effect.sleep("10 millis")
}).pipe(
  Effect.withSpan("handle-request", {
    kind: "server",
    attributes: { "http.method": "POST", "http.route": "/checkout" }
  })
)
```

`Effect.fn("name")(function*…)` is the idiomatic way to define an
effect-returning function: it both names the function for stack traces and opens
a span named after it for every call — so most of your spans come for free just
from writing functions this way.

### Annotating the active span

Use `Effect.annotateSpans` to attach attributes to every span created within an
effect, or `Effect.annotateCurrentSpan` to add an attribute to the innermost
span only. You can also reach the active span directly with `Effect.currentSpan`.

```ts
import { Effect } from "effect"

const work = Effect.gen(function*() {
  // Add an attribute to the currently active span.
  yield* Effect.annotateCurrentSpan("cache.hit", false)

  // Access the span object itself when you need its trace/span ids.
  const span = yield* Effect.currentSpan
  yield* Effect.logInfo("inside span", { spanId: span.spanId, traceId: span.traceId })
}).pipe(Effect.withSpan("work"))
```

### Scoped spans and layer spans

`Effect.withSpanScoped` ties a span's lifetime to a `Scope` instead of a single
effect — handy when a span should stay open across several steps of a resource's
lifecycle. `Layer.withSpan` wraps the construction of a layer in a span, which
is useful for tracing application startup.

```ts
import { Effect, Layer } from "effect"

// Trace the work done while a layer is being built.
const Setup = Layer.effectDiscard(
  Effect.gen(function*() {
    yield* Effect.logInfo("running migrations")
    yield* Effect.sleep("30 millis")
  }).pipe(Effect.withSpan("startup.migrate"))
).pipe(Layer.withSpan("startup"))
```

## Exporting traces

By itself, Effect builds the span tree but does not send it anywhere. You enable
export by providing a tracer layer at the edge of the application. The
lightest-weight option is `OtlpTracer` from `effect/unstable/observability`,
which POSTs spans to any OpenTelemetry collector:

```ts
import { Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import { OtlpSerialization, OtlpTracer } from "effect/unstable/observability"

const Tracing = OtlpTracer.layer({
  url: "http://localhost:4318/v1/traces",
  resource: { serviceName: "checkout-api", serviceVersion: "1.0.0" }
}).pipe(
  // OtlpTracer needs a serializer and an HttpClient to do the POSTing.
  Layer.provide(OtlpSerialization.layerJson),
  Layer.provide(FetchHttpClient.layer)
)
```

Provide `Tracing` last, after your application layers, so every span the app
creates is captured before being exported. For the full configuration surface
(batching, headers, `resource` attributes), `OtlpLogger`, `PrometheusMetrics`,
and the `@effect/opentelemetry` integration for existing OpenTelemetry SDKs, see
the [Exporters](https://effect.plants.sh/observability/exporters/) page.
**Tip:** Add `Logger.tracerLogger` to your logger set (see [Logging](https://effect.plants.sh/observability/logging/))
to record log lines as events on the active span. Combined with an OTLP log
exporter, your logs and traces share the same trace and span ids in the backend.

## Effect span reference

Everything above is built on a handful of combinators on the `Effect` module.
This section enumerates them; the data types they accept (`SpanOptions`,
`SpanKind`, `SpanLink`, …) are documented in the [Tracer module
reference](#tracer-module-reference) below.

### Effect.withSpan

Wraps an effect in a child span that opens when the effect starts and ends when
it completes — on success, failure, or interruption. The second argument is a
`SpanOptions` object (see the fields below); it can also be a function of the
function arguments when used as the trailing combinator of `Effect.fn`.

```ts
import { Effect } from "effect"

const traced = Effect.succeed(42).pipe(
  Effect.withSpan("compute", { attributes: { tier: "free" } })
)
// => span "compute" with attribute tier=free wraps the success of 42
```

`SpanOptions` (= `SpanOptionsNoTrace` + `TraceOptions`) accepts the following
fields, all optional:

- `attributes?: Record<string, unknown>` — key/value tags recorded on the span.
- `links?: ReadonlyArray<SpanLink>` — links to other (possibly external) spans.
- `parent?: AnySpan` — force a specific parent instead of the contextual one.
- `root?: boolean` — when `true`, start a brand-new trace (ignore any parent).
- `annotations?: Context.Context<never>` — context-level annotations to attach.
- `kind?: SpanKind` — `"internal"` (default) | `"server"` | `"client"` |
  `"producer"` | `"consumer"`.
- `sampled?: boolean` — explicit sampling decision; bypasses trace-level gating.
- `level?: LogLevel` — the trace level used by `MinimumTraceLevel` gating.
- `captureStackTrace?: boolean | (() => string | undefined)` — capture (or
  supply) a stack trace for the span; set `false` to skip the cost.

```ts
import { Effect, Tracer } from "effect"

const upstream = Tracer.externalSpan({ spanId: "s1", traceId: "t1" })

const child = Effect.succeed("ok").pipe(
  Effect.withSpan("handle", {
    kind: "server",
    parent: upstream,
    links: [{ span: upstream, attributes: { relationship: "follows" } }],
    attributes: { "http.route": "/checkout" }
  })
)
// => span "handle" parented to the external trace t1, kind=server
```

### Effect.withSpanScoped

Like `withSpan`, but the span ends when the surrounding `Scope` is finalized
rather than when the effect completes. Use it when a span must stay open across
several steps of a resource's lifecycle.

```ts
import { Effect } from "effect"

const program = Effect.scoped(
  Effect.gen(function*() {
    yield* Effect.withSpanScoped(Effect.logInfo("step 1"), "session")
    yield* Effect.logInfo("step 2") // still inside the "session" span
  })
)
// => "session" span covers both steps, closed when the scope ends
```

### Effect.makeSpan

Creates a span value and returns it **without** installing it as the current
parent and **without** ending it automatically — you control its lifecycle by
hand (or hand it to `withParentSpan`).

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const span = yield* Effect.makeSpan("manual-op")
  return span.name
})
// => "manual-op" (you are responsible for ending it)
```

### Effect.makeSpanScoped

Creates a standalone span and registers a finalizer that ends it when the
`Scope` closes. The span is not pushed onto the span stack, so no child spans
attach to it implicitly.

```ts
import { Effect } from "effect"

const program = Effect.scoped(
  Effect.gen(function*() {
    const span = yield* Effect.makeSpanScoped("resource-lifetime")
    yield* Effect.logInfo("using resource")
    return span.spanId
    // => span ends automatically here
  })
)
```

### Effect.useSpan

Runs a callback with a freshly created span and ends it when the callback
completes. The span is standalone (not added to the stack), which makes it the
low-level building block behind `withSpan`.

```ts
import { Effect } from "effect"

const program = Effect.useSpan("user-op", (span) =>
  Effect.gen(function*() {
    span.attribute("user.id", "123")
    return "done"
  })
)
// => "done", with span "user-op" carrying attribute user.id=123
```

### Effect.withParentSpan

Pushes a span (local or external) onto the current span stack so that spans
created inside the effect become its children. Pair it with `makeSpan` or
`Tracer.externalSpan` to graft work under a chosen parent.

```ts
import { Effect, Tracer } from "effect"

const parent = Tracer.externalSpan({ spanId: "s9", traceId: "t9" })

const program = Effect.succeed("child work").pipe(
  Effect.withSpan("child"),
  Effect.withParentSpan(parent)
)
// => "child" span is parented to the external trace t9
```

### Effect.annotateSpans

Adds an attribute (or a record of attributes) to **every** span created inside
the effect. Dual: data-first `(effect, key, value)` or data-last for `.pipe`.

```ts
import { Effect } from "effect"

const program = Effect.succeed(1).pipe(
  Effect.withSpan("a"),
  Effect.annotateSpans({ tenant: "acme", region: "us-east-1" })
)
// => both attributes appear on span "a" (and any nested spans)
```

### Effect.annotateCurrentSpan

Adds an attribute (or record) to the **innermost** active span only, leaving
outer spans untouched. Returns `Effect<void>`.

```ts
import { Effect } from "effect"

const work = Effect.gen(function*() {
  yield* Effect.annotateCurrentSpan("cache.hit", true)
}).pipe(Effect.withSpan("lookup"))
// => attribute cache.hit=true on span "lookup" only
```

### Effect.currentSpan

Yields the currently active local `Span`, or fails with `NoSuchElementError`
when there is no active span. Use it to read the span's `spanId` / `traceId`,
e.g. to correlate with external systems.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const span = yield* Effect.currentSpan
  return span.traceId
}).pipe(Effect.withSpan("op"))
// => the active trace id (fails with NoSuchElementError if no span is active)
```

### Effect.currentParentSpan

Yields the current parent span — which may be a local `Span` or an
`ExternalSpan` — or fails with `NoSuchElementError` when no parent is present.

```ts
import { Effect } from "effect"

const child = Effect.gen(function*() {
  const parent = yield* Effect.currentParentSpan
  return parent._tag // "Span" | "ExternalSpan"
}).pipe(Effect.withSpan("child"), Effect.withSpan("parent"))
// => "Span" (the enclosing "parent" span)
```

### Effect.spanAnnotations

Returns the span annotations currently carried in the effect context as a plain
record. These are applied to spans created within the context.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  return yield* Effect.spanAnnotations
}).pipe(Effect.annotateSpans({ userId: "123" }))
// => { userId: "123" }
```

### Effect.spanLinks

Returns the `SpanLink`s currently carried in the effect context. Span links
connect related spans without a parent/child relationship.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const links = yield* Effect.spanLinks
  return links.length
})
// => 0 (no links provided in this context)
```

### Effect.linkSpans

Adds a link to the provided span (or array of spans), with optional attributes,
on every span created inside the effect. Useful for fan-out/fan-in or
cross-trace relationships.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const here = yield* Effect.currentSpan
  return yield* Effect.succeed("work").pipe(
    Effect.withSpan("follow-up"),
    Effect.linkSpans(here, { relationship: "follows" })
  )
}).pipe(Effect.withSpan("origin"))
// => span "follow-up" carries a link to "origin"
```

### Tracer controls: withTracer, withTracerEnabled, withTracerTiming

`Effect.withTracer` swaps the active `Tracer` for a region of code.
`Effect.withTracerEnabled(false)` skips registering spans with the tracer (they
still form the local stack but are not exported). `Effect.withTracerTiming(false)`
drops timing information from spans.

```ts
import { Effect } from "effect"

const noisy = Effect.succeed(1).pipe(
  Effect.withSpan("health-check"),
  // Don't ship this span to the backend, and don't record timing.
  Effect.withTracerEnabled(false),
  Effect.withTracerTiming(false)
)
// => span exists locally but is not registered/timed
```

### Effect.tracer

An effect that yields the active `Tracer` from context — the backend responsible
for allocating spans. Most code never needs this; reach for it when building
tooling on top of the tracer.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const tracer = yield* Effect.tracer
  return typeof tracer.span
})
// => "function"
```

## Tracer module reference

The `Tracer` module (``) holds the low-level
tracing data model: the backend interface, span types, and the context
references that control propagation and sampling. Application code rarely
touches it directly — `Effect.withSpan` and friends are the everyday surface —
but it is what custom backends and external-trace integrations are built on.

### Tracer (interface) and Tracer.make

A `Tracer` is a backend with a single `span(options)` method that allocates a
`Span` from a name, parent, annotations, links, start time, kind, root flag, and
sampling decision. `Tracer.make` is the identity constructor that brands an
implementation object as a `Tracer`.

```ts
import { Tracer } from "effect"

const custom = Tracer.make({
  span: (options) => new Tracer.NativeSpan(options)
})
// => a Tracer that delegates to the built-in NativeSpan
```

### Tracer.Tracer (context reference)

The active tracer service, keyed by `Tracer.TracerKey` (`"effect/Tracer"`). Its
default value is the native tracer, which produces `NativeSpan` instances.
Provide a different value (e.g. via `Effect.withTracer` or an exporter layer) to
change where spans go.

```ts
import { Effect, Tracer } from "effect"

const program = Effect.gen(function*() {
  const tracer = yield* Effect.service(Tracer.Tracer)
  return typeof tracer.span
})
// => "function"

console.log(Tracer.TracerKey)
// => "effect/Tracer"
```

### Tracer.Span

A span produced by an Effect tracer. It carries `name`, `spanId`, `traceId`,
`parent`, `attributes`, `links`, `sampled`, `kind`, and lifecycle `status`, plus
methods `end`, `attribute`, `event`, and `addLinks`.

```ts
import { Effect } from "effect"

const program = Effect.gen(function*() {
  const span = yield* Effect.currentSpan
  span.attribute("step", "validate")
  return span._tag
}).pipe(Effect.withSpan("op"))
// => "Span"
```

### Tracer.ExternalSpan and Tracer.AnySpan

`ExternalSpan` represents a span imported from another tracing system: it
carries identity (`spanId`, `traceId`), `sampled`, and `annotations`, but has no
lifecycle methods. `AnySpan` is the union `Span | ExternalSpan` accepted wherever
a span can act as a parent or link.

```ts
import { Context } from "effect"
import type { Tracer } from "effect"

const external: Tracer.ExternalSpan = {
  _tag: "ExternalSpan",
  spanId: "span-abc",
  traceId: "trace-xyz",
  sampled: true,
  annotations: Context.empty()
}
// => use as parent/link, but you cannot call .end() on it
```

### Tracer.externalSpan

Constructs an `ExternalSpan` from `spanId` / `traceId`, defaulting `sampled` to
`true` and `annotations` to an empty context. This is how you adopt an upstream
trace context (e.g. from incoming HTTP headers) so Effect spans nest under it.

```ts
import { Effect, Tracer } from "effect"

const parent = Tracer.externalSpan({ spanId: "s1", traceId: "t1" })

const program = Effect.succeed("ok").pipe(
  Effect.withSpan("child", { parent })
)
// => "child" span belongs to the upstream trace t1
```

### Tracer.SpanStatus

A span's lifecycle state: `{ _tag: "Started", startTime }` or
`{ _tag: "Ended", startTime, endTime, exit }`. The `exit` records how the span's
work completed.

```ts
import { Exit } from "effect"
import type { Tracer } from "effect"

const ended: Tracer.SpanStatus = {
  _tag: "Ended",
  startTime: 1_000_000_000n,
  endTime: 1_500_000_000n,
  exit: Exit.succeed("result")
}
// => ended.endTime - ended.startTime === 500_000_000n
```

### Tracer.SpanKind

The OpenTelemetry role of a span: `"internal" | "server" | "client" |
"producer" | "consumer"`. Passed via the `kind` option of `withSpan`.

```ts
import { Effect } from "effect"
import type { Tracer } from "effect"

const kind: Tracer.SpanKind = "client"
const call = Effect.succeed(1).pipe(Effect.withSpan("api-call", { kind }))
// => span "api-call" with kind=client
```

### Tracer.SpanLink

A relationship from one span to another, with descriptive attributes. Supplied
through the `links` option or `Effect.linkSpans`.

```ts
import { Tracer } from "effect"
import type { Tracer as T } from "effect"

const link: T.SpanLink = {
  span: Tracer.externalSpan({ spanId: "s2", traceId: "t2" }),
  attributes: { "link.type": "follows-from" }
}
// => attach via Effect.withSpan("op", { links: [link] })
```

### Tracer.SpanOptions / SpanOptionsNoTrace / TraceOptions

`SpanOptions` is the full options bag accepted by `Effect.withSpan`. It extends
`SpanOptionsNoTrace` (attributes, links, parent, root, annotations, kind,
sampled, level) with `TraceOptions` (`captureStackTrace`). The split exists
because some APIs (`makeSpan`, `makeSpanScoped`, `useSpan`) take only the
no-trace subset.

```ts
import type { Tracer } from "effect"

const options: Tracer.SpanOptions = {
  attributes: { "user.id": "123" },
  kind: "internal",
  root: false,
  captureStackTrace: true
}
// => pass to Effect.withSpan("op", options)
```

### Tracer.ParentSpan and Tracer.ParentSpanKey

`ParentSpan` is the context service holding the `AnySpan` used as the parent of
newly created spans, keyed by `ParentSpanKey` (`"effect/Tracer/ParentSpan"`).
`Effect.withParentSpan` provides it; you can read it back to inspect the current
parent.

```ts
import { Effect, Tracer } from "effect"

const program = Effect.gen(function*() {
  const parent = yield* Effect.service(Tracer.ParentSpan)
  return parent.spanId
})

console.log(Tracer.ParentSpanKey)
// => "effect/Tracer/ParentSpan"
```

### Tracer.NativeSpan

The default in-memory `Span` implementation used by the native tracer. It
generates random span ids (inheriting the parent's trace id, or generating a new
one for a root), and stores attributes, events, and links in memory.

```ts
import { Option, Context } from "effect"
import { Tracer } from "effect"

const span = new Tracer.NativeSpan({
  name: "in-memory",
  parent: Option.none(),
  annotations: Context.empty(),
  links: [],
  startTime: 0n,
  kind: "internal",
  sampled: true
})
// => span.traceId is a fresh 32-char hex id, span.spanId a 16-char hex id
```

### Tracer.CurrentTraceLevel

A `Context.Reference<LogLevel>` (default `"Info"`) giving the trace level used
for a span when its options don't set `level`. Combined with
`MinimumTraceLevel`, it decides the default sampling of spans.

```ts
import { Effect, Tracer } from "effect"

const program = Effect.succeed(1).pipe(
  Effect.withSpan("debug-only"),
  Effect.provideService(Tracer.CurrentTraceLevel, "Debug")
)
// => the span is treated as Debug-level for gating purposes
```

### Tracer.MinimumTraceLevel

A `Context.Reference<LogLevel>` (default `"All"`) setting the threshold below
which spans are not sampled. Spans whose level is below this threshold have their
sampling forced to `false`. An explicit `sampled` option bypasses this.

```ts
import { Effect, Tracer } from "effect"

const program = Effect.succeed(1).pipe(
  Effect.withSpan("trace-detail", { level: "Trace" }),
  // Only Info-and-above spans are sampled; this Trace span is dropped.
  Effect.provideService(Tracer.MinimumTraceLevel, "Info")
)
// => "trace-detail" is not exported
```

### Tracer.DisablePropagation

A `Context.Reference<boolean>` (default `false`). When `true`, new spans become
non-propagating no-ops and disabled spans are skipped when deriving a parent —
useful for suppressing tracing in noisy regions.

```ts
import { Effect, Tracer } from "effect"

const program = Effect.gen(function*() {
  yield* Effect.logInfo("not traced")
}).pipe(
  Effect.withSpan("noop"),
  Effect.provideService(Tracer.DisablePropagation, true)
)
// => no span is propagated out of this region
```

## Related

- [Logging](https://effect.plants.sh/observability/logging/) — correlate logs with spans via `Logger.tracerLogger`.
- [Metrics](https://effect.plants.sh/observability/metrics/) — aggregate numeric telemetry.
- [Exporters](https://effect.plants.sh/observability/exporters/) — full OTLP / Prometheus / OpenTelemetry wiring.
- [Services & Layers](https://effect.plants.sh/services-and-layers/) — provide exporters as layers.