Exporters and OTLP: getting spans to Jaeger or Langfuse

2 min read

Short answer

An exporter sends finished spans from the SDK to a backend. OTLP is OpenTelemetry's own protocol, over HTTP or gRPC, and Jaeger, Langfuse and most tracing backends accept it. You pick the destination with OTEL_EXPORTER_OTLP_ENDPOINT. Because exporters send in batches, a short-lived process has to shut the SDK down, or its last spans never leave.

On this page

In the demo trace, the slowest span is the reverted confirm: 264.9 ms, against 11.8 ms for the successful one. It also fetches the revert reason. In a short script, it’s the span most likely to miss the backend.

The Node.js OTLP export path from span.end() through a BatchSpanProcessor and an OTLP exporter to Jaeger or Langfuse, and why a short script must flush and shut down before it exits.

What does an exporter do?

It takes finished spans from a span processor and sends them somewhere. The usual pair in Node.js:

Piece Job
BatchSpanProcessor Collects finished spans, exports them in batches on a timer
OTLPTraceExporter Encodes them as OTLP and sends them over HTTP or gRPC
OTEL_EXPORTER_OTLP_ENDPOINT Where they go
OTEL_EXPORTER_OTLP_HEADERS Auth, if the backend needs it

Node.js has three OTLP trace exporter packages: exporter-trace-otlp-proto (HTTP with protobuf), -http (HTTP with JSON) and -grpc. The hashspan demo uses -proto with a batch processor, and switches to the console exporter when OTEL_TRACES_EXPORTER=console is set.

Jaeger or Langfuse?

Both speak OTLP over HTTP, so only the variables change:

Terminal window
# Jaeger, local
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Langfuse Cloud (EU)
OTEL_EXPORTER_OTLP_ENDPOINT=https://cloud.langfuse.com/api/public/otel
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <base64 of public-key:secret-key>"

The Langfuse values are from its OpenTelemetry docs. Langfuse doesn’t take gRPC yet, so use the -proto or -http exporter. In both cases the exporter adds /v1/traces to the base URL.

Why do spans vanish at exit?

The batch processor waits before it exports, and a script can end before it does. Here it is, run twice against a local Jaeger (@opentelemetry/sdk-trace-node@2.11.0, exporter-trace-otlp-proto@0.222.0), once without SHUTDOWN and once with it:

import { trace } from '@opentelemetry/api';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
const provider = new NodeTracerProvider({
resource: resourceFromAttributes({ 'service.name': process.env.SVC ?? 'learn-otlp' }),
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())], // reads OTEL_EXPORTER_OTLP_ENDPOINT
});
provider.register();
trace.getTracer('learn-demo').startSpan('send 8453').end();
if (process.env.SHUTDOWN) await provider.shutdown(); // exports what the batch still holds
Run Jaeger
No shutdown “No traces found”
await provider.shutdown() The span is there

No error either way. The span just isn’t there.

FAQ

How do I configure the OTLP exporter in Node.js?

Create an OTLPTraceExporter from @opentelemetry/exporter-trace-otlp-proto or -http, put it in a BatchSpanProcessor, and set OTEL_EXPORTER_OTLP_ENDPOINT. For HTTP, the exporter appends /v1/traces to that base URL.

Why are my spans missing when my Node.js script exits?

The BatchSpanProcessor holds spans and exports them on a timer. If the process exits first, they're lost. Await provider.shutdown() before exiting, which exports what's left.

Does Langfuse accept OTLP?

Yes, over HTTP with JSON or protobuf, at /api/public/otel with Basic auth from your public and secret key. gRPC isn't supported.

Go further

Type to search the docs, Learn topics and the blog.