OpenTelemetry JavaScript can instrument Node.js applications without tying your code to one observability vendor. The Node SDK, API, and auto-instrumentation packages can create spans for supported frameworks such as Express, while OTLP exporters send telemetry to a Collector or compatible backend.
The critical setup rule is the same across modern Node instrumentation stacks: instrumentation must load before the application code it needs to instrument.
This tutorial uses a small Express app, current OpenTelemetry packages, and an OTLP/HTTP exporter pointed at a local Collector. It intentionally goes beyond console-only output so the result resembles a real telemetry path.
Last verified: September 10, 2026. OpenTelemetry JavaScript currently marks traces and metrics Stable and logs Development. It supports active or maintenance LTS Node.js versions. ESM loader behavior remains version-sensitive, so recheck the current Node.js getting-started page before using these commands later.
What You'll Accomplish
You will:
- create a minimal Express application;
- install the OpenTelemetry Node SDK and auto-instrumentation;
- configure OTLP trace export;
- load instrumentation before application code;
- generate HTTP requests;
- verify spans in a Collector; and
- troubleshoot startup order, ESM, endpoint, and conflicting preload options.
Prerequisites
You need:
- an active/maintenance Node.js LTS release;
- npm;
- a simple Express application;
- an OpenTelemetry Collector accepting OTLP/HTTP on
http://localhost:4318; - the Collector tutorial completed or an equivalent OTLP endpoint.
The official JavaScript page currently marks OpenTelemetry logs as Development. This tutorial therefore focuses on traces rather than presenting Node log instrumentation as equally mature.
Quick Answer
Install @opentelemetry/sdk-node, @opentelemetry/api, @opentelemetry/auto-instrumentations-node, and an OTLP trace exporter. Create instrumentation.mjs that initializes NodeSDK with getNodeAutoInstrumentations() and an OTLPTraceExporter targeting your Collector. Start the SDK, then launch the application with the instrumentation file loaded before app code—for example, the current docs use node --import ./instrumentation.mjs app.js for their JavaScript flow. Generate requests and verify the Collector receives HTTP server spans. If nothing arrives, inspect initialization order, ESM loader requirements, NODE_OPTIONS conflicts, protocol/endpoint mismatch, and Collector logs.
Step 1: Create a Small Express App
Initialize a project:
mkdir otel-node-demo
cd otel-node-demo
npm init -y
npm install express
Create app.js:
const express = require("express");
const app = express();
app.get("/hello", (req, res) => {
res.json({ message: "hello" });
});
app.listen(8080, () => {
console.log("Listening on http://localhost:8080");
});
Run it without instrumentation first:
node app.js
Verify:
curl http://localhost:8080/hello
Stop the process before adding telemetry.
Step 2: Install OpenTelemetry Packages
The current Node.js getting-started guide installs the SDK, API, auto-instrumentations, and SDK trace/metric packages.
For this trace-to-Collector path, install:
npm install \
@opentelemetry/sdk-node \
@opentelemetry/api \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
The OTLP exporter package is the key addition that replaces the official quick-start's ConsoleSpanExporter with a Collector destination.
Use your lockfile to keep dependency versions reproducible.
Step 3: Create the Instrumentation Bootstrap
Create instrumentation.mjs:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const traceExporter = new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces",
});
const sdk = new NodeSDK({
traceExporter,
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
This is intentionally small:
NodeSDKcoordinates the telemetry SDK;getNodeAutoInstrumentations()loads supported Node instrumentation libraries;OTLPTraceExportersends trace data over OTLP/HTTP.
For production, avoid hard-coding remote credentials. Use environment/deployment configuration and current OTLP exporter settings.
Step 4: Start Instrumentation Before the App
Current OpenTelemetry Node.js documentation says instrumentation setup must run before application code.
Start:
node --import ./instrumentation.mjs app.js
Then generate requests:
for i in 1 2 3 4 5; do
curl -s http://localhost:8080/hello > /dev/null
done
If you use TypeScript, the official docs currently show a tsx --import approach and note Node.js version requirements.
ESM caveat
The current Node.js documentation also calls out extra loader-hook requirements for applications compiled/written as ESM in some instrumentation paths.
Do not assume that one preload command works identically for CommonJS, ESM, TypeScript, and every Node version. Recheck the ESM support note for your runtime.
Step 5: Verify Spans in the Collector
If you use the Collector from the sibling tutorial, its debug exporter should print received spans.
Look for:
- HTTP server span;
- route/path-related attributes;
- service/process resource attributes;
- current timestamps;
- trace/span IDs.
The exact names and semantic-convention fields can evolve. Verify the structural outcome, not a single hard-coded field name.
If the Collector receives nothing, inspect both application and Collector logs.
Step 6: Configure the OTLP Endpoint with Environment Where Appropriate
OpenTelemetry's general OTLP configuration defines common endpoint variables such as:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
Language support for each environment variable can differ, so check the current compliance/documentation for JavaScript before replacing explicit exporter configuration.
For a production deployment, environment configuration is often preferable because the same build artifact can point at different Collector endpoints by environment.
Do not put bearer tokens or backend credentials directly into the source file.
Step 7: Add Manual Spans Only for Business-Specific Work
Auto-instrumentation is a baseline, not a complete application model.
Use manual spans when you need visibility around custom work that frameworks cannot infer, for example:
- report generation;
- custom queue processing;
- expensive transformations;
- domain-specific validation.
Do not wrap every function. Excessive custom spans increase telemetry volume and can make traces harder to read.
The exact manual tracing API should be taken from the current OpenTelemetry JS API documentation when you implement it; this tutorial's core success criterion is verified auto-instrumentation plus OTLP export.
Step 8: Shut Down Gracefully in Short-Lived Processes
A long-running web server naturally gives exporters time to flush batches. Short-lived scripts and tests can exit before telemetry leaves the process.
For scripts, workers, or test runners, use the current SDK shutdown guidance rather than arbitrary sleeps.
This matters less for the Express server demo but becomes important when you reuse the instrumentation bootstrap elsewhere.
Verify the Setup
Your Node.js instrumentation is ready when:
- the application still works normally;
- instrumentation starts before app code;
- several HTTP requests generate spans;
- the Collector receives those spans over the configured OTLP protocol;
- timestamps and service/process attributes are plausible;
- no conflicting preload options exist;
- your runtime/module format matches current documented support.
A console exporter alone is useful for learning, but this tutorial's completion criterion is a Collector receiving the spans.
Common Problems and Fixes
Problem: App works but no spans appear
Cause: instrumentation loaded after Express, exporter endpoint is wrong, SDK failed to initialize, or requests were sent to another process.
Fix: verify the startup command, enable OpenTelemetry diagnostic logging, and inspect Collector receiver logs.
Problem: OTLP HTTP request fails
Cause: exporter points to the wrong URL/protocol. OTLP/HTTP commonly uses port 4318 and signal paths such as /v1/traces.
Fix: match the exporter to the Collector receiver and verify the host/port from the application's network namespace.
Problem: localhost fails in containers
Cause: inside an application container, localhost refers to that container, not necessarily the Collector.
Fix: use the Collector service name or appropriate network address.
Problem: ESM app has missing spans
Cause: loader-hook behavior is version-sensitive.
Fix: follow the current OpenTelemetry ESM support instructions for the Node version and transpilation mode.
Problem: Conflicting NODE_OPTIONS
Cause: another --require or --import already registers OpenTelemetry or a competing instrumentation bootstrap.
Fix: current OpenTelemetry docs explicitly warn about conflicting preload flags. Keep one clear initialization path.
Problem: Duplicate spans
Cause: the same library is instrumented twice or two telemetry SDKs both own instrumentation.
Fix: inventory automatic/manual instrumentation and remove overlap.
Best Practices
Use an LTS Node runtime
OpenTelemetry JavaScript supports active or maintenance LTS versions. Align runtime upgrades with instrumentation testing.
Initialize once, before application code
Make the instrumentation bootstrap part of the service startup contract.
Export through a Collector for production architectures
A Collector decouples application instrumentation from backend-specific transport and provides a place for processing/routing.
Keep service identity stable
Add resource/service attributes deliberately so the same service is recognizable across deployments.
Protect attributes
Do not put secrets, tokens, or unbounded sensitive user data in span attributes.
Watch signal maturity
As of verification, JS traces and metrics are Stable while logs are Development. Recheck before designing a production logging strategy around the JS SDK.
When This Setup Makes Sense
OpenTelemetry Node.js is a strong fit when:
- you want vendor-neutral instrumentation;
- traces may be routed to different backends;
- your organization standardizes on OTLP and Collector pipelines;
- supported libraries can be auto-instrumented.
A backend-native agent can be simpler if your organization is committed to one vendor and values its product-specific automatic setup.
FAQ
Does OpenTelemetry auto-instrument Express?
The current @opentelemetry/auto-instrumentations-node package includes instrumentation libraries for supported Node modules, and the official getting-started guide uses it to generate Express spans.
Why must instrumentation load before app code?
Instrumentation needs to patch supported modules as they load. If Express is already loaded, expected automatic hooks may be missed.
Should Node.js export directly to a backend?
It can, but a Collector is often useful for routing, processing, credentials, and backend decoupling. This tutorial intentionally exports to a Collector.
Are OpenTelemetry JavaScript logs stable?
Not according to the current status page: traces and metrics are Stable; logs are Development as of September 10, 2026.
Conclusion
A dependable Node.js OpenTelemetry setup has a clear bootstrap and a verifiable export path. Load instrumentation first, use auto-instrumentation for supported libraries, send traces over OTLP to a Collector, and prove those spans arrive.
Do not hide ESM/version limitations or present console output as a complete production architecture.
References
- OpenTelemetry Documentation — "Node.js" — https://opentelemetry.io/docs/languages/js/getting-started/nodejs/ — accessed September 10, 2026.
- OpenTelemetry Documentation — "JavaScript" — https://opentelemetry.io/docs/languages/js/ — accessed September 10, 2026.
- OpenTelemetry Documentation — "Exporters" — https://opentelemetry.io/docs/languages/js/exporters/ — accessed September 10, 2026.
- OpenTelemetry Documentation — "OTLP Exporter Configuration" — https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/ — accessed September 10, 2026.
- OpenTelemetry Documentation — "Configuration" — https://opentelemetry.io/docs/collector/configuration/ — accessed September 10, 2026.