您可以通过使用 Effect.withSpan API 创建 span 来为 effect 添加追踪。这有助于您跟踪 effect 中的特定操作。
示例(为 Effect 添加 Span)
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect } from"effect"
2
3
// 定义一个延迟 100 毫秒的 effect
4
const
constprogram:Effect.Effect<void, never, never>
program=
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constvoid:Effect.Effect<void, never, never>
export void
Represents an effect that does nothing and produces no value.
When to Use
Use this effect when you need to represent an effect that does nothing.
This is useful in scenarios where you need to satisfy an effect-based
interface or control program flow without performing any operations. For
example, it can be used in situations where you want to return an effect
from a function but do not need to compute or return any result.
constdelay: (duration:DurationInput) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R> (+1overload)
Delays the execution of an effect by a specified Duration.
**Details
This function postpones the execution of the provided effect by the specified
duration. The duration can be provided in various formats supported by the
Duration module.
Internally, this function does not block the thread; instead, it uses an
efficient, non-blocking mechanism to introduce the delay.
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Wraps the effect with a new span for tracing.
@since ― 2.0.0
withSpan("myspan"))
使用 span 检测 effect 不会改变其类型。如果您从 Effect<A, E, R> 开始,结果仍然是 Effect<A, E, R>。
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,
5
classBatchSpanProcessor
BatchSpanProcessor
6
} from"@opentelemetry/sdk-trace-base"
7
8
// 定义一个延迟 100 毫秒的 effect
9
const
constprogram:Effect.Effect<void, never, never>
program=
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constvoid:Effect.Effect<void, never, never>
export void
Represents an effect that does nothing and produces no value.
When to Use
Use this effect when you need to represent an effect that does nothing.
This is useful in scenarios where you need to satisfy an effect-based
interface or control program flow without performing any operations. For
example, it can be used in situations where you want to return an effect
from a function but do not need to compute or return any result.
constdelay: (duration:DurationInput) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R> (+1overload)
Delays the execution of an effect by a specified Duration.
**Details
This function postpones the execution of the provided effect by the specified
duration. The duration can be provided in various formats supported by the
Duration module.
Internally, this function does not block the thread; instead, it uses an
efficient, non-blocking mechanism to introduce the delay.
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Executes an effect and returns the result as a Promise.
Details
This function runs an effect and converts its result into a Promise. If the
effect succeeds, the Promise will resolve with the successful result. If
the effect fails, the Promise will reject with an error, which includes the
failure details of the effect.
The optional options parameter allows you to pass an AbortSignal for
cancellation, enabling more fine-grained control over asynchronous tasks.
When to Use
Use this function when you need to execute an effect and work with its result
in a promise-based system, such as when integrating with third-party
libraries that expect Promise results.
Example (Running a Successful Effect as a Promise)
constprovide: <Resource, never, never>(layer:Layer<Resource, never, never>) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Resource>> (+9overloads)
Provides necessary dependencies to an effect, removing its environmental
requirements.
Details
This function allows you to supply the required environment for an effect.
The environment can be provided in the form of one or more Layers, a
Context, a Runtime, or a ManagedRuntime. Once the environment is
provided, the effect can run without requiring external dependencies.
You can compose layers to create a modular and reusable way of setting up the
environment for effects. For example, layers can be used to configure
databases, logging services, or any other required dependencies.
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
.
Example (Creating a Failed Effect)
import { Effect } from"effect"
// ┌─── Effect<never, Error, never>
// ▼
constfailure= Effect.fail(
newError("Operation failed due to network error")
)
@see ― succeed to create an effect that represents a successful value.
constdelay: (duration:DurationInput) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R> (+1overload)
Delays the execution of an effect by a specified Duration.
**Details
This function postpones the execution of the provided effect by the specified
duration. The duration can be provided in various formats supported by the
Duration module.
Internally, this function does not block the thread; instead, it uses an
efficient, non-blocking mechanism to introduce the delay.
Example
import { Console, Effect } from"effect"
consttask= Console.log("Task executed")
constprogram= Console.log("start").pipe(
Effect.andThen(
// Delays the log message by 2 seconds
task.pipe(Effect.delay("2 seconds"))
)
)
Effect.runFork(program)
// Output:
// start
// Task executed
@since ― 2.0.0
delay("100 millis"),
10
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Runs an effect and returns a Promise that resolves to an Exit,
representing the outcome.
Details
This function executes an effect and resolves to an Exit object. The Exit
type provides detailed information about the result of the effect:
If the effect succeeds, the Exit will be of type Success and include
the value produced by the effect.
If the effect fails, the Exit will be of type Failure and contain a
Cause object, detailing the failure.
Using this function allows you to examine both successful results and failure
cases in a unified way, while still leveraging Promise for handling the
asynchronous behavior of the effect.
When to Use
Use this function when you need to understand the outcome of an effect,
whether it succeeded or failed, and want to work with this result using
Promise syntax. This is particularly useful when integrating with systems
that rely on promises but need more detailed error handling than a simple
rejection.
Example (Handling Results as Exit)
import { Effect } from"effect"
// Execute a successful effect and get the Exit result as a Promise
constprovide: <Resource, never, never>(layer:Layer<Resource, never, never>) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Resource>> (+9overloads)
Provides necessary dependencies to an effect, removing its environmental
requirements.
Details
This function allows you to supply the required environment for an effect.
The environment can be provided in the form of one or more Layers, a
Context, a Runtime, or a ManagedRuntime. Once the environment is
provided, the effect can run without requiring external dependencies.
You can compose layers to create a modular and reusable way of setting up the
environment for effects. For example, layers can be used to configure
databases, logging services, or any other required dependencies.
Attaches callbacks for the resolution and/or rejection of the Promise.
@param ― onfulfilled The callback to execute when the Promise is resolved.
@param ― onrejected The callback to execute when the Promise is rejected.
@returns ― A Promise for the completion of which ever callback is executed.
then(
19
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(newError('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
constname='Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console class:
constout=getStreamSomehow();
consterr=getStreamSomehow();
constmyConsole=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(newError('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
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()).
In this example, the span’s status code is 2, indicating an error. The message in the status provides more details about the failure.
Adding Annotations
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)
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect } from"effect"
2
import {
import NodeSdk
NodeSdk } from"@effect/opentelemetry"
3
import {
4
classConsoleSpanExporter
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,
5
classBatchSpanProcessor
BatchSpanProcessor
6
} from"@opentelemetry/sdk-trace-base"
7
8
const
constprogram:Effect.Effect<void, never, never>
program=
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constvoid:Effect.Effect<void, never, never>
export void
Represents an effect that does nothing and produces no value.
When to Use
Use this effect when you need to represent an effect that does nothing.
This is useful in scenarios where you need to satisfy an effect-based
interface or control program flow without performing any operations. For
example, it can be used in situations where you want to return an effect
from a function but do not need to compute or return any result.
constdelay: (duration:DurationInput) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R> (+1overload)
Delays the execution of an effect by a specified Duration.
**Details
This function postpones the execution of the provided effect by the specified
duration. The duration can be provided in various formats supported by the
Duration module.
Internally, this function does not block the thread; instead, it uses an
efficient, non-blocking mechanism to introduce the delay.
Example
import { Console, Effect } from"effect"
consttask= Console.log("Task executed")
constprogram= Console.log("start").pipe(
Effect.andThen(
// Delays the log message by 2 seconds
task.pipe(Effect.delay("2 seconds"))
)
)
Effect.runFork(program)
// Output:
// start
// Task executed
@since ― 2.0.0
delay("100 millis"),
10
// Annotate the span with a key-value pair
11
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
consttap: <void, Effect.Effect<void, never, never>>(f: (a:void) =>Effect.Effect<void, never, never>) => <E, R>(self:Effect.Effect<void, E, R>) =>Effect.Effect<void, E, R> (+7overloads)
Runs a side effect with the result of an effect without changing the original
value.
Details
This function 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.
When to Use
Use this function 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.
Example (Logging a step in a pipeline)
import { Console, Effect, pipe } from"effect"
// Function to apply a discount safely to a transaction amount
constapplyDiscount= (
total:number,
discountRate:number
):Effect.Effect<number, Error> =>
discountRate ===0
? Effect.fail(newError("Discount rate cannot be zero"))
Adds annotations to the currently active span for traceability.
Details
This function adds key-value annotations to the currently active span in the
effect's trace. These annotations help provide more context about the
operation being executed at a specific point in time. Unlike
annotateSpans
, which applies to all spans in an effect, this function
focuses solely on the active span.
You can either pass a single key-value pair or a record of key-value pairs to
annotate the span. These annotations are useful for adding metadata to
operations, especially in systems with detailed observability requirements.
@since ― 2.0.0
annotateCurrentSpan("key", "value")),
12
// Wrap the effect in a span named 'myspan'
13
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Executes an effect and returns the result as a Promise.
Details
This function runs an effect and converts its result into a Promise. If the
effect succeeds, the Promise will resolve with the successful result. If
the effect fails, the Promise will reject with an error, which includes the
failure details of the effect.
The optional options parameter allows you to pass an AbortSignal for
cancellation, enabling more fine-grained control over asynchronous tasks.
When to Use
Use this function when you need to execute an effect and work with its result
in a promise-based system, such as when integrating with third-party
libraries that expect Promise results.
Example (Running a Successful Effect as a Promise)
constprovide: <Resource, never, never>(layer:Layer<Resource, never, never>) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Resource>> (+9overloads)
Provides necessary dependencies to an effect, removing its environmental
requirements.
Details
This function allows you to supply the required environment for an effect.
The environment can be provided in the form of one or more Layers, a
Context, a Runtime, or a ManagedRuntime. Once the environment is
provided, the effect can run without requiring external dependencies.
You can compose layers to create a modular and reusable way of setting up the
environment for effects. For example, layers can be used to configure
databases, logging services, or any other required dependencies.
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.
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect } from"effect"
2
import {
import NodeSdk
NodeSdk } from"@effect/opentelemetry"
3
import {
4
classConsoleSpanExporter
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,
5
classBatchSpanProcessor
BatchSpanProcessor
6
} from"@opentelemetry/sdk-trace-base"
7
8
// Define a program that logs a message and delays for 100 milliseconds
Logs one or more messages or error causes at the current log level.
Details
This function provides a simple way to log messages or error causes during
the execution of your effects. By default, logs are recorded at the INFO
level, but this can be adjusted using other logging utilities
(Logger.withMinimumLogLevel). Multiple items, including Cause instances,
can be logged in a single call. When logging Cause instances, detailed
error information is included in the log output.
The log output includes useful metadata like the current timestamp, log
level, and fiber ID, making it suitable for debugging and tracking purposes.
This function does not interrupt or alter the effect's execution flow.
constdelay: (duration:DurationInput) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R> (+1overload)
Delays the execution of an effect by a specified Duration.
**Details
This function postpones the execution of the provided effect by the specified
duration. The duration can be provided in various formats supported by the
Duration module.
Internally, this function does not block the thread; instead, it uses an
efficient, non-blocking mechanism to introduce the delay.
Example
import { Console, Effect } from"effect"
consttask= Console.log("Task executed")
constprogram= Console.log("start").pipe(
Effect.andThen(
// Delays the log message by 2 seconds
task.pipe(Effect.delay("2 seconds"))
)
)
Effect.runFork(program)
// Output:
// start
// Task executed
@since ― 2.0.0
delay("100 millis"),
11
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Executes an effect and returns the result as a Promise.
Details
This function runs an effect and converts its result into a Promise. If the
effect succeeds, the Promise will resolve with the successful result. If
the effect fails, the Promise will reject with an error, which includes the
failure details of the effect.
The optional options parameter allows you to pass an AbortSignal for
cancellation, enabling more fine-grained control over asynchronous tasks.
When to Use
Use this function when you need to execute an effect and work with its result
in a promise-based system, such as when integrating with third-party
libraries that expect Promise results.
Example (Running a Successful Effect as a Promise)
constprovide: <Resource, never, never>(layer:Layer<Resource, never, never>) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Resource>> (+9overloads)
Provides necessary dependencies to an effect, removing its environmental
requirements.
Details
This function allows you to supply the required environment for an effect.
The environment can be provided in the form of one or more Layers, a
Context, a Runtime, or a ManagedRuntime. Once the environment is
provided, the effect can run without requiring external dependencies.
You can compose layers to create a modular and reusable way of setting up the
environment for effects. For example, layers can be used to configure
databases, logging services, or any other required dependencies.
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:
Field
Description
name
The name of the event, which corresponds to the logged message (e.g., 'Hello').
attributes
Key-value pairs that provide additional context about the event, such as fiberId and log level.
time
The timestamp of when the event occurred, shown in a high-precision format.
droppedAttributesCount
Indicates how many attributes were discarded, if any. In this case, no attributes were dropped.
Nesting Spans
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)
1
import {
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect } from"effect"
2
import {
import NodeSdk
NodeSdk } from"@effect/opentelemetry"
3
import {
4
classConsoleSpanExporter
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,
5
classBatchSpanProcessor
BatchSpanProcessor
6
} from"@opentelemetry/sdk-trace-base"
7
8
const
constchild:Effect.Effect<void, never, never>
child=
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constvoid:Effect.Effect<void, never, never>
export void
Represents an effect that does nothing and produces no value.
When to Use
Use this effect when you need to represent an effect that does nothing.
This is useful in scenarios where you need to satisfy an effect-based
interface or control program flow without performing any operations. For
example, it can be used in situations where you want to return an effect
from a function but do not need to compute or return any result.
constdelay: (duration:DurationInput) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, R> (+1overload)
Delays the execution of an effect by a specified Duration.
**Details
This function postpones the execution of the provided effect by the specified
duration. The duration can be provided in various formats supported by the
Duration module.
Internally, this function does not block the thread; instead, it uses an
efficient, non-blocking mechanism to introduce the delay.
Example
import { Console, Effect } from"effect"
consttask= Console.log("Task executed")
constprogram= Console.log("start").pipe(
Effect.andThen(
// Delays the log message by 2 seconds
task.pipe(Effect.delay("2 seconds"))
)
)
Effect.runFork(program)
// Output:
// start
// Task executed
@since ― 2.0.0
delay("100 millis"),
10
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
Effect.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.
Suspends the execution of an effect for a specified Duration.
Details
This function pauses the execution of an effect for a given duration. It is
asynchronous, meaning that it does not block the fiber executing the effect.
Instead, the fiber is suspended during the delay period and can resume once
the specified time has passed.
The duration can be specified using various formats supported by the
Duration module, such as a string ("2 seconds") or numeric value
representing milliseconds.
Example
import { Effect } from"effect"
constprogram= Effect.gen(function*() {
console.log("Starting task...")
yield* Effect.sleep("3 seconds") // Waits for 3 seconds
Suspends the execution of an effect for a specified Duration.
Details
This function pauses the execution of an effect for a given duration. It is
asynchronous, meaning that it does not block the fiber executing the effect.
Instead, the fiber is suspended during the delay period and can resume once
the specified time has passed.
The duration can be specified using various formats supported by the
Duration module, such as a string ("2 seconds") or numeric value
representing milliseconds.
Example
import { Effect } from"effect"
constprogram= Effect.gen(function*() {
console.log("Starting task...")
yield* Effect.sleep("3 seconds") // Waits for 3 seconds
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Executes an effect and returns the result as a Promise.
Details
This function runs an effect and converts its result into a Promise. If the
effect succeeds, the Promise will resolve with the successful result. If
the effect fails, the Promise will reject with an error, which includes the
failure details of the effect.
The optional options parameter allows you to pass an AbortSignal for
cancellation, enabling more fine-grained control over asynchronous tasks.
When to Use
Use this function when you need to execute an effect and work with its result
in a promise-based system, such as when integrating with third-party
libraries that expect Promise results.
Example (Running a Successful Effect as a Promise)
constprovide: <Resource, never, never>(layer:Layer<Resource, never, never>) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Resource>> (+9overloads)
Provides necessary dependencies to an effect, removing its environmental
requirements.
Details
This function allows you to supply the required environment for an effect.
The environment can be provided in the form of one or more Layers, a
Context, a Runtime, or a ManagedRuntime. Once the environment is
provided, the effect can run without requiring external dependencies.
You can compose layers to create a modular and reusable way of setting up the
environment for effects. For example, layers can be used to configure
databases, logging services, or any other required dependencies.
parentId: undefined, // Indicates this is the root span
64
traceState: undefined,
65
name: 'parent',
66
id: 'a09e5c3fdfdbbc1d', // Unique ID for the parent span
67
kind: 0,
68
timestamp: 1733220970569015.2,
69
duration: 132612.208,
70
attributes: {},
71
status: { code: 1 },
72
events: [],
73
links: []
74
}
75
*/
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.
Tutorial: Visualizing Traces
In this tutorial, we will guide you through visualizing traces generated by a sample Effect application. The sample application has also been configured to export traces traces and/or metrics via HTTP using OTLP format.
To visualize the traces being exported by our application, we will use a Docker image that contains a preconfigured OpenTelemetry backend based on the OpenTelemetry Collector, Prometheus, Loki, Tempo, and Grafana.
Tools Explained
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.
Loki: Loki is a log aggregation system inspired by Prometheus. It does not index the contents of the logs, but rather a set of labels for each log stream.
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.
# Required to export traces over HTTP in OTLP format
npminstall@opentelemetry/exporter-trace-otlp-http
# Required by all applications
npminstall@opentelemetry/sdk-trace-base
# For NodeJS applications
npminstall@opentelemetry/sdk-trace-node
# For browser applications
npminstall@opentelemetry/sdk-trace-web
# If you also need to export metrics
npminstall@opentelemetry/sdk-metrics
Terminal window
# If not already installed
pnpmaddeffect
# Required to integrate Effect with OpenTelemetry
pnpmadd@effect/opentelemetry
# Required to export traces over HTTP in OTLP format
pnpmadd@opentelemetry/exporter-trace-otlp-http
# Required by all applications
pnpmadd@opentelemetry/sdk-trace-base
# For NodeJS applications
pnpmadd@opentelemetry/sdk-trace-node
# For browser applications
pnpmadd@opentelemetry/sdk-trace-web
# If you also need to export metrics
pnpmadd@opentelemetry/sdk-metrics
Terminal window
# If not already installed
yarnaddeffect
# Required to integrate Effect with OpenTelemetry
yarnadd@effect/opentelemetry
# Required to export traces over HTTP in OTLP format
yarnadd@opentelemetry/exporter-trace-otlp-http
# Required by all applications
yarnadd@opentelemetry/sdk-trace-base
# For NodeJS applications
yarnadd@opentelemetry/sdk-trace-node
# For browser applications
yarnadd@opentelemetry/sdk-trace-web
# If you also need to export metrics
yarnadd@opentelemetry/sdk-metrics
Terminal window
# If not already installed
bunaddeffect
# Required to integrate Effect with OpenTelemetry
bunadd@effect/opentelemetry
# Required to export traces over HTTP in OTLP format
bunadd@opentelemetry/exporter-trace-otlp-http
# Required by all applications
bunadd@opentelemetry/sdk-trace-base
# For NodeJS applications
bunadd@opentelemetry/sdk-trace-node
# For browser applications
bunadd@opentelemetry/sdk-trace-web
# If you also need to export metrics
bunadd@opentelemetry/sdk-metrics
Simulate Traces
Now, let’s simulate traces using a sample Node.js application.
The following code simulates a set of tasks and generates traces for each task. It also sets up a Layer which will export traces from our application to our OpenTelemetry backend over HTTP in OTLP format.
The Effect interface defines a value that describes a workflow or job,
which can succeed or fail.
Details
The Effect interface represents a computation that can model a workflow
involving various types of operations, such as synchronous, asynchronous,
concurrent, and parallel interactions. It operates within a context of type
R, and the result can either be a success with a value of type A or a
failure with an error of type E. The Effect is designed to handle complex
interactions with external resources, offering advanced features such as
fiber-based concurrency, scheduling, interruption handling, and scalability.
This makes it suitable for tasks that require fine-grained control over
concurrency and error management.
To execute an Effect value, you need a Runtime, which provides the
environment necessary to run and manage the computation.
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
Effect.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.
Logs one or more messages or error causes at the current log level.
Details
This function provides a simple way to log messages or error causes during
the execution of your effects. By default, logs are recorded at the INFO
level, but this can be adjusted using other logging utilities
(Logger.withMinimumLogLevel). Multiple items, including Cause instances,
can be logged in a single call. When logging Cause instances, detailed
error information is included in the log output.
The log output includes useful metadata like the current timestamp, log
level, and fiber ID, making it suitable for debugging and tracking purposes.
This function does not interrupt or alter the effect's execution flow.
Suspends the execution of an effect for a specified Duration.
Details
This function pauses the execution of an effect for a given duration. It is
asynchronous, meaning that it does not block the fiber executing the effect.
Instead, the fiber is suspended during the delay period and can resume once
the specified time has passed.
The duration can be specified using various formats supported by the
Duration module, such as a string ("2 seconds") or numeric value
representing milliseconds.
Example
import { Effect } from"effect"
constprogram= Effect.gen(function*() {
console.log("Starting task...")
yield* Effect.sleep("3 seconds") // Waits for 3 seconds
Suspends the execution of an effect for a specified Duration.
Details
This function pauses the execution of an effect for a given duration. It is
asynchronous, meaning that it does not block the fiber executing the effect.
Instead, the fiber is suspended during the delay period and can resume once
the specified time has passed.
The duration can be specified using various formats supported by the
Duration module, such as a string ("2 seconds") or numeric value
representing milliseconds.
Example
import { Effect } from"effect"
constprogram= Effect.gen(function*() {
console.log("Starting task...")
yield* Effect.sleep("3 seconds") // Waits for 3 seconds
constwithSpan: (name:string, options?:SpanOptions|undefined) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, ParentSpan>> (+1overload)
Combines multiple effects into one, returning results based on the input
structure.
Details
Use this function 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
This 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.
Executes an effect and returns the result as a Promise.
Details
This function runs an effect and converts its result into a Promise. If the
effect succeeds, the Promise will resolve with the successful result. If
the effect fails, the Promise will reject with an error, which includes the
failure details of the effect.
The optional options parameter allows you to pass an AbortSignal for
cancellation, enabling more fine-grained control over asynchronous tasks.
When to Use
Use this function when you need to execute an effect and work with its result
in a promise-based system, such as when integrating with third-party
libraries that expect Promise results.
Example (Running a Successful Effect as a Promise)
constprovide: <Resource, never, never>(layer:Layer<Resource, never, never>) => <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<A, E, Exclude<R, Resource>> (+9overloads)
Provides necessary dependencies to an effect, removing its environmental
requirements.
Details
This function allows you to supply the required environment for an effect.
The environment can be provided in the form of one or more Layers, a
Context, a Runtime, or a ManagedRuntime. Once the environment is
provided, the effect can run without requiring external dependencies.
You can compose layers to create a modular and reusable way of setting up the
environment for effects. For example, layers can be used to configure
databases, logging services, or any other required dependencies.
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 (Recovering from All Errors)
import { Cause, Effect } from"effect"
// Define an effect that may fail with a recoverable or unrecoverable error
constprogram= Effect.fail("Something went wrong!")
// Recover from all errors by examining the cause
constrecovered= program.pipe(
Effect.catchAllCause((cause) =>
Cause.isFailure(cause)
? Effect.succeed("Recovered from a regular error")
This function logs messages at the ERROR level, suitable for reporting
application errors or failures. These logs are typically used for unexpected
issues that need immediate attention.
Open your web browser and go to http://localhost:3000/explore. You should see the Grafana Tempo TraceQL interface.
To get a list of all available traces, we can select the "Search" query type to get a list of all available traces.
Clicking the generated Trace ID will allow us to inspect the details of the trace.
Integrations
Sentry
To send span data directly to Sentry for analysis, replace the default span processor with Sentry’s implementation. This allows you to use Sentry as a backend for tracing and debugging.
Example (Configuring Sentry for Tracing)
1
import {
import NodeSdk
NodeSdk } from"@effect/opentelemetry"
2
import {
classSentrySpanProcessor
Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via
the Sentry SDK.