Skip to content

Tracing in Effect

Although logs and metrics are useful to understand the behavior of individual services, they are not enough to provide a complete overview of the lifetime of a request in a distributed system.

In a distributed system, a request can span multiple services and each service can make multiple requests to other services to fulfill the request. In such a scenario, we need to have a way to track the lifetime of a request across multiple services to diagnose what services are the bottlenecks and where the request is spending most of its time.

A span represents a single unit of work or operation within a request. It provides a detailed view of what happened during the execution of that specific operation.

Each span typically contains the following information:

Span ComponentDescription
NameDescribes the specific operation being tracked.
Timing DataTimestamps indicating when the operation started and its duration.
Log MessagesStructured logs capturing important events during the operation.
AttributesMetadata providing additional context about the operation.

Spans are key building blocks in tracing, helping you visualize and understand the flow of requests through various services.

A trace records the paths taken by requests (made by an application or end-user) as they propagate through multi-service architectures, like microservice and serverless applications.

Without tracing, it is challenging to pinpoint the cause of performance problems in a distributed system.

A trace is made of one or more spans. The first span represents the root span. Each root span represents a request from start to finish. The spans underneath the parent provide a more in-depth context of what occurs during a request (or what steps make up a request).

Many Observability back-ends visualize traces as waterfall diagrams that may look something like this:

Trace Waterfall Diagram

Waterfall diagrams show the parent-child relationship between a root span and its child spans. When a span encapsulates another span, this also represents a nested relationship.

You can add tracing to an effect by creating a span using the Effect.withSpan API. This helps you track specific operations within the effect.

Example (Adding a Span to an Effect)

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
// Define an effect that delays for 100 milliseconds
const
const program: Effect.Effect<void, never, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const void: Effect.Effect<void, never, never>
export void

@since2.0.0

void
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
("100 millis"))
// Instrument the effect with a span for tracing
const
const instrumented: Effect.Effect<void, never, never>
instrumented
=
const program: Effect.Effect<void, never, never>
program
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("myspan"))

Instrumenting an effect with a span does not change its type. If you start with an Effect<A, E, R>, the result remains an Effect<A, E, R>.

To print spans for debugging or analysis, you’ll need to install the required tracing tools. Here’s how to set them up for your project.

Choose your package manager and install the necessary libraries:

Terminal window
npm install @effect/opentelemetry
npm install @opentelemetry/sdk-trace-base
# For NodeJS applications
npm install @opentelemetry/sdk-trace-node
# For browser applications
npm install @opentelemetry/sdk-trace-web
# If you also need to export metrics
npm install @opentelemetry/sdk-metrics

Once the dependencies are installed, you can set up span printing using OpenTelemetry. Here’s an example showing how to print a span for an effect.

Example (Setting Up and Printing a Span)

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
import {
import NodeSdk
NodeSdk
} from "@effect/opentelemetry"
import {
class ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
,
class BatchSpanProcessor
BatchSpanProcessor
} from "@opentelemetry/sdk-trace-base"
// Define an effect that delays for 100 milliseconds
const
const program: Effect.Effect<void, never, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const void: Effect.Effect<void, never, never>
export void

@since2.0.0

void
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
("100 millis"))
// Instrument the effect with a span for tracing
const
const instrumented: Effect.Effect<void, never, never>
instrumented
=
const program: Effect.Effect<void, never, never>
program
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("myspan"))
// Set up tracing with the OpenTelemetry SDK
const
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
=
import NodeSdk
NodeSdk
.
const layer: (evaluate: LazyArg<NodeSdk.Configuration>) => Layer<Resource> (+1 overload)

@since1.0.0

layer
(() => ({
Configuration.resource?: {
readonly serviceName: string;
readonly serviceVersion?: string;
readonly attributes?: ResourceAttributes;
} | undefined
resource
: {
serviceName: string
serviceName
: "example" },
// Export span data to the console
Configuration.spanProcessor?: SpanProcessor | readonly SpanProcessor[] | undefined
spanProcessor
: new
new BatchSpanProcessor<BufferConfig>(_exporter: SpanExporter, config?: BufferConfig | undefined): BatchSpanProcessor
BatchSpanProcessor
(new
new ConsoleSpanExporter(): ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
())
}))
// Run the effect, providing the tracing layer
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <void, never>(effect: Effect.Effect<void, never, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<void>

Executes an effect and returns the result as a Promise.

When to Use

Use runPromise when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@example

// Title: Running a Successful Effect as a Promise
import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

@example

//Example: Handling a Failing Effect as a Rejected Promise import { Effect } from "effect"

Effect.runPromise(Effect.fail("my error")).catch(console.error) // Output: // (FiberFailure) Error: my error

@since2.0.0

runPromise
(
const instrumented: Effect.Effect<void, never, never>
instrumented
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <Resource, never, never>(layer: Layer<Resource, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Resource>> (+9 overloads)

Provides the necessary Layers to an effect, removing its dependency on the environment.

You can pass multiple layers, a Context, Runtime, or ManagedRuntime to the effect.

@seeprovideService for providing a service to an effect.

@example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@since2.0.0

provide
(
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
)))
/*
Example Output:
{
resource: {
attributes: {
'service.name': 'example',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': '@effect/opentelemetry',
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined },
traceId: '673c06608bd815f7a75bf897ef87e186',
parentId: undefined,
traceState: undefined,
name: 'myspan',
id: '401b2846170cd17b',
kind: 0,
timestamp: 1733220735529855.5,
duration: 102079.958,
attributes: {},
status: { code: 1 },
events: [],
links: []
}
*/

The output provides detailed information about the span:

FieldDescription
traceIdA unique identifier for the entire trace, helping trace requests or operations as they move through an application.
parentIdIdentifies the parent span of the current span, marked as undefined in the output when there is no parent span, making it a root span.
nameDescribes the name of the span, indicating the operation being tracked (e.g., “myspan”).
idA unique identifier for the current span, distinguishing it from other spans within a trace.
timestampA timestamp representing when the span started, measured in microseconds since the Unix epoch.
durationSpecifies the duration of the span, representing the time taken to complete the operation (e.g., 2895.769 microseconds).
attributesSpans may contain attributes, which are key-value pairs providing additional context or information about the operation. In this output, it’s an empty object, indicating no specific attributes in this span.
statusThe status field provides information about the span’s status. In this case, it has a code of 1, which typically indicates an OK status (whereas a code of 2 signifies an ERROR status)
eventsSpans can include events, which are records of specific moments during the span’s lifecycle. In this output, it’s an empty array, suggesting no specific events recorded.
linksLinks can be used to associate this span with other spans in different traces. In the output, it’s an empty array, indicating no specific links for this span.

Here’s how a span looks when the effect encounters an error:

Example (Span for an Effect that Fails)

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
import {
import NodeSdk
NodeSdk
} from "@effect/opentelemetry"
import {
class ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
,
class BatchSpanProcessor
BatchSpanProcessor
} from "@opentelemetry/sdk-trace-base"
const
const program: Effect.Effect<never, string, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const fail: <string>(error: string) => Effect.Effect<never, string, never>

Creates an Effect that represents a recoverable error.

When to Use

Use this function to explicitly signal an error in an Effect. The error will keep propagating unless it is handled. You can handle the error with functions like

catchAll

or

catchTag

.

@seesucceed to create an effect that represents a successful value.

@example

// Title: Creating a Failed Effect
import { Effect } from "effect"
// ┌─── Effect<never, Error, never>
// ▼
const failure = Effect.fail(
new Error("Operation failed due to network error")
)

@since2.0.0

fail
("Oh no!").
Pipeable.pipe<Effect.Effect<never, string, never>, Effect.Effect<never, string, never>, Effect.Effect<never, string, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<never, string, never>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
("100 millis"),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("myspan")
)
const
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
=
import NodeSdk
NodeSdk
.
const layer: (evaluate: LazyArg<NodeSdk.Configuration>) => Layer<Resource> (+1 overload)

@since1.0.0

layer
(() => ({
Configuration.resource?: {
readonly serviceName: string;
readonly serviceVersion?: string;
readonly attributes?: ResourceAttributes;
} | undefined
resource
: {
serviceName: string
serviceName
: "example" },
Configuration.spanProcessor?: SpanProcessor | readonly SpanProcessor[] | undefined
spanProcessor
: new
new BatchSpanProcessor<BufferConfig>(_exporter: SpanExporter, config?: BufferConfig | undefined): BatchSpanProcessor
BatchSpanProcessor
(new
new ConsoleSpanExporter(): ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
())
}))
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromiseExit: <never, string>(effect: Effect.Effect<never, string, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<Exit<never, string>>

Runs an effect and returns a Promise that resolves to an Exit, which represents the outcome (success or failure) of the effect.

When to Use

Use runPromiseExit when you need to determine if an effect succeeded or failed, including any defects, and you want to work with a Promise.

Details

The Exit type represents the result of the effect:

  • If the effect succeeds, the result is wrapped in a Success.
  • If it fails, the failure information is provided as a Failure containing a Cause type.

@example

// Title: Handling Results as Exit
import { Effect } from "effect"
// Execute a successful effect and get the Exit result as a Promise
Effect.runPromiseExit(Effect.succeed(1)).then(console.log)
// Output:
// {
// _id: "Exit",
// _tag: "Success",
// value: 1
// }
// Execute a failing effect and get the Exit result as a Promise
Effect.runPromiseExit(Effect.fail("my error")).then(console.log)
// Output:
// {
// _id: "Exit",
// _tag: "Failure",
// cause: {
// _id: "Cause",
// _tag: "Fail",
// failure: "my error"
// }
// }

@since2.0.0

runPromiseExit
(
const program: Effect.Effect<never, string, never>
program
.
Pipeable.pipe<Effect.Effect<never, string, never>, Effect.Effect<never, string, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<never, string, never>) => Effect.Effect<never, string, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <Resource, never, never>(layer: Layer<Resource, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Resource>> (+9 overloads)

Provides the necessary Layers to an effect, removing its dependency on the environment.

You can pass multiple layers, a Context, Runtime, or ManagedRuntime to the effect.

@seeprovideService for providing a service to an effect.

@example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@since2.0.0

provide
(
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
))).
Promise<Exit<never, string>>.then<void, never>(onfulfilled?: ((value: Exit<never, string>) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.

@paramonrejected The callback to execute when the Promise is rejected.

@returnsA Promise for the completion of which ever callback is executed.

then
(
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@seesource

console
.
Console.log(message?: any, ...optionalParams: any[]): void

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@sincev0.1.100

log
)
/*
Example Output:
{
resource: {
attributes: {
'service.name': 'example',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': '@effect/opentelemetry',
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined },
traceId: 'eee9619866179f209b7aae277283e71f',
parentId: undefined,
traceState: undefined,
name: 'myspan',
id: '3a5725c91884c9e1',
kind: 0,
timestamp: 1733220830575626,
duration: 106578.042,
attributes: {
'code.stacktrace': 'at <anonymous> (/Users/giuliocanti/Documents/GitHub/website/content/dev/index.ts:10:10)'
},
status: { code: 2, message: 'Oh no!' },
events: [
{
name: 'exception',
attributes: {
'exception.type': 'Error',
'exception.message': 'Oh no!',
'exception.stacktrace': 'Error: Oh no!'
},
time: [ 1733220830, 682204083 ],
droppedAttributesCount: 0
}
],
links: []
}
{
_id: 'Exit',
_tag: 'Failure',
cause: { _id: 'Cause', _tag: 'Fail', failure: 'Oh no!' }
}
*/

In this example, the span’s status code is 2, indicating an error. The message in the status provides more details about the failure.

You can provide extra information to a span by utilizing the Effect.annotateCurrentSpan function. This function allows you to attach key-value pairs, offering more context about the execution of the span.

Example (Annotating a Span)

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
import {
import NodeSdk
NodeSdk
} from "@effect/opentelemetry"
import {
class ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
,
class BatchSpanProcessor
BatchSpanProcessor
} from "@opentelemetry/sdk-trace-base"
const
const program: Effect.Effect<void, never, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const void: Effect.Effect<void, never, never>
export void

@since2.0.0

void
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<...>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>, cd: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
("100 millis"),
// Annotate the span with a key-value pair
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const tap: <void, Effect.Effect<void, never, never>>(f: (a: void) => Effect.Effect<void, never, never>) => <E, R>(self: Effect.Effect<void, E, R>) => Effect.Effect<...> (+7 overloads)

Runs a side effect with the result of an effect without changing the original value.

When to Use

Use tap when you want to perform a side effect, like logging or tracking, without modifying the main value. This is useful when you need to observe or record an action but want the original value to be passed to the next step.

Details

tap works similarly to flatMap, but it ignores the result of the function passed to it. The value from the previous effect remains available for the next part of the chain. Note that if the side effect fails, the entire chain will fail too.

@example

// Title: Logging a step in a pipeline
import { Console, Effect, pipe } from "effect"
// Function to apply a discount safely to a transaction amount
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
// Simulated asynchronous task to fetch a transaction amount from database
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const finalAmount = pipe(
fetchTransactionAmount,
// Log the fetched transaction amount
Effect.tap((amount) => Console.log(`Apply a discount to: ${amount}`)),
// `amount` is still available!
Effect.flatMap((amount) => applyDiscount(amount, 5))
)
Effect.runPromise(finalAmount).then(console.log)
// Output:
// Apply a discount to: 100
// 95

@since2.0.0

tap
(() =>
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const annotateCurrentSpan: (key: string, value: unknown) => Effect.Effect<void> (+1 overload)

Adds an annotation to the current span if available

@since2.0.0

annotateCurrentSpan
("key", "value")),
// Wrap the effect in a span named 'myspan'
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("myspan")
)
// Set up tracing with the OpenTelemetry SDK
const
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
=
import NodeSdk
NodeSdk
.
const layer: (evaluate: LazyArg<NodeSdk.Configuration>) => Layer<Resource> (+1 overload)

@since1.0.0

layer
(() => ({
Configuration.resource?: {
readonly serviceName: string;
readonly serviceVersion?: string;
readonly attributes?: ResourceAttributes;
} | undefined
resource
: {
serviceName: string
serviceName
: "example" },
Configuration.spanProcessor?: SpanProcessor | readonly SpanProcessor[] | undefined
spanProcessor
: new
new BatchSpanProcessor<BufferConfig>(_exporter: SpanExporter, config?: BufferConfig | undefined): BatchSpanProcessor
BatchSpanProcessor
(new
new ConsoleSpanExporter(): ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
())
}))
// Run the effect, providing the tracing layer
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <void, never>(effect: Effect.Effect<void, never, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<void>

Executes an effect and returns the result as a Promise.

When to Use

Use runPromise when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@example

// Title: Running a Successful Effect as a Promise
import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

@example

//Example: Handling a Failing Effect as a Rejected Promise import { Effect } from "effect"

Effect.runPromise(Effect.fail("my error")).catch(console.error) // Output: // (FiberFailure) Error: my error

@since2.0.0

runPromise
(
const program: Effect.Effect<void, never, never>
program
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <Resource, never, never>(layer: Layer<Resource, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Resource>> (+9 overloads)

Provides the necessary Layers to an effect, removing its dependency on the environment.

You can pass multiple layers, a Context, Runtime, or ManagedRuntime to the effect.

@seeprovideService for providing a service to an effect.

@example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@since2.0.0

provide
(
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
)))
/*
Example Output:
{
resource: {
attributes: {
'service.name': 'example',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': '@effect/opentelemetry',
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined },
traceId: 'c8120e01c0f1ea83ccc1d388e5cdebd3',
parentId: undefined,
traceState: undefined,
name: 'myspan',
id: '81c430ba4979f1db',
kind: 0,
timestamp: 1733220874356084,
duration: 102821.417,
attributes: { key: 'value' },
status: { code: 1 },
events: [],
links: []
}
*/

In the context of tracing, logs are converted into “Span Events.” These events offer structured insights into your application’s activities and provide a timeline of when specific operations occurred.

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
import {
import NodeSdk
NodeSdk
} from "@effect/opentelemetry"
import {
class ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
,
class BatchSpanProcessor
BatchSpanProcessor
} from "@opentelemetry/sdk-trace-base"
// Define a program that logs a message and delays for 100 milliseconds
const
const program: Effect.Effect<void, never, never>
program
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const log: (...message: ReadonlyArray<any>) => Effect.Effect<void, never, never>

Logs one or more messages or error causes at the current log level, which is INFO by default. This function allows logging multiple items at once and can include detailed error information using Cause instances.

To adjust the log level, use the Logger.withMinimumLogLevel function.

@example

import { Cause, Effect } from "effect"
const program = Effect.log(
"message1",
"message2",
Cause.die("Oh no!"),
Cause.die("Oh uh!")
)
// Effect.runFork(program)
// Output:
// timestamp=... level=INFO fiber=#0 message=message1 message=message2 cause="Error: Oh no!
// Error: Oh uh!"

@since2.0.0

log
("Hello").
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
("100 millis"),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("myspan")
)
// Set up tracing with the OpenTelemetry SDK
const
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
=
import NodeSdk
NodeSdk
.
const layer: (evaluate: LazyArg<NodeSdk.Configuration>) => Layer<Resource> (+1 overload)

@since1.0.0

layer
(() => ({
Configuration.resource?: {
readonly serviceName: string;
readonly serviceVersion?: string;
readonly attributes?: ResourceAttributes;
} | undefined
resource
: {
serviceName: string
serviceName
: "example" },
Configuration.spanProcessor?: SpanProcessor | readonly SpanProcessor[] | undefined
spanProcessor
: new
new BatchSpanProcessor<BufferConfig>(_exporter: SpanExporter, config?: BufferConfig | undefined): BatchSpanProcessor
BatchSpanProcessor
(new
new ConsoleSpanExporter(): ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
())
}))
// Run the effect, providing the tracing layer
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <void, never>(effect: Effect.Effect<void, never, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<void>

Executes an effect and returns the result as a Promise.

When to Use

Use runPromise when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@example

// Title: Running a Successful Effect as a Promise
import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

@example

//Example: Handling a Failing Effect as a Rejected Promise import { Effect } from "effect"

Effect.runPromise(Effect.fail("my error")).catch(console.error) // Output: // (FiberFailure) Error: my error

@since2.0.0

runPromise
(
const program: Effect.Effect<void, never, never>
program
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <Resource, never, never>(layer: Layer<Resource, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Resource>> (+9 overloads)

Provides the necessary Layers to an effect, removing its dependency on the environment.

You can pass multiple layers, a Context, Runtime, or ManagedRuntime to the effect.

@seeprovideService for providing a service to an effect.

@example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@since2.0.0

provide
(
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
)))
/*
Example Output:
{
resource: {
attributes: {
'service.name': 'example',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': '@effect/opentelemetry',
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined },
traceId: 'b0f4f012b5b13c0a040f7002a1d7b020',
parentId: undefined,
traceState: undefined,
name: 'myspan',
id: 'b9ba8472002715a8',
kind: 0,
timestamp: 1733220905504162.2,
duration: 103790,
attributes: {},
status: { code: 1 },
events: [
{
name: 'Hello',
attributes: { 'effect.fiberId': '#0', 'effect.logLevel': 'INFO' }, // Log attributes
time: [ 1733220905, 607761042 ], // Event timestamp
droppedAttributesCount: 0
}
],
links: []
}
*/

Each span can include events, which capture specific moments during the execution of a span. In this example, a log message "Hello" is recorded as an event within the span. Key details of the event include:

FieldDescription
nameThe name of the event, which corresponds to the logged message (e.g., 'Hello').
attributesKey-value pairs that provide additional context about the event, such as fiberId and log level.
timeThe timestamp of when the event occurred, shown in a high-precision format.
droppedAttributesCountIndicates how many attributes were discarded, if any. In this case, no attributes were dropped.

Spans can be nested to represent a hierarchy of operations. This allows you to track how different parts of your application relate to one another during execution. The following example demonstrates how to create and manage nested spans.

Example (Nesting Spans in a Trace)

import {
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
} from "effect"
import {
import NodeSdk
NodeSdk
} from "@effect/opentelemetry"
import {
class ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
,
class BatchSpanProcessor
BatchSpanProcessor
} from "@opentelemetry/sdk-trace-base"
const
const child: Effect.Effect<void, never, never>
child
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const void: Effect.Effect<void, never, never>
export void

@since2.0.0

void
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const delay: (duration: DurationInput) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R> (+1 overload)

Returns an effect that is delayed from this effect by the specified Duration.

@since2.0.0

delay
("100 millis"),
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("child")
)
const
const parent: Effect.Effect<void, never, never>
parent
=
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const gen: <YieldWrap<Effect.Effect<void, never, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<void, never, never>>, void, never>) => Effect.Effect<...> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to Use

gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

@example

import { Effect } from "effect"
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, Error> =>
discountRate === 0
? Effect.fail(new Error("Discount rate cannot be zero"))
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function* () {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})

@since2.0.0

gen
(function* () {
yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const sleep: (duration: DurationInput) => Effect.Effect<void>

Returns an effect that suspends for the specified duration. This method is asynchronous, and does not actually block the fiber executing the effect.

@since2.0.0

sleep
("20 millis")
yield*
const child: Effect.Effect<void, never, never>
child
yield*
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const sleep: (duration: DurationInput) => Effect.Effect<void>

Returns an effect that suspends for the specified duration. This method is asynchronous, and does not actually block the fiber executing the effect.

@since2.0.0

sleep
("10 millis")
}).
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

Wraps the effect with a new span for tracing.

@since2.0.0

withSpan
("parent"))
// Set up tracing with the OpenTelemetry SDK
const
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
=
import NodeSdk
NodeSdk
.
const layer: (evaluate: LazyArg<NodeSdk.Configuration>) => Layer<Resource> (+1 overload)

@since1.0.0

layer
(() => ({
Configuration.resource?: {
readonly serviceName: string;
readonly serviceVersion?: string;
readonly attributes?: ResourceAttributes;
} | undefined
resource
: {
serviceName: string
serviceName
: "example" },
Configuration.spanProcessor?: SpanProcessor | readonly SpanProcessor[] | undefined
spanProcessor
: new
new BatchSpanProcessor<BufferConfig>(_exporter: SpanExporter, config?: BufferConfig | undefined): BatchSpanProcessor
BatchSpanProcessor
(new
new ConsoleSpanExporter(): ConsoleSpanExporter

This is implementation of

SpanExporter

that prints spans to the console. This class can be used for diagnostic purposes.

NOTE: This

SpanExporter

is intended for diagnostics use only, output rendered to the console may change at any time.

ConsoleSpanExporter
())
}))
// Run the effect, providing the tracing layer
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const runPromise: <void, never>(effect: Effect.Effect<void, never, never>, options?: {
readonly signal?: AbortSignal;
} | undefined) => Promise<void>

Executes an effect and returns the result as a Promise.

When to Use

Use runPromise when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

@seerunPromiseExit for a version that returns an Exit type instead of rejecting.

@example

// Title: Running a Successful Effect as a Promise
import { Effect } from "effect"
Effect.runPromise(Effect.succeed(1)).then(console.log)
// Output: 1

@example

//Example: Handling a Failing Effect as a Rejected Promise import { Effect } from "effect"

Effect.runPromise(Effect.fail("my error")).catch(console.error) // Output: // (FiberFailure) Error: my error

@since2.0.0

runPromise
(
const parent: Effect.Effect<void, never, never>
parent
.
Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
pipe
(
import Effect

@since2.0.0

@since2.0.0

@since2.0.0

Effect
.
const provide: <Resource, never, never>(layer: Layer<Resource, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Resource>> (+9 overloads)

Provides the necessary Layers to an effect, removing its dependency on the environment.

You can pass multiple layers, a Context, Runtime, or ManagedRuntime to the effect.

@seeprovideService for providing a service to an effect.

@example

import { Context, Effect, Layer } from "effect"
class Database extends Context.Tag("Database")<
Database,
{ readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
>() {}
const DatabaseLive = Layer.succeed(
Database,
{
// Simulate a database query
query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
}
)
// ┌─── Effect<unknown[], never, Database>
// ▼
const program = Effect.gen(function*() {
const database = yield* Database
const result = yield* database.query("SELECT * FROM users")
return result
})
// ┌─── Effect<unknown[], never, never>
// ▼
const runnable = Effect.provide(program, DatabaseLive)
Effect.runPromise(runnable).then(console.log)
// Output:
// timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
// []

@since2.0.0

provide
(
const NodeSdkLive: Layer<Resource, never, never>
NodeSdkLive
)))
/*
Example Output:
{
resource: {
attributes: {
'service.name': 'example',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': '@effect/opentelemetry',
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined },
traceId: 'a9cd69ad70698a0c7b7b774597c77d39',
parentId: 'a09e5c3fdfdbbc1d', // This indicates the span is a child of 'parent'
traceState: undefined,
name: 'child',
id: '210d2f9b648389a4', // Unique ID for the child span
kind: 0,
timestamp: 1733220970590126.2,
duration: 101579.875,
attributes: {},
status: { code: 1 },
events: [],
links: []
}
{
resource: {
attributes: {
'service.name': 'example',
'telemetry.sdk.language': 'nodejs',
'telemetry.sdk.name': '@effect/opentelemetry',
'telemetry.sdk.version': '1.28.0'
}
},
instrumentationScope: { name: 'example', version: undefined, schemaUrl: undefined },
traceId: 'a9cd69ad70698a0c7b7b774597c77d39',
parentId: undefined, // Indicates this is the root span
traceState: undefined,
name: 'parent',
id: 'a09e5c3fdfdbbc1d', // Unique ID for the parent span
kind: 0,
timestamp: 1733220970569015.2,
duration: 132612.208,
attributes: {},
status: { code: 1 },
events: [],
links: []
}
*/

The parent-child relationship is evident in the span output, where the parentId of the child span matches the id of the parent span. This structure helps track how operations are related within a single trace.

In this tutorial, we’ll guide you through simulating and visualizing traces using a sample instrumented Node.js application. We will use Docker, Prometheus, Grafana, and Tempo to create, collect, and visualize traces.

Let’s understand the tools we’ll be using in simple terms:

  • Docker: Docker allows us to run applications in containers. Think of a container as a lightweight and isolated environment where your application can run consistently, regardless of the host system. It’s a bit like a virtual machine but more efficient.

  • Prometheus: Prometheus is a monitoring and alerting toolkit. It collects metrics and data about your applications and stores them for further analysis. This helps in identifying performance issues and understanding the behavior of your applications.

  • Grafana: Grafana is a visualization and analytics platform. It helps in creating beautiful and interactive dashboards to visualize your application’s data. You can use it to graphically represent metrics collected by Prometheus.

  • Tempo: Tempo is a distributed tracing system that allows you to trace the journey of a request as it flows through your application. It provides insights into how requests are processed and helps in debugging and optimizing your applications.

To get Docker, follow these steps:

  1. Visit the Docker website at https://www.docker.com/.

  2. Download Docker Desktop for your operating system (Windows or macOS) and install it.

  3. After installation, open Docker Desktop, and it will run in the background.

Now, let’s simulate traces using a sample Node.js application. We’ll provide you with the code and guide you on setting up the necessary components.

  1. Download Docker Files. Download the required Docker files: docker.zip

  2. Set Up docker. Unzip the downloaded file, navigate to the /docker/local directory in your terminal or command prompt and run the following command to start the necessary services:

    Terminal window
    docker-compose up
  3. Simulate Traces. Run the following example code in your Node.js environment. This code simulates a set of tasks and generates traces.

    Before proceeding, you’ll need to install additional libraries in addition to the latest version of effect. Here are the required libraries:

    Terminal window
    npm install @effect/opentelemetry
    npm install @opentelemetry/exporter-trace-otlp-http
    npm install @opentelemetry/sdk-trace-base
    npm install @opentelemetry/sdk-trace-web
    npm install @opentelemetry/sdk-trace-node
    import {
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    } from "effect"
    import {
    import NodeSdk
    NodeSdk
    } from "@effect/opentelemetry"
    import {
    class BatchSpanProcessor
    BatchSpanProcessor
    } from "@opentelemetry/sdk-trace-base"
    import {
    class OTLPTraceExporter

    Collector Trace Exporter for Node

    OTLPTraceExporter
    } from "@opentelemetry/exporter-trace-otlp-http"
    // Function to simulate a task with possible subtasks
    const
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    = (
    name: string
    name
    : string,
    delay: number
    delay
    : number,
    children: readonly Effect.Effect<void, never, never>[]
    children
    :
    interface ReadonlyArray<T>
    ReadonlyArray
    <
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    interface Effect<out A, out E = never, out R = never>

    The Effect interface defines a value that lazily describes a workflow or job. The workflow requires some context R, and may fail with an error of type E, or succeed with a value of type A.

    Effect values model resourceful interaction with the outside world, including synchronous, asynchronous, concurrent, and parallel interaction. They use a fiber-based concurrency model, with built-in support for scheduling, fine-grained interruption, structured concurrency, and high scalability.

    To run an Effect value, you need a Runtime, which is a type that is capable of executing Effect values.

    @since2.0.0

    @since2.0.0

    Effect
    <void>> = []
    ) =>
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const gen: <YieldWrap<Effect.Effect<void, never, never>>, void>(f: (resume: Effect.Adapter) => Generator<YieldWrap<Effect.Effect<void, never, never>>, void, never>) => Effect.Effect<...> (+1 overload)

    Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

    When to Use

    gen allows you to write code that looks and behaves like synchronous code, but it can handle asynchronous tasks, errors, and complex control flow (like loops and conditions). It helps make asynchronous code more readable and easier to manage.

    The generator functions work similarly to async/await but with more explicit control over the execution of effects. You can yield* values from effects and return the final result at the end.

    @example

    import { Effect } from "effect"
    const addServiceCharge = (amount: number) => amount + 1
    const applyDiscount = (
    total: number,
    discountRate: number
    ): Effect.Effect<number, Error> =>
    discountRate === 0
    ? Effect.fail(new Error("Discount rate cannot be zero"))
    : Effect.succeed(total - (total * discountRate) / 100)
    const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
    const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
    export const program = Effect.gen(function* () {
    const transactionAmount = yield* fetchTransactionAmount
    const discountRate = yield* fetchDiscountRate
    const discountedAmount = yield* applyDiscount(
    transactionAmount,
    discountRate
    )
    const finalAmount = addServiceCharge(discountedAmount)
    return `Final amount to charge: ${finalAmount}`
    })

    @since2.0.0

    gen
    (function* () {
    yield*
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const log: (...message: ReadonlyArray<any>) => Effect.Effect<void, never, never>

    Logs one or more messages or error causes at the current log level, which is INFO by default. This function allows logging multiple items at once and can include detailed error information using Cause instances.

    To adjust the log level, use the Logger.withMinimumLogLevel function.

    @example

    import { Cause, Effect } from "effect"
    const program = Effect.log(
    "message1",
    "message2",
    Cause.die("Oh no!"),
    Cause.die("Oh uh!")
    )
    // Effect.runFork(program)
    // Output:
    // timestamp=... level=INFO fiber=#0 message=message1 message=message2 cause="Error: Oh no!
    // Error: Oh uh!"

    @since2.0.0

    log
    (
    name: string
    name
    )
    yield*
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const sleep: (duration: DurationInput) => Effect.Effect<void>

    Returns an effect that suspends for the specified duration. This method is asynchronous, and does not actually block the fiber executing the effect.

    @since2.0.0

    sleep
    (`${
    delay: number
    delay
    } millis`)
    for (const
    const child: Effect.Effect<void, never, never>
    child
    of
    children: readonly Effect.Effect<void, never, never>[]
    children
    ) {
    yield*
    const child: Effect.Effect<void, never, never>
    child
    }
    yield*
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const sleep: (duration: DurationInput) => Effect.Effect<void>

    Returns an effect that suspends for the specified duration. This method is asynchronous, and does not actually block the fiber executing the effect.

    @since2.0.0

    sleep
    (`${
    delay: number
    delay
    } millis`)
    }).
    Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<void, never, never>): Effect.Effect<...> (+21 overloads)
    pipe
    (
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const withSpan: (name: string, options?: SpanOptions | undefined) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1 overload)

    Wraps the effect with a new span for tracing.

    @since2.0.0

    withSpan
    (
    name: string
    name
    ))
    const
    const poll: Effect.Effect<void, never, never>
    poll
    =
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/poll", 1)
    // Create a program with tasks and subtasks
    const
    const program: Effect.Effect<void, never, never>
    program
    =
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("client", 2, [
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/api", 3, [
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/authN", 4, [
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/authZ", 5)]),
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/payment Gateway", 6, [
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("DB", 7),
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("Ext. Merchant", 8)
    ]),
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/dispatch", 9, [
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/dispatch/search", 10),
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const all: <readonly [Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>], {
    concurrency: "inherit";
    }>(arg: readonly [Effect.Effect<void, never, never>, Effect.Effect<...>, Effect.Effect<...>], options?: {
    concurrency: "inherit";
    } | undefined) => Effect.Effect<...>

    Combines multiple effects into one, returning results based on the input structure.

    When to Use

    Use Effect.all when you need to run multiple effects and combine their results into a single output. It supports tuples, iterables, structs, and records, making it flexible for different input types.

    For instance, if the input is a tuple:

    // ┌─── a tuple of effects
    // ▼
    Effect.all([effect1, effect2, ...])

    the effects are executed sequentially, and the result is a new effect containing the results as a tuple. The results in the tuple match the order of the effects passed to Effect.all.

    Concurrency

    You can control the execution order (e.g., sequential vs. concurrent) using the concurrency option.

    Short-Circuiting Behavior

    The Effect.all function stops execution on the first error it encounters, this is called "short-circuiting". If any effect in the collection fails, the remaining effects will not run, and the error will be propagated. To change this behavior, you can use the mode option, which allows all effects to run and collect results as Either or Option.

    The mode option

    The { mode: "either" } option changes the behavior of Effect.all to ensure all effects run, even if some fail. Instead of stopping on the first failure, this mode collects both successes and failures, returning an array of Either instances where each result is either a Right (success) or a Left (failure).

    Similarly, the { mode: "validate" } option uses Option to indicate success or failure. Each effect returns None for success and Some with the error for failure.

    @seeforEach for iterating over elements and applying an effect.

    @example

    // Title: Combining Effects in Tuples
    import { Effect, Console } from "effect"
    const tupleOfEffects = [
    Effect.succeed(42).pipe(Effect.tap(Console.log)),
    Effect.succeed("Hello").pipe(Effect.tap(Console.log))
    ] as const
    // ┌─── Effect<[number, string], never, never>
    // ▼
    const resultsAsTuple = Effect.all(tupleOfEffects)
    Effect.runPromise(resultsAsTuple).then(console.log)
    // Output:
    // 42
    // Hello
    // [ 42, 'Hello' ]

    @example

    // Title: Combining Effects in Iterables import { Effect, Console } from "effect"

    const iterableOfEffects: Iterable<Effect.Effect> = [1, 2, 3].map( (n) => Effect.succeed(n).pipe(Effect.tap(Console.log)) )

    // ┌─── Effect<number[], never, never> // ▼ const resultsAsArray = Effect.all(iterableOfEffects)

    Effect.runPromise(resultsAsArray).then(console.log) // Output: // 1 // 2 // 3 // [ 1, 2, 3 ]

    @example

    // Title: Combining Effects in Structs import { Effect, Console } from "effect"

    const structOfEffects = { a: Effect.succeed(42).pipe(Effect.tap(Console.log)), b: Effect.succeed("Hello").pipe(Effect.tap(Console.log)) }

    // ┌─── Effect<{ a: number; b: string; }, never, never> // ▼ const resultsAsStruct = Effect.all(structOfEffects)

    Effect.runPromise(resultsAsStruct).then(console.log) // Output: // 42 // Hello // { a: 42, b: 'Hello' }

    @example

    // Title: Combining Effects in Records import { Effect, Console } from "effect"

    const recordOfEffects: Record<string, Effect.Effect> = { key1: Effect.succeed(1).pipe(Effect.tap(Console.log)), key2: Effect.succeed(2).pipe(Effect.tap(Console.log)) }

    // ┌─── Effect<{ [x: string]: number; }, never, never> // ▼ const resultsAsRecord = Effect.all(recordOfEffects)

    Effect.runPromise(resultsAsRecord).then(console.log) // Output: // 1 // 2 // { key1: 1, key2: 2 }

    @example

    // Title: Short-Circuiting Behavior import { Effect, Console } from "effect"

    const program = Effect.all([ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), // Won't execute due to earlier failure Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ])

    Effect.runPromiseExit(program).then(console.log) // Output: // Task1 // { // _id: 'Exit', // _tag: 'Failure', // cause: { _id: 'Cause', _tag: 'Fail', failure: 'Task2: Oh no!' } // }

    @example

    // Title: Collecting Results with mode: "either" import { Effect, Console } from "effect"

    const effects = [ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ]

    const program = Effect.all(effects, { mode: "either" })

    Effect.runPromiseExit(program).then(console.log) // Output: // Task1 // Task3 // { // _id: 'Exit', // _tag: 'Success', // value: [ // { _id: 'Either', _tag: 'Right', right: 'Task1' }, // { _id: 'Either', _tag: 'Left', left: 'Task2: Oh no!' }, // { _id: 'Either', _tag: 'Right', right: 'Task3' } // ] // }

    @example

    //Example: Collecting Results with mode: "validate" import { Effect, Console } from "effect"

    const effects = [ Effect.succeed("Task1").pipe(Effect.tap(Console.log)), Effect.fail("Task2: Oh no!").pipe(Effect.tap(Console.log)), Effect.succeed("Task3").pipe(Effect.tap(Console.log)) ]

    const program = Effect.all(effects, { mode: "validate" })

    Effect.runPromiseExit(program).then((result) => console.log("%o", result)) // Output: // Task1 // Task3 // { // _id: 'Exit', // _tag: 'Failure', // cause: { // _id: 'Cause', // _tag: 'Fail', // failure: [ // { _id: 'Option', _tag: 'None' }, // { _id: 'Option', _tag: 'Some', value: 'Task2: Oh no!' }, // { _id: 'Option', _tag: 'None' } // ] // } // }

    @since2.0.0

    all
    ([
    const poll: Effect.Effect<void, never, never>
    poll
    ,
    const poll: Effect.Effect<void, never, never>
    poll
    ,
    const poll: Effect.Effect<void, never, never>
    poll
    ], {
    concurrency: "inherit"
    concurrency
    : "inherit" }),
    const task: (name: string, delay: number, children?: ReadonlyArray<Effect.Effect<void>>) => Effect.Effect<void, never, never>
    task
    ("/pollDriver/{id}", 11)
    ])
    ])
    ])
    const
    const NodeSdkLive: Layer<Resource, never, never>
    NodeSdkLive
    =
    import NodeSdk
    NodeSdk
    .
    const layer: (evaluate: LazyArg<NodeSdk.Configuration>) => Layer<Resource> (+1 overload)

    @since1.0.0

    layer
    (() => ({
    Configuration.resource?: {
    readonly serviceName: string;
    readonly serviceVersion?: string;
    readonly attributes?: ResourceAttributes;
    } | undefined
    resource
    : {
    serviceName: string
    serviceName
    : "example" },
    Configuration.spanProcessor?: SpanProcessor | readonly SpanProcessor[] | undefined
    spanProcessor
    : new
    new BatchSpanProcessor<BufferConfig>(_exporter: SpanExporter, config?: BufferConfig | undefined): BatchSpanProcessor
    BatchSpanProcessor
    (new
    new OTLPTraceExporter(config?: OTLPExporterNodeConfigBase): OTLPTraceExporter

    Collector Trace Exporter for Node

    OTLPTraceExporter
    ())
    }))
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const runPromise: <void, never>(effect: Effect.Effect<void, never, never>, options?: {
    readonly signal?: AbortSignal;
    } | undefined) => Promise<void>

    Executes an effect and returns the result as a Promise.

    When to Use

    Use runPromise when you need to execute an effect and work with the result using Promise syntax, typically for compatibility with other promise-based code.

    If the effect succeeds, the promise will resolve with the result. If the effect fails, the promise will reject with an error.

    @seerunPromiseExit for a version that returns an Exit type instead of rejecting.

    @example

    // Title: Running a Successful Effect as a Promise
    import { Effect } from "effect"
    Effect.runPromise(Effect.succeed(1)).then(console.log)
    // Output: 1

    @example

    //Example: Handling a Failing Effect as a Rejected Promise import { Effect } from "effect"

    Effect.runPromise(Effect.fail("my error")).catch(console.error) // Output: // (FiberFailure) Error: my error

    @since2.0.0

    runPromise
    (
    const program: Effect.Effect<void, never, never>
    program
    .
    Pipeable.pipe<Effect.Effect<void, never, never>, Effect.Effect<void, never, never>, Effect.Effect<void, never, never>>(this: Effect.Effect<...>, ab: (_: Effect.Effect<void, never, never>) => Effect.Effect<...>, bc: (_: Effect.Effect<...>) => Effect.Effect<...>): Effect.Effect<...> (+21 overloads)
    pipe
    (
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const provide: <Resource, never, never>(layer: Layer<Resource, never, never>) => <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Resource>> (+9 overloads)

    Provides the necessary Layers to an effect, removing its dependency on the environment.

    You can pass multiple layers, a Context, Runtime, or ManagedRuntime to the effect.

    @seeprovideService for providing a service to an effect.

    @example

    import { Context, Effect, Layer } from "effect"
    class Database extends Context.Tag("Database")<
    Database,
    { readonly query: (sql: string) => Effect.Effect<Array<unknown>> }
    >() {}
    const DatabaseLive = Layer.succeed(
    Database,
    {
    // Simulate a database query
    query: (sql: string) => Effect.log(`Executing query: ${sql}`).pipe(Effect.as([]))
    }
    )
    // ┌─── Effect<unknown[], never, Database>
    // ▼
    const program = Effect.gen(function*() {
    const database = yield* Database
    const result = yield* database.query("SELECT * FROM users")
    return result
    })
    // ┌─── Effect<unknown[], never, never>
    // ▼
    const runnable = Effect.provide(program, DatabaseLive)
    Effect.runPromise(runnable).then(console.log)
    // Output:
    // timestamp=... level=INFO fiber=#0 message="Executing query: SELECT * FROM users"
    // []

    @since2.0.0

    provide
    (
    const NodeSdkLive: Layer<Resource, never, never>
    NodeSdkLive
    ),
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const catchAllCause: <never, void, never, never>(f: (cause: Cause<never>) => Effect.Effect<void, never, never>) => <A, R>(self: Effect.Effect<A, never, R>) => Effect.Effect<void | A, never, R> (+1 overload)

    Handles both recoverable and unrecoverable errors by providing a recovery effect.

    When to Use

    The catchAllCause function allows you to handle all errors, including unrecoverable defects, by providing a recovery effect. The recovery logic is based on the Cause of the error, which provides detailed information about the failure.

    When to Recover from Defects

    Defects are unexpected errors that typically shouldn't be recovered from, as they often indicate serious issues. However, in some cases, such as dynamically loaded plugins, controlled recovery might be needed.

    @example

    // Title: Recovering from All Errors
    import { Cause, Effect } from "effect"
    // Define an effect that may fail with a recoverable or unrecoverable error
    const program = Effect.fail("Something went wrong!")
    // Recover from all errors by examining the cause
    const recovered = program.pipe(
    Effect.catchAllCause((cause) =>
    Cause.isFailType(cause)
    ? Effect.succeed("Recovered from a regular error")
    : Effect.succeed("Recovered from a defect")
    )
    )
    Effect.runPromise(recovered).then(console.log)
    // Output: "Recovered from a regular error"

    @since2.0.0

    catchAllCause
    (
    import Effect

    @since2.0.0

    @since2.0.0

    @since2.0.0

    Effect
    .
    const logError: (...message: ReadonlyArray<any>) => Effect.Effect<void, never, never>

    Logs the specified message or cause at the Error log level.

    @since2.0.0

    logError
    )
    )
    )
    /*
    Output:
    timestamp=... level=INFO fiber=#0 message=client
    timestamp=... level=INFO fiber=#0 message=/api
    timestamp=... level=INFO fiber=#0 message=/authN
    timestamp=... level=INFO fiber=#0 message=/authZ
    timestamp=... level=INFO fiber=#0 message="/payment Gateway"
    timestamp=... level=INFO fiber=#0 message=DB
    timestamp=... level=INFO fiber=#0 message="Ext. Merchant"
    timestamp=... level=INFO fiber=#0 message=/dispatch
    timestamp=... level=INFO fiber=#0 message=/dispatch/search
    timestamp=... level=INFO fiber=#3 message=/poll
    timestamp=... level=INFO fiber=#4 message=/poll
    timestamp=... level=INFO fiber=#5 message=/poll
    timestamp=... level=INFO fiber=#0 message=/pollDriver/{id}
    */
  4. Visualize Traces. Now, open your web browser and go to http://localhost:3000/explore. You will see a generated Trace ID on the web page. Click on it to see the details of the trace.

    Traces in Grafana Tempo