Skip to content
Night Owls.dev
Jump to a page

@nightowlsdev/telemetry-langfuse

Telemetry

Langfuse v5 (OTel-native) telemetry exporter for @nightowlsdev/core swarm runs.

What it does

A thin adapter over @nightowlsdev/telemetry-core that forwards a swarm run's spans through Langfuse's OTel-native LangfuseSpanProcessor (@langfuse/otel). Because telemetry-core emits gen_ai.* attributes and Langfuse's default filter forwards any span carrying them, each model call lands in Langfuse as a generation with its model, token usage, and cost. Call langfuseTelemetry({ publicKey, secretKey, baseUrl?, exportMode?, environment?, release? }) and pass the result to defineSwarm({ telemetry }); exportMode defaults to 'immediate' (flushes per run, serverless-safe), use 'batched' for long-running hosts. It also exports nightOwlsPlugin, the declarative CLI plugin manifest consumed by @nightowlsdev/cli. Telemetry is best-effort, so a throwing exporter never breaks the run.

Install

pnpm add @nightowlsdev/telemetry-langfuse

Key exports

  • langfuseTelemetry
  • nightOwlsPlugin
  • LangfuseTelemetryOpts (type)

Usage

telemetry-langfuse.ts
import { defineSwarm } from "@nightowlsdev/core";
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";

const telemetry = langfuseTelemetry({
  publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
  secretKey: process.env.LANGFUSE_SECRET_KEY!,
});

const swarm = defineSwarm({ agents, telemetry });

What it provides

A Langfuse v5 (OpenTelemetry-native) telemetry exporter for swarm runs. Call langfuseTelemetry({ publicKey, secretKey }) and hand the result to defineSwarm({ telemetry }); each model call lands in Langfuse as a generation with its model, token usage, and cost, and the whole run shows up as a trace. It is a thin shell over @nightowlsdev/telemetry-core — it differs only in passing Langfuse's LangfuseSpanProcessor (@langfuse/otel) as the span sink.

When to use it

  • You want purpose-built LLM observability — a per-generation view of model, prompt/response, token usage, and cost, plus Langfuse's traces, sessions, evals, and prompt-management product.
  • You run Langfuse (Cloud EU/US or self-hosted) and want swarm runs to appear natively without wiring OTLP yourself.
  • You deploy serverless / per-request — exportMode 'immediate' (the default here) flushes on each run's export so nothing is stranded when the function freezes.
  • You want env-driven setup — owl install telemetry-langfuse wires it from LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_BASEURL and composes with any other telemetry adapter.

When not to

  • You already run an APM / distributed-tracing stack (Datadog, Honeycomb, Grafana Tempo, Jaeger) and want swarm spans unified with your HTTP/db/queue traces — use @nightowlsdev/telemetry-otel (or run both).
  • You're pinned to the old Langfuse v3 SDK — that langfuse.trace() / .generation() API is gone; this package targets Langfuse v5's OTel-native LangfuseSpanProcessor only.
  • You have no Langfuse deployment and don't want to stand one up — an OTLP backend or core's customTelemetry may be lower-lift.

Alternatives

  • @nightowlsdev/telemetry-otelYou want vendor-neutral OTLP into your existing observability backend rather than an LLM-specialized SaaS, or you need swarm spans correlated with the rest of your system's traces.
  • Both at once (compositeTelemetry / a telemetry array)You want the Langfuse generation view for the AI team AND OTLP into the org's APM for infra correlation — send the same span batch to both.
  • core's customTelemetry(fn)You want the raw SwarmSpan[] to persist or forward yourself, with no Langfuse account and no OpenTelemetry pipeline.

Strengths

  • Purpose-built LLM view — model generations are first-class, with model, tokens, and cost surfaced from the gen_ai.* attributes telemetry-core emits.
  • Two-line wiring, or one CLI command (owl install telemetry-langfuse) that merges the env vars and inserts the config.
  • exportMode 'immediate' by default — serverless-safe (flushes per run); switch to 'batched' for a long-running host.
  • Best-effort: the engine exports in a finally and swallows errors, so a throwing or hung Langfuse export never breaks the run.
  • @mastra-free (the engine wall) — the built .d.ts has zero @mastra references.
  • Composes cleanly — pass an array of exporters or compositeTelemetry to also ship to an OTLP backend.

Limits & trade-offs

  • Langfuse v5 only. If you depend on the removed v3 client API, this won't fit.
  • Requires a Langfuse deployment (Cloud account or self-hosted) plus public/secret keys — the secret key is server-side only.
  • LLM-specialized, not a general APM — you won't see your HTTP/db/infra spans here; use OTel for that (or run both).
  • The LangfuseSpanProcessor's default filter is gen_ai.*-centric, so model generations are the first-class citizens; a run/tool span without gen_ai.* attributes is not the focus of the default view.
  • Node-only, inherited from telemetry-core's node:crypto trace-id derivation.

How it works

langfuseTelemetry constructs a @langfuse/otel LangfuseSpanProcessor from your publicKey / secretKey / baseUrl / exportMode and hands it to telemetry-core's replayerExporter. When core's engine finishes a run it calls that exporter with the batch; the replayer builds real OTel spans (run + generations + tools) carrying gen_ai.* attributes and pushes them through the processor, which uploads to Langfuse. Because Langfuse's default filter forwards any span carrying gen_ai.* attributes, each model call lands as a Langfuse generation with model, token usage, and cost. exportMode 'immediate' flushes on each export (serverless); 'batched' defers for throughput on a long-running host.

Examples

Wire Langfuse into a swarm

The default exportMode 'immediate' flushes per run — safe under serverless.

telemetry-langfuse-example-1.ts
import { defineSwarm } from "@nightowlsdev/core";
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";

const telemetry = langfuseTelemetry({
  publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
  secretKey: process.env.LANGFUSE_SECRET_KEY!,
});

const swarm = defineSwarm({ agents, telemetry });

Batched mode with environment + release tags

On a long-running host, 'batched' trades per-run flushing for throughput; environment/release tag the trace.

telemetry-langfuse-example-2.ts
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";

const telemetry = langfuseTelemetry({
  publicKey: process.env.LANGFUSE_PUBLIC_KEY!,
  secretKey: process.env.LANGFUSE_SECRET_KEY!,
  baseUrl: "https://cloud.langfuse.com",
  exportMode: "batched",
  environment: "prod",
  release: "1.4.0",
});

Send to Langfuse and an OTLP backend at once

compositeTelemetry isolates each exporter (Promise.allSettled) — one throwing backend never blocks the other or the run.

telemetry-langfuse-example-3.ts
import { defineSwarm, compositeTelemetry } from "@nightowlsdev/core";
import { langfuseTelemetry } from "@nightowlsdev/telemetry-langfuse";
import { otelTelemetry } from "@nightowlsdev/telemetry-otel";

const swarm = defineSwarm({
  agents,
  telemetry: compositeTelemetry([
    langfuseTelemetry({ publicKey, secretKey }),
    otelTelemetry({ url, headers }),
  ]),
});

Related

  • telemetry-otelThe other exporter — pick it when you want vendor-neutral OTLP into an APM instead of (or alongside) Langfuse.
  • telemetry-coreThe shared replay engine this wraps — and where the aisdkTelemetry helper lives for raw, non-swarm calls.
  • coreProvides defineSwarm({ telemetry }), compositeTelemetry, and the SwarmSpan batch this exports.