1.7K App
Effect SDK / Quick start

Quick start

OpenTelemetry traces, logs, and metrics for Effect applications across Node.js, Bun, Deno, browsers, and Cloudflare Workers.

@maple-dev/effect-sdk provides a pre-configured Effect Layer that sets up OpenTelemetry traces, logs, and metrics for Maple. It wraps Effect’s built-in Otlp.layerJson exporter, fills in resource attributes from the runtime, and defaults the endpoint to the public Maple ingest — so in most cases an ingest key is the only configuration you need. Point MAPLE_ENDPOINT at a local maple start sink or your own collector to run the same code against a different destination.

Node.js Bun Deno Browsers Cloudflare Workers

Install

Effect 4+

npm install @maple-dev/effect-sdk effect

Effect 3

npm install @maple-dev/effect-sdk@effect-v3 effect @effect/platform @effect/opentelemetry

The API and import paths are identical between versions. The only differences are the install command and that duration config types use Duration.DurationInput instead of Duration.Input in Effect 3.

Pick your platform

The SDK ships three platform-specific entry points. Each one has its own setup story:

  • Server — Node.js, Bun, Deno. Background-export fiber, env-var auto-detection, graceful shutdown.
  • Browser — single-page apps. Explicit config (no env vars), browser metadata baked into resource attributes.
  • Cloudflare Workers — short-lived isolates. Manual flush() in ctx.waitUntil, lazy env resolution, in-isolate buffering.

Custom Spans

Use Effect.withSpan to trace operations. Add attributes with Effect.annotateCurrentSpan:

import { Effect } from "effect"

const processOrder = (orderId: string) =>
	Effect.gen(function* () {
		yield* Effect.annotateCurrentSpan("order.id", orderId)
		yield* Effect.annotateCurrentSpan("peer.service", "payment-api")
		const result = yield* chargePayment(orderId)
		return result
	}).pipe(Effect.withSpan("process-order"))

Setting peer.service on outgoing calls makes them visible on Maple’s service map.

Log Correlation

Effect.log automatically includes trace context when called inside a span — no additional setup needed:

const program = Effect.gen(function* () {
	yield* Effect.log("Processing started")
	yield* doWork()
	yield* Effect.log("Processing complete")
}).pipe(Effect.withSpan("process"))

Logs emitted inside spans are correlated with the active trace in the Maple dashboard.

Configuration Reference

All options for Maple.layer() (server and browser entry points). The Cloudflare entry point accepts a slightly different shape — see the Cloudflare page for its config table.

OptionTypeRequiredDescription
serviceNamestringYesService name reported in traces, logs, and metrics
endpointstringNo (server) / Yes (client)Maple ingest endpoint URL. Server auto-detects from MAPLE_ENDPOINT
ingestKeystringRequired by the public ingestMaple ingest key. Server auto-detects from MAPLE_INGEST_KEY. The flushable and Cloudflare presets no-op without one
serviceVersionstringNoOverride auto-detected commit SHA
serviceNamespacestringNoLogical group, emitted as the service.namespace resource attribute
repositoryUrlstringNo (server / Cloudflare)Repository URL, emitted as vcs.repository.url.full
environmentstringNoOverride auto-detected deployment environment
attributesRecord<string, unknown>NoAdditional resource attributes merged into telemetry
maxBatchSizenumberNoMax telemetry items per export batch
loggerExportIntervalDuration.InputNoExport interval for logs
metricsExportIntervalDuration.InputNoExport interval for metrics
tracerExportIntervalDuration.InputNoExport interval for traces
shutdownTimeoutDuration.InputNoGraceful shutdown timeout

In Effect 3, duration fields use the Duration.DurationInput type instead of Duration.Input.

Browser-only options — replay (built-in rrweb session replay, on by default) and emitSessionMeta — are documented in Session Replay & Sessions on the Browser page.