Skip to content
Monitoring
Skill

/maple-effect-style

Effect-TS OpenTelemetry style for Maple via @maple-dev/effect-sdk: Maple.layer() bootstrap, Effect.withSpan / Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server / browser / Cloudflare entry points.

BOOST
From plugin
maple
1.8k37 skills
Install
$ npx -y skills add mapletechlabs/maple --skill maple-effect-style --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/maple-effect-style

Context preview

The summary Claude sees to decide when to auto-load this skill.

Effect-TS OpenTelemetry style for Maple via @maple-dev/effect-sdk: Maple.layer() bootstrap, Effect.withSpan / Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server / browser / Cloudflare entry points.

SKILL.md

maple-effect-style.SKILL.md
name: maple-effect-style
description: "Effect-TS OpenTelemetry style for Maple via @maple-dev/effect-sdk: Maple.layer() bootstrap, Effect.withSpan / Effect.annotateCurrentSpan call sites, Effect.log for trace-correlated logging, server / browser / Cloudflare entry points."

Maple Effect style

For Effect apps, use `@maple-dev/effect-sdk`. It wraps Effect's built-in `Otlp.layerJson` exporter and handles batching, shutdown, and resource attributes.

Install

npm install @maple-dev/effect-sdk effect

The current release requires Effect 4 (`effect >= 4.0.0-rc.113`). For Effect 3, install `@maple-dev/effect-sdk@effect-v3 effect @effect/platform @effect/opentelemetry`. The API and import paths are the same.

Bootstrap

Pick the entry point per runtime. Each has different lifecycle requirements:

  • **Server (Node.js, Bun, Deno):** background-export fiber, env-var auto-detection, graceful shutdown.
  • **Browser:** explicit config (no env vars), browser metadata baked in.
  • **Cloudflare Workers:** manual `flush()` in `ctx.waitUntil`, lazy env resolution, in-isolate buffering.

Server

import { Maple } from "@maple-dev/effect-sdk"
import { Effect } from "effect"

const TracerLive = Maple.layer({
	serviceName: "orders-api",
	endpoint: "https://ingest.maple.dev", // EU: https://ingest.eu.maple.dev
	ingestKey: "MAPLE_TEST", // public ingest key (maple_pk_…), or MAPLE_TEST until the user has one
	repositoryUrl: "https://github.com/acme/orders-api",
})

const program = Effect.gen(function* () {
	yield* Effect.log("Order received")
}).pipe(Effect.withSpan("order.submit"))

Effect.runPromise(program.pipe(Effect.provide(TracerLive)))

The default import resolves to the server build under Node.js. Import `@maple-dev/effect-sdk/server` explicitly when needed.

If `endpoint` is omitted, the server layer reads `MAPLE_ENDPOINT`, then `OTEL_EXPORTER_OTLP_ENDPOINT`, then falls back to the public ingest for the region (`https://ingest.maple.dev`, or `https://ingest.eu.maple.dev` with `region: "eu"` / `MAPLE_REGION=eu`). The key falls back to `MAPLE_INGEST_KEY`. `Maple.layer` always exports. A missing ingest key does not disable it, so keyless local-mode and self-hosted-collector setups keep working. Inline the key when telemetry must flow regardless of env (the maple-onboard inline-key pattern). `MapleFlush.make` and the Cloudflare `make()` differ: they no-op without a key.

The server layer also auto-fills `vcs.ref.head.revision` from `COMMIT_SHA` / `RAILWAY_GIT_COMMIT_SHA` / `VERCEL_GIT_COMMIT_SHA` / `CF_PAGES_COMMIT_SHA` / `RENDER_GIT_COMMIT` (first match wins). For `vcs.repository.url.full`, use the `repositoryUrl` option or `MAPLE_REPOSITORY_URL`. Do not hand-write the attribute. The layer also dual-emits `deployment.environment` and `deployment.environment.name` from the `environment` option or `MAPLE_ENVIRONMENT`.

Cloudflare Workers

The Cloudflare entry point exports `make()`, not a `Maple` namespace. Build the telemetry object **once at module scope**. It buffers in-isolate and resolves `env` lazily on the first flush:

import * as MapleCloudflareSDK from "@maple-dev/effect-sdk/cloudflare"
import { Effect } from "effect"

const telemetry = MapleCloudflareSDK.make({
	serviceName: "orders-edge",
	endpoint: "https://ingest.maple.dev", // EU: https://ingest.eu.maple.dev
	ingestKey: "MAPLE_TEST",
})

export default {
	async fetch(req: Request, env: Env, ctx: ExecutionContext) {
		const program = Effect.gen(function* () {
			yield* Effect.log("edge request")
			return new Response("ok")
		}).pipe(Effect.withSpan("edge.handle"))

		const response = await Effect.runPromise(program.pipe(Effect.provide(telemetry.layer)))
		ctx.waitUntil(telemetry.flush(env))
		return response
	},
}

Call `ctx.waitUntil(telemetry.flush(env))` on every request so telemetry survives the isolate exit. `flush` takes `env`. A missing `waitUntil` is the most common reason Worker traces never arrive. When routes go through `HttpRouter.toWebHandler`, provide `telemetry.layer` to the layer you pass it (`Layer.provideMerge(telemetry.layer)`), not to a separate per-request runtime.

Browser

import { Maple } from "@maple-dev/effect-sdk/client"

const TracerLive = Maple.layer({
	serviceName: "web-client",
	endpoint: "https://ingest.maple.dev", // EU: https://ingest.eu.maple.dev
	ingestKey: "MAPLE_TEST",
})

The browser entry point has no env-var fallback. Pass all config explicitly. An explicit `endpoint` wins over `region`, so EU organizations change the endpoint itself (or drop `endpoint` and set `region: "eu"`). It records session replays by default; opt out with `replay: { enabled: false }`.

Custom spans

Use `Effect.withSpan` to trace operations and `Effect.annotateCurrentSpan` for attributes. Do not reach for the raw `@opentelemetry/api` tracer when an Effect-native primitive exists.

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

Maple's service map draws a service-to-service edge by joining a Client span to the downstream service's child Server span. `peer.service` does not draw edges. Make outgoing calls through Effect's `HttpClient`: it creates the Client span and injects `traceparent` by default. Calls to uninstrumented dependencies appear as external nodes keyed on `server.address`.

`Effect.fail` and uncaught defects end the span with status `Error` and an `exception` event. Do not wrap with `try` / `catch` / `finally`.

In Effect code, use the Effect-native span primitives, not the raw `@opentelemetry/api` tracer.

Logs

`Effect.log` inside a span carries the trace context. No extra setup is needed:

const program = Effect.gen(function* () {
	yield* Effect.log("Processing started")
	yield* doWork()
	yield* Effect.log("Processing complete")
}).pipe(Effect.w
Read more
Ships withmaple

OpenTelemetry observability platform

Get the whole plugin

Other skills on maple.