Skip to content
Agentmart
Submit a tool

effect-observability

Effect 4 logging, tracing and metrics, and their export.

Skill by andabove

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/andabove-effect-observability

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Effect 4 logging, tracing and metrics, and their export. Use when adding Effect.log calls or log annotations, choosing or replacing a logger, routing Effect logs into an existing structured logger, adding spans with Effect.fn or Effect.withSpan, recording metrics, or exporting telemetry with the effect/observability OTLP modules or an existing OpenTelemetry SDK.

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/effect-observability

What's inside

Skill name
effect-observability
Description
Effect 4 logging, tracing and metrics, and their export. Use when adding Effect.log calls or log annotations, choosing or replacing a logger, routing Effect logs into an existing structured logger, adding spans with Effect.fn or Effect.withSpan, recording metrics, or exporting telemetry with the effect/observability OTLP modules or an existing OpenTelemetry SDK.
License
MIT
Instructions
194 lines in SKILL.md · view on GitHub

Files (5)

  • SKILL.md
  • effect-observability-export.md · references/
  • effect-observability-loggers.md · references/
  • effect-observability-metrics.md · references/
  • effect-observability-tracing.md · references/

SKILL.md (first lines)

# Effect observability

Checked against `effect 4.0.0` and `@effect/opentelemetry 4.0.0`. When the project has a newer version, read `node_modules/effect/src/Logger.ts`, `Tracer.ts`, `Metric.ts`, `References.ts` and `node_modules/effect/src/observability/`, and `node_modules/effect/AGENTS.md`, before you trust a name here.

Import each module from its own subpath, as every example here does: `import * as Logger from "effect/Logger"`, `import * as Otlp from "effect/observability/Otlp"`, `import * as OtelTracer from "@effect/opentelemetry/OtelTracer"`.

Instrument with Effect APIs in every module. Choose where the telemetry goes once, at the entry point, with one layer. The signals share context: a log written inside a span becomes an event on that span, and a logger can read the current trace and span ids.

## Logging

```ts
import * as Effect from "effect/Effect"

declare const chargeCard: (orderId: string, cents: number) => Effect.Effect<string, "CardDeclined">

export const placeOrder = Effect.fn("Orders.place")(
  function*(orderId: string, cents: number) {
    yield* Effect.logDebug("pricing order")
    const receipt = yield* chargeCard(orderId, cents).pipe(
      Effect.tapError((error) => Effect.logWarning("charge refused").pipe(Effect.annotateLogs({ error })))
    )
    yield* Effect.logInfo("order placed").pipe(Effect.annotateLogs({ receipt, cents }))
    return receipt
  },
  // trailing arguments receive the effect and the call arguments
  (effect, orderId) => Effect.annotateLogs(effect, { orderId })
)
```

- Log with `Effect.logTrace`, `logDebug`, `logInfo` (same as `Effect.log`), `logWarning`, `logError`, `logFatal`. Use `console.log` only outside Effect code; it skips levels, annotations, spans and the configured logger.
- The minimum level is `"Info"`, so `logDebug` and `logTrace` print nothing by default. Set it once at the edge: `Layer.succeed(References.MinimumLogLevel, "Debug")`, or from config with `Config.LogLevel("LOG_LEVEL")` (values `"All"`, `"Trace"`, `"Debug"`, `"Info"`, `"Warn"`, `"Error"`, `"Fatal"`, `"None"`, case-sensitive). `"None"` silences all logs.
- Write a fixed event text as the only message, and put the variable data in `Effect.annotateLogs`. Extra arguments are kept as values in the message array, and a span event built from several arguments is named after their JSON.
- `Effect.annotateLogs(key, value)` or `Effect.annotateLogs({ ... })` reaches every log inside the effect, nested calls included. Annotate request id, tenant and user once at the boundary. `Effect.annotateLogsScoped({ ... })` annotates the rest of the current `Scope`.
- Pass a `Cause` as an argument to attach the failure: `Effect.catchCause((cause) => Effect.logError("import failed", cause))`. Loggers receive it as `cause`, not in the message.
- `Effect.withLogSpan("checkout")` adds `checkout=<ms>` to each log inside: a duration label, not a trace span.
- `Redacted` values print as `<redacted>` in every built-in logger.

## Loggers

A logger receives every entry at or above the minimum level. The default set holds `Logger.defaultLogger` (one human-readable line on stdout: `[12:00:00.000] INFO (#1) checkout=3ms: order placed { orderId: 'o-1' }`) and `Logger.tracerLogger` (adds each log as an event on the current span).

| Need | Layer |
| --- | --- |
| JSON lines for a log pipeline | `Logger.layer([Logger.consoleJson, Logger.tracerLogger])` |
| Colors for local development | `Logger.layer([Logger.consolePretty(), Logger.tracerLogger])` |
| logfmt | `Logger.layer([Logger.consoleLogFmt, Logger.tracerLogger])` |
| Add a logger, keep the current ones | `Logger.layer([myLogger], { mergeWithExisting: true })` |
| JSON lines on stderr | `Logger.layer([Logger.withConsoleError(Logger.formatJson), Logger.tracerLogger])` (`References.LogToStderr` moves only `defaultLogger` and `consolePretty`) |

- `Logger.layer([...])` replaces the whole set. Keep `Logger.tracerLogger` in the list, or logs stop appearing as span events.
- Provide one logger layer at the entry point. A second `Logger.layer` deeper in the program replaces the first for that part only.
- Never put two layers that set loggers (`Logger.layer`, the OTLP logger in `Otlp.layer*`, `OtelLogger.layer`) side by side in `Layer.mergeAll`. Each builds its set from the loggers current when it is built, so one silently drops the other. Nest them instead: `ObservabilityLive.pipe(Layer.provideMerge(LoggingLive))` keeps both.
- In tests, capture entries with `Logger.make` (see [the loggers reference](references/effect-observability-loggers.md)).

## A project with a structured logger already

When the project already ships a logger such as pino or winston, with its transports, redaction and log shipping, keep it as the one sink. Replace Effect's default logger with a bridge that maps level, message, annotations, log spans, cause and trace ids onto the project logger's fields:

```ts
import * as Cause from "effect/Cause"
import * as Logger from "effect/Logger"
import type * as LogLevel from "effect/LogLevel"
import * as Predicate from "effect/Predicate"
import * as References from "effect/References"

type LogFn = (fields: Record<string, unknown>, message: string) => void
// the project's existing logger, pino-style: logger.info(fields, message)
declare const appLogger: Record<"trace" | "debug" | "info" | "warn" | "error" | "fatal", LogFn>

const methods: Record<LogLevel.LogLevel, keyof typeof appLogger> = {
  All: "trace", Trace: "trace", Debug: "debug", Info: "info", Warn: "warn", Error: "error", Fatal: "fatal", None: "info"
}

const appLoggerBridge = Logger.make(({ cause, date, fiber, logLevel, message }) => {
  const parts: Array<unknown> = Array.isArray(message) ? [...message] : [message]
  const text = typeof parts[0] === "string" ? String(parts.shift()) : ""
  const fields: Record<string, unknown> = { ...fiber.getRef(References.CurrentLogAnnotations) }
  const args: Array<unknown> = []
  for (const value of parts) {
    if (value instanceof Error) fields["err"] = value
    else if (Predicate.isObject(value)) Object.assign(fields, value)
    else args.push(value)
  }
  if (args.length > 0) fields["args"] = args
  for (const [label, start] of fiber.getRef(References.CurrentLogSpans)) fields[`${label}_ms`] = date.getTime() - start
  const span = fiber.cache.span
  if (span !== undefined) Object.assign(fields, { trace_id: span.traceId, span_id: span.spanId })
  if (cause.reasons.length > 0) fields["err"] = Cause.squash(cause)
  appLogger[methods[logLevel]](fields, text)
})

export const LoggingLive = Logger.layer([appLoggerBridge, Logger.tracerLogger])
```

- Combine it with an OTLP or OpenTelemetry logger by nesting, not `Layer.mergeAll` (see [Loggers](#loggers)).
- Both sides filter by level. Set `References.MinimumLogLevel` to match the project logger's level, or `logDebug` calls are dropped before they reach it.
- Non-Effect code keeps calling the project logger directly. Effect code calls `Effect.log*` only, so annotations and spans apply.
- When the codebase is mostly Effect and has no logger to keep, skip the bridge and use `Logger.consoleJson`.

## Tracing

```ts
import * as Effect from "effect/Effect"

declare const insertOrder: (orderId: string) => Effect.Effect<void>

export const createOrder = Effect.fn("Orders.create", { attributes: { "order.source": "web" } })(
  function*(orderId: string, lines: number) {
    yield* Effect.annotateCurrentSpan({ "order.id": orderId, "order.lines": lines })
    yield* insertOrder(orderId).pipe(Effect.withSpan("Orders.insert", { kind: "client" }))
  }
)

export const handleRequest = createOrder("o-1", 3).pipe(
  Effect.withSpan("POST /orders", { kind: "server", attributes: { "http.route": "/orders" } })
)
```

- Write every service method and exported effectful function as `Effect.fn("Service.method")(function*() { ... })`. The name becomes the span name and a stack frame. Name it after the function.

From andabove/skills, MIT.

More from andabove/skills

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Oct 3, 2026 (26 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from andabove

Cite this page

effect-observability skill. Agent Mart. https://agentmart.dev/skills/andabove-effect-observability (last updated 2026-10-24)