Instrumenting a JavaScript App with OpenTelemetry
OpenTelemetry (OTel) is an open-source observability framework that provides a set of APIs, SDKs, and tools for collecting telemetry data, such as metrics, logs, and traces, from applications. With OpenTelemetry, developers can obtain valuable insights into how their services perform in production or during local development.
A key component of OpenTelemetry is the OpenTelemetry Protocol (OTLP) a general-purpose, vendor-agnostic protocol designed to transmit telemetry data efficiently and reliably. OTLP supports multiple data types (traces, metrics, logs) over HTTP or gRPC, making it the default and recommended protocol for communication between instrumented applications, the OpenTelemetry Collector, and backends such as Jaeger or Prometheus.
This guide walks you through how to instrument a simple Node.js application with OpenTelemetry and run both the app and a collector using Docker. This setup is ideal for local development and testing observability before integrating with external observability platforms like Prometheus, Jaeger, or Grafana.
In this guide, you'll learn how to:
- How to set up OpenTelemetry in a Node.js app.
- How to run an OpenTelemetry Collector in Docker.
- How to visualize traces with Jaeger.
- How to use Docker Compose to manage the full observability stack.
Using OpenTelemetry with Docker
The Docker Official Image for OpenTelemetry provides a convenient way to deploy and manage Dex instances. OpenTelemetry is available for various CPU architectures, including amd64, armv7, and arm64, ensuring compatibility with different devices and platforms. Same for the Jaeger docekr image.
Prerequisites
Docker Compose: Recommended for managing multi-container Docker applications.
Basic knowledge of Node.js and Docker.
Project Structure
Create the project directory:
mkdir otel-js-app
cd otel-js-app
otel-js-app/
├── docker-compose.yaml
├── collector-config.yaml
├── app/
│ ├── package.json
│ ├── app.js
│ └── tracer.js
Create a Simple Node.js App
Initialize a basic Node.js app:
mkdir app && cd app
npm init -y
npm install express @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
Now, add the application logic:
// app/app.js
const express = require('express');
require('./tracer'); // Initialize OpenTelemetry
const app = express();
app.get('/', (req, res) => {
res.send('Hello from OpenTelemetry demo app!');
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`App listening at http://localhost:${PORT}`);
});
Configure OpenTelemetry Tracing
Create the tracer configuration file:
// app/tracer.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: 'http://collector:4318/v1/traces',
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Configure the OpenTelemetry Collector
Create a collector-config.yaml file at the root:
# collector-config.yaml
receivers:
otlp:
protocols:
http:
exporters:
logging:
loglevel: debug
jaeger:
endpoint: jaeger:14250
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
exporters: [logging, jaeger]
Add Docker Compose Configuration
Create the docker-compose.yaml
file:
version: '3.9'
services:
app:
build: ./app
ports:
- "3000:3000"
environment:
- NODE_ENV=development
depends_on:
- collector
collector:
image: otel/opentelemetry-collector:latest
volumes:
- ./collector-config.yaml:/etc/otelcol/config.yaml
command: ["--config=/etc/otelcol/config.yaml"]
ports:
- "4318:4318" # OTLP
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # UI
- "14250:14250" # Collector gRPC
Now, add the Dockerfile
inside the app/
folder:
# app/Dockerfile
FROM node:18
WORKDIR /usr/src/app
COPY . .
RUN npm install
CMD ["node", "app.js"]
Start the Stack
Start all services with Docker Compose:
docker compose up --build
Once the services are running:
Visit your app at http://localhost:3000
View traces at http://localhost:16686 in the Jaeger UI
Verify Traces in Jaeger
After visiting your app's root endpoint, open Jaeger’s UI, search for the service (default is usually unknown_service
unless explicitly named), and check the traces.
You should see spans for the HTTP request, middleware, and auto-instrumented libraries.
Conclusion
You now have a fully functional OpenTelemetry setup using Docker Compose. You've instrumented a basic JavaScript app to export traces and visualized them using Jaeger. This architecture is extendable for more complex applications and observability pipelines using Prometheus, Grafana, or cloud-native exporters.
For advanced topics such as custom span creation, metrics, and logs, consult the OpenTelemetry JavaScript docs.