Effect
Effect v4 ships official OpenTelemetry support via @effect/opentelemetry. Autotel
registers the global TracerProvider and handles export. autotel-effect
connects the two so you do not copy the same layer wiring into every app.
Installation
Section titled “Installation”npm install autotel autotel-effect effect @effect/opentelemetryEffect v4 only (^4.0.0-rc.112).
Initialize autotel first
Section titled “Initialize autotel first”import { init } from 'autotel';
init({ service: 'my-api', endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,});tsx --import ./instrumentation.ts src/index.tsProvide the Effect layer
Section titled “Provide the Effect layer”import * as Effect from 'effect/Effect';import { layer } from 'autotel-effect';
const AutotelEffect = layer({ serviceName: 'my-api' });
await Effect.runPromise( Effect.withSpan('my.operation')(Effect.log('hello')).pipe( Effect.provide(AutotelEffect), ),);HTTP spans from autotel’s node:http instrumentation become parents of domain
spans from Effect.withSpan.
Logs come with it
Section titled “Logs come with it”That one layer() call bridges logs as well as spans. Effect.log* becomes an
OpenTelemetry log record — reaching any OTLP log backend, autotel-devtools
included — plus a trace-correlated structured line on stdout.
A log written inside Effect.withSpan carries that span’s trace and span ids,
so logs and traces line up in the same backend. Effect.annotateLogs values
become log-record attributes, and an Error or a Cause passed to
Effect.logError is reported as an err attribute with its stack:
Effect.log('charged').pipe(Effect.annotateLogs({ 'order.id': 'o-1' }));// {"level":"info","msg":"charged","order.id":"o-1","traceId":"...","spanId":"..."}Rich annotation values are flattened to the attribute shape the rest of autotel
produces: a nested object becomes dot-notation keys, a Date becomes an ISO
string, an Error becomes its message.
Effect.annotateLogs({ order: { id: 7 }, at: new Date(0) });// attributes: { 'order.id': 7, at: '1970-01-01T00:00:00.000Z' }Pass logs: false for spans only, or an options object to configure the
logger:
layer({ serviceName: 'my-api', logs: { level: 'debug', pretty: true } });layer({ serviceName: 'my-api', logs: false });mergeWithExisting: true keeps Effect’s console logger alongside this one,
level sets the stdout minimum level (default 'info', so pass 'debug' for
Effect.logDebug to reach stdout), pretty: true swaps JSON for
human-readable stdout, and console: false emits only the log record — use it
when captureConsole() is on. loggerLayer() is exported for the case where
something else owns the tracer.
Unit tests
Section titled “Unit tests”Effect’s default Tracer is an in-memory native tracer, so handler tests need no
layer at all. Provide layer(...) with autotel’s createMemoryExporter() only
where you assert on exported spans.