@bluealba/opentelemetry-nodejs
v1.0.0
Published
OpenTelemetry wrapper for NodeJs Apps
Readme
@bluealba/opentelemetry-nodejs
OpenTelemetry wrapper for Node.js that simplifies instrumentation and observability of applications.
Table of Contents
- Quick Start
- Prerequisites
- Installation
- Initial Setup
- Environment Variables
- Custom Traces
- Metrics
- Framework Examples
- Development
- Troubleshooting
- Additional Resources
Quick Start
# 1. Install the package
npm install @bluealba/opentelemetry-nodejs
# 2. Set up environment variables
export OTEL_ENABLED=true
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
# 3. Initialize in your application (must be FIRST import)
# main.ts
import { startOtel } from "@bluealba/opentelemetry-nodejs";
import { name, version } from "./package.json";
startOtel(name, version);
// Then import the rest of your app
import { app } from "./app";Prerequisites
Before using this package, you need to have an OpenTelemetry Collector running and listening on the port specified in OTEL_EXPORTER_OTLP_ENDPOINT.
We recommend https://hub.docker.com/r/otel/opentelemetry-collector-contrib as it has support for exporting traces to Datadog: https://docs.datadoghq.com/opentelemetry/setup/collector_exporter/install/
Installation
npm install @bluealba/opentelemetry-nodejsInitial Setup
⚠️ Important: OpenTelemetry must be initialized before any other imports and before the application bootstraps. It is recommended to be the first line of code executed.
Example with NestJS
// main.ts
import { startOtel } from "@bluealba/opentelemetry-nodejs";
import { name, version } from "../package.json";
// Initialize OpenTelemetry FIRST
startOtel(name, version);
// Or
startOtel(name, version, customLoggerInstance);
// Where customLoggerInstance is optional an can be any logger that support, info, log, warn and error methods
// Then, import and start the application normally
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { json } from "express";
import { NestFactory } from "@nestjs/core";
import { ValidationPipe } from "@nestjs/common";
import { AppConfigService } from "./app-config/app-config.service";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
transform: true,
})
);
const configService = app.get(AppConfigService);
await app.listen(configService.get("PORT"));
}
bootstrap();Environment Variables
Required Environment Variables
| Variable | Description | Default |
| ----------------------------- | ---------------------------------- | -------------------------------------- |
| OTEL_ENABLED | Enables/disables OpenTelemetry | false |
| OTEL_EXPORTER_OTLP_ENDPOINT | URL of the OpenTelemetry collector | None (SDK won't initialize if missing) |
Important: Both OTEL_ENABLED=true AND OTEL_EXPORTER_OTLP_ENDPOINT must be set for OpenTelemetry to initialize. If the endpoint is missing, a warning will be logged but your application will continue to run normally.
What are auto-instrumentations?
https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node
The node_autoinstrumentations are a set of automatic instrumentations for popular Node.js libraries (HTTP, Express, PostgreSQL, MongoDB, Redis, KafkaJs, etc.). When enabled, OpenTelemetry automatically captures traces from these operations without requiring additional code.
Exporter Configuration
| Variable | Description | Default | Example |
| ----------------------------- | --------------------------------------------------------------- | ------- | ------------------------------------ |
| OTEL_EXPORTER_OTLP_ENDPOINT | URL of the OpenTelemetry collector that will receive the traces | None | http://localhost:4318/v1/traces |
| OTEL_BATCH_EXPORT_TIMEOUT | Timeout to export collected traces (in ms) | 5000 | 5000 |
| OTEL_BATCH_SCHEDULE_DELAY | Delay between trace exports (in ms) | 2000 | 2000 |
| OTEL_BATCH_MAX_SIZE | Maximum size of the trace batch to be sent | 512 | 512 |
| OTEL_QUEUE_SIZE | Queue size (number of traces to accumulate before sending them) | 2048 | 2048 |
Optional Configuration
| Variable | Default | Description |
| ------------------------------------- | ------------------------------- | ----------------------------------------------------------------- |
| OTEL_APP_NAME | Service name from startOtel() | Override service name |
| OTEL_APP_NAMESPACE | namespace | Service namespace for grouping |
| TARGET_ENV | local | Environment name (dev, staging, prod) |
| OTEL_NODE_AUTOINSTRUMENTATIONS | false | Enable auto-instrumentations for popular Node.js libraries |
| OTEL_SAMPLING_RATIO | 1 | Sampling ratio (0-1) for traces. 1 = 100% sampling |
| OTEL_METRICS_ENABLED | false | Enable metrics exporter |
| OTEL_EXPORTER_OTLP_METRICS_ENDPOINT | Falls back to traces endpoint | Metrics exporter endpoint (uses OTEL_EXPORTER_OTLP_ENDPOINT if not set) |
| OTEL_METRICS_EXPORT_INTERVAL | 60000 | How often metrics are exported (ms) |
| OTEL_ENABLE_HOST_METRICS | false | Enable host metrics exporter (needs OTEL_METRICS_ENABLED to init) |
Example .env File
# OpenTelemetry Configuration
OTEL_ENABLED=true
OTEL_NODE_AUTOINSTRUMENTATIONS=true
OTEL_SAMPLING_RATIO=1
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318/v1/traces
OTEL_BATCH_EXPORT_TIMEOUT=5000
OTEL_BATCH_SCHEDULE_DELAY=2000
OTEL_BATCH_MAX_SIZE=512
OTEL_QUEUE_SIZE=2048
#Service identity
OTEL_APP_NAME=your_app_name
OTEL_APP_NAMESPACE=your_workspace
TARGET_ENV=dev
# Metrics Configuration
OTEL_METRICS_ENABLED=true # to enable metrics export
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://localhost:4318/v1/metrics # Specify the otel-collector EP
OTEL_METRICS_EXPORT_INTERVAL=60000 # How often metrics are going to be exported in milliseconds
OTEL_ENABLE_HOST_METRICS=true # to export host metrics (cpu metrics from the host running the node process)Custom Traces
@Span() Decorator
You can use the @Span() decorator to generate custom spans on specific methods:
import { Span } from "@bluealba/opentelemetry-nodejs";
export class MyService {
// Without arguments: automatically uses the class and method name
@Span()
async processData(data: any) {
// your logic...
}
// With custom name
@Span("custom-operation-name")
async complexOperation() {
// your logic...
}
}withSpan() Function
For functional/imperative code, use withSpan to manually create spans:
import { withSpan } from "@bluealba/opentelemetry-nodejs";
async function processOrder(orderId: string) {
return withSpan("process-order", async () => {
// Your logic here
const order = await fetchOrder(orderId);
return order;
});
}Adding Custom Attributes
You can add specific attributes to the current span using setOtelAttribute:
import { Span, setOtelAttribute } from "@bluealba/opentelemetry-nodejs";
export class PaymentService {
@Span()
async handlePayload(payload: any[]) {
// Your logic...
// Add custom attribute to the current span
setOtelAttribute("payload.count", payload.length);
setOtelAttribute("payload.type", typeof payload);
// More logic...
}
}Custom attributes are useful for:
- Adding business context to traces
- Facilitating filtering and searching in observability tools
- Debugging and performance analysis
Available Metrics
When OTEL_METRICS_ENABLED=true, the following metrics are automatically exported:
Runtime Metrics (from auto-instrumentations)
nodejs_eventloop_delay_*- Event loop delay (mean, p50, p90, p99)nodejs_eventloop_utilization- Event loop utilization (0-1)v8js_memory_heap_*- V8 heap memory usagev8js_gc_duration_*- Garbage collection duration by typehttp_server_duration- HTTP server request durationhttp_client_duration- HTTP client request durationdb_client_operation_duration- Database operation duration
Host Metrics (when OTEL_ENABLE_HOST_METRICS=true)
process_cpu_time- Process CPU time (user/system)process_cpu_utilization- Process CPU utilization (0-1)process_resident_memory_bytes- Resident memory (RSS)process_heap_bytes- Heap memory
All metrics include resource attributes: service.name, service.namespace, service.version, deployment.environment
Framework Examples
Express.js
// server.ts
import { startOtel } from "@bluealba/opentelemetry-nodejs";
import { name, version } from "./package.json";
// Initialize OpenTelemetry FIRST
startOtel(name, version);
// Then import Express and other dependencies
import express from "express";
import { Span, setOtelAttribute } from "@bluealba/opentelemetry-nodejs";
const app = express();
app.use(express.json());
// Routes will be automatically instrumented if OTEL_NODE_AUTOINSTRUMENTATIONS=true
app.get("/api/users/:id", async (req, res) => {
// Add custom attributes to the auto-generated span
setOtelAttribute("user.id", req.params.id);
const user = await getUserById(req.params.id);
res.json(user);
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});Fastify
// server.ts
import { startOtel } from "@bluealba/opentelemetry-nodejs";
import { name, version } from "./package.json";
// Initialize OpenTelemetry FIRST
startOtel(name, version);
// Then import Fastify
import Fastify from "fastify";
import { setOtelAttribute } from "@bluealba/opentelemetry-nodejs";
const fastify = Fastify({ logger: true });
fastify.get("/api/health", async (request, reply) => {
setOtelAttribute("health.status", "ok");
return { status: "healthy" };
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();Standalone Node.js Application
// index.ts
import { startOtel, Span, withSpan, setOtelAttribute } from "@bluealba/opentelemetry-nodejs";
import { name, version } from "./package.json";
// Initialize OpenTelemetry FIRST
startOtel(name, version);
class DataProcessor {
@Span("process-batch")
async processBatch(items: any[]) {
setOtelAttribute("batch.size", items.length);
for (const item of items) {
await this.processItem(item);
}
}
@Span()
async processItem(item: any) {
setOtelAttribute("item.id", item.id);
// Processing logic...
}
}
// Using withSpan for functional code
async function main() {
await withSpan("main-operation", async () => {
const processor = new DataProcessor();
await processor.processBatch([{ id: 1 }, { id: 2 }]);
});
}
main();Development
This section is for developers who want to contribute to this library or run it locally.
Setup Development Environment
# Clone the repository
git clone <repository-url>
cd opentelemetry-nodejs
# Install dependencies
npm install
# Build the project
npm run buildDevelopment Commands
# Watch mode for development
npm run dev
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Generate coverage report
npm run coverage
# Lint code
npm run lint
# Auto-fix linting issues
npm run lint:fix
# Clean build artifacts
npm run cleanRunning Tests
This project uses Jest for testing. Test files follow the pattern *.spec.ts and are located alongside source files in src/.
# Run all tests
npm test
# Run specific test file
npm test -- src/otel/otel.spec.ts
# Run tests matching a pattern
npm test -- -t "should initialize SDK"
# Run with coverage
npm run coverageProject Structure
src/
├── index.ts # Public API exports
└── otel/
├── otel.ts # Main initialization logic
├── otel.spec.ts # Tests for otel.ts
├── otel-config.ts # Environment variable configuration
├── otel-config.spec.ts # Tests for otel-config.ts
├── otel.helper.ts # User-facing utilities (@Span, withSpan, etc.)
└── otel.helper.spec.ts # Tests for otel.helper.tsMaking Changes
- Create a new branch for your feature/fix
- Make your changes and add tests
- Run
npm testto ensure all tests pass - Run
npm run lint:fixto format code - Commit your changes following conventional commits
- Submit a pull request
Troubleshooting
OpenTelemetry is not initializing
Problem: No traces are being sent to the collector.
Solutions:
- Verify
OTEL_ENABLED=trueis set - Verify
OTEL_EXPORTER_OTLP_ENDPOINTis set and reachable - Check that
startOtel()is called BEFORE any other imports - Check application logs for initialization warnings
# Test if collector is reachable
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d '{}'Spans are not showing custom attributes
Problem: Custom attributes added with setOtelAttribute() are not visible in traces.
Solutions:
- Ensure
OTEL_ENABLED=true - Verify you're calling
setOtelAttribute()within an active span (inside a@Span()decorated method orwithSpan()callback) - Check that the attribute name and value are valid
Auto-instrumentation not working
Problem: HTTP requests, database queries, etc. are not being traced automatically.
Solutions:
- Set
OTEL_NODE_AUTOINSTRUMENTATIONS=true - Ensure
startOtel()is called BEFORE importing libraries like Express, PostgreSQL clients, etc. - Verify the library you're using is supported by @opentelemetry/auto-instrumentations-node
Metrics not being exported
Problem: Metrics are not appearing in your observability platform.
Solutions:
- Set
OTEL_METRICS_ENABLED=true - Verify
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTis set (or it will fall back to the traces endpoint) - Check that your collector is configured to receive metrics
- For host metrics, ensure
OTEL_ENABLE_HOST_METRICS=true
TypeScript compilation errors
Problem: Getting type errors when using the library.
Solutions:
- Ensure you have
@types/nodeinstalled in your project - Check that your
tsconfig.jsonincludes the necessary compiler options:{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
Initialization order issues
Problem: Getting errors or unexpected behavior.
Solution: Ensure OpenTelemetry initialization happens FIRST. Common mistake:
// WRONG - imports before initialization
import { NestFactory } from "@nestjs/core";
import { startOtel } from "@bluealba/opentelemetry-nodejs";
startOtel("app", "1.0.0");
// CORRECT - initialization before imports
import { startOtel } from "@bluealba/opentelemetry-nodejs";
startOtel("app", "1.0.0");
import { NestFactory } from "@nestjs/core";Collector connection refused
Problem: Error connecting to the OpenTelemetry collector.
Solutions:
- Verify the collector is running:
docker psor check your collector service - Check the endpoint URL format:
http://host:port/v1/traces(note the/v1/tracespath) - Ensure the port is accessible from your application
- Check firewall rules if running in containers or different networks
Performance impact
Problem: Concerned about performance overhead.
Information:
- Adjust
OTEL_SAMPLING_RATIOto sample fewer traces (e.g.,0.1for 10%) - Increase
OTEL_BATCH_SCHEDULE_DELAYto batch spans less frequently - Disable auto-instrumentations if not needed:
OTEL_NODE_AUTOINSTRUMENTATIONS=false - Disable metrics if only traces are needed:
OTEL_METRICS_ENABLED=false
