Zero-Code Instrumentation of Node.js using OpenTelemetry
9 minute read
Overview
Zero-code instrumentation in Node.js with OpenTelemetry provides automatic telemetry collection from popular libraries and frameworks without requiring any code modifications. By simply installing a package and using environment variables or Node.js command-line flags, you can capture traces, metrics, and logs from your application’s interactions with HTTP servers, databases, message queues, and more. This approach is ideal for quickly adding observability to existing applications or when you cannot modify application code.
How It Works
The @opentelemetry/auto-instrumentations-node package bundles together automatic instrumentation for many popular Node.js libraries. When loaded before your application starts, it:
- Hooks into Modules: Intercepts
require()calls to wrap supported libraries - Injects Instrumentation: Adds telemetry collection code automatically
- Captures Telemetry: Records spans, metrics, and context for each operation
- Exports Data: Sends telemetry to configured backends via OTLP
All of this happens transparently without any changes to your application code.
Prerequisites
Ensure that you have:
- Node.js: Version 14 or later (Active LTS versions recommended)
- npm or yarn: Package manager for installing dependencies
- Edge Delta: An account and pipeline with an OTLP input node
- Supported Libraries: Your application uses supported frameworks (see Supported Instrumentation)
Installation
1. Install Auto-Instrumentation Packages
Install the required OpenTelemetry packages:
npm install --save @opentelemetry/api \
@opentelemetry/auto-instrumentations-node
These packages include:
- @opentelemetry/api: Core OpenTelemetry API
- @opentelemetry/auto-instrumentations-node: Bundled automatic instrumentation for popular Node.js libraries
The installation includes the SDK, exporters, and instrumentation libraries needed for zero-code operation.
2. Verify Installation
Check that the packages are installed:
npm list @opentelemetry/auto-instrumentations-node
Configuration
Zero-code instrumentation is configured entirely through environment variables. There are no configuration files to create or code changes to make.
Required Environment Variables
Set these environment variables to configure basic telemetry export:
| Variable | Description | Example |
|---|---|---|
OTEL_SERVICE_NAME | Service name for identification | my-nodejs-service |
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP endpoint for all signals | http://localhost:4318 |
Common Configuration Variables
| Variable | Description | Default | Example |
|---|---|---|---|
OTEL_TRACES_EXPORTER | Trace exporter type | otlp | otlp, console, none |
OTEL_METRICS_EXPORTER | Metrics exporter type | otlp | otlp, console, none |
OTEL_LOGS_EXPORTER | Logs exporter type | otlp | otlp, console, none |
OTEL_EXPORTER_OTLP_PROTOCOL | OTLP protocol | http/protobuf | http/protobuf, grpc |
OTEL_LOG_LEVEL | Logging verbosity | info | none, error, warn, info, debug, verbose, all |
Endpoint Configuration for Edge Delta
The endpoint configuration depends on your deployment environment:
Kubernetes Environment:
export OTEL_EXPORTER_OTLP_ENDPOINT="http://ed-data-supply-svc:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
Non-Kubernetes (same host as Edge Delta agent):
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
Using gRPC (port 4317):
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
Signal-Specific Endpoints:
If you need different endpoints for traces, metrics, and logs:
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://localhost:4318/v1/traces"
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://localhost:4318/v1/metrics"
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://localhost:4318/v1/logs"
See Ingest Data from an OTLP Source for detailed Edge Delta configuration.
Resource Attributes
Add metadata to all telemetry using resource attributes:
export OTEL_RESOURCE_ATTRIBUTES="service.name=my-service,service.version=1.0.0,deployment.environment=production,service.namespace=my-team"
Resource Detectors
Control which resource detectors are enabled:
export OTEL_NODE_RESOURCE_DETECTORS="env,host,os,process,serviceinstance"
Available detectors:
env- Environment variableshost- Host informationos- Operating system detailsprocess- Process informationserviceinstance- Service instance IDcontainer- Container environment (Docker, Kubernetes)alibaba- Alibaba Cloudaws- Amazon Web Servicesazure- Microsoft Azuregcp- Google Cloud Platformall- Enable all detectorsnone- Disable all detectors
Controlling Instrumentation
Enable Only Specific Instrumentations:
export OTEL_NODE_ENABLED_INSTRUMENTATIONS="http,express,pg"
This enables only HTTP, Express, and PostgreSQL instrumentation (specify names without the @opentelemetry/instrumentation- prefix).
Disable Specific Instrumentations:
export OTEL_NODE_DISABLED_INSTRUMENTATIONS="fs,dns"
This disables file system and DNS instrumentation while keeping all others enabled.
Priority: When both variables are set, enabled instrumentations are processed first, then disabled ones are applied. Instrumentations in both lists will be disabled.
Running Your Application
There are multiple ways to run your Node.js application with zero-code instrumentation.
Method 1: Using –require Flag (Recommended)
Use Node.js’s --require flag to load the auto-instrumentation before your application:
node --require @opentelemetry/auto-instrumentations-node/register app.js
Method 2: Using NODE_OPTIONS Environment Variable
Set the NODE_OPTIONS environment variable:
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
node app.js
This method is useful in containerized environments where you can’t modify the startup command.
Method 3: Complete Example with All Configuration
Single Command:
env OTEL_SERVICE_NAME="my-nodejs-service" \
OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318" \
OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf" \
OTEL_TRACES_EXPORTER="otlp" \
OTEL_METRICS_EXPORTER="otlp" \
OTEL_LOG_LEVEL="info" \
OTEL_RESOURCE_ATTRIBUTES="service.version=1.0.0,deployment.environment=production" \
OTEL_NODE_RESOURCE_DETECTORS="env,host,os,process" \
node --require @opentelemetry/auto-instrumentations-node/register app.js
Using Export Statements:
# Set configuration
export OTEL_SERVICE_NAME="my-nodejs-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_EXPORTER_OTLP_PROTOCOL="http/protobuf"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOG_LEVEL="info"
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.0.0,deployment.environment=production"
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
# Run application
node app.js
Deployment Scenarios
Scenario 1: Direct Deployment (Development/VM)
For development or virtual machine deployments:
Step 1: Install Dependencies
npm install --save @opentelemetry/api @opentelemetry/auto-instrumentations-node
Step 2: Configure Environment
Create a .env file or export variables:
export OTEL_SERVICE_NAME="my-service"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
export OTEL_TRACES_EXPORTER="otlp"
export OTEL_METRICS_EXPORTER="otlp"
export OTEL_LOG_LEVEL="info"
Step 3: Run Application
node --require @opentelemetry/auto-instrumentations-node/register app.js
Scenario 2: Docker Container
Dockerfile:
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies including instrumentation
RUN npm ci --only=production && \
npm install --save @opentelemetry/api @opentelemetry/auto-instrumentations-node
# Copy application code
COPY . .
# Set NODE_OPTIONS to load instrumentation
ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
# Run application
CMD ["node", "app.js"]
docker-compose.yml:
version: '3.8'
services:
app:
build: .
environment:
- OTEL_SERVICE_NAME=my-nodejs-service
- OTEL_EXPORTER_OTLP_ENDPOINT=http://ed-data-supply-svc:4318
- OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
- OTEL_TRACES_EXPORTER=otlp
- OTEL_METRICS_EXPORTER=otlp
- OTEL_LOG_LEVEL=info
- OTEL_RESOURCE_ATTRIBUTES=service.version=1.0.0,deployment.environment=production
ports:
- "3000:3000"
Scenario 3: Kubernetes Deployment
Deployment Manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-app
spec:
replicas: 3
selector:
matchLabels:
app: nodejs-app
template:
metadata:
labels:
app: nodejs-app
spec:
containers:
- name: app
image: my-nodejs-app:latest
ports:
- containerPort: 3000
env:
# OpenTelemetry Configuration
- name: NODE_OPTIONS
value: "--require @opentelemetry/auto-instrumentations-node/register"
- name: OTEL_SERVICE_NAME
value: "my-nodejs-service"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://ed-data-supply-svc.default.svc.cluster.local:4318"
- name: OTEL_EXPORTER_OTLP_PROTOCOL
value: "http/protobuf"
- name: OTEL_TRACES_EXPORTER
value: "otlp"
- name: OTEL_METRICS_EXPORTER
value: "otlp"
- name: OTEL_LOG_LEVEL
value: "info"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.version=1.0.0,deployment.environment=production,k8s.namespace.name=default"
- name: OTEL_NODE_RESOURCE_DETECTORS
value: "env,host,os,process,container"
# Application Configuration
- name: PORT
value: "3000"
Using ConfigMap for Configuration:
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-config
data:
OTEL_SERVICE_NAME: "my-nodejs-service"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://ed-data-supply-svc:4318"
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf"
OTEL_TRACES_EXPORTER: "otlp"
OTEL_METRICS_EXPORTER: "otlp"
OTEL_LOG_LEVEL: "info"
NODE_OPTIONS: "--require @opentelemetry/auto-instrumentations-node/register"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nodejs-app
spec:
template:
spec:
containers:
- name: app
image: my-nodejs-app:latest
envFrom:
- configMapRef:
name: otel-config
Supported Instrumentation
The @opentelemetry/auto-instrumentations-node package includes automatic instrumentation for many popular Node.js libraries:
Web Frameworks
- Express - Express.js web framework
- Koa - Koa web framework
- Fastify - Fastify web framework
- Hapi - Hapi.js framework
- Restify - Restify framework
- Connect - Connect middleware framework
HTTP Clients
- http/https - Native Node.js HTTP modules
- axios - Promise-based HTTP client
- node-fetch - Fetch API implementation
- got - HTTP request library
- superagent - HTTP request library
- undici - HTTP/1.1 client
Databases
- mongodb - MongoDB driver
- mysql - MySQL driver
- mysql2 - MySQL2 driver
- pg - PostgreSQL driver
- redis - Redis client
- ioredis - Redis client (alternative)
- memcached - Memcached client
- knex - SQL query builder
Message Queues
- amqplib - RabbitMQ/AMQP client
- kafkajs - Kafka client
- aws-sdk - AWS SDK (SQS, SNS, etc.)
GraphQL
- graphql - GraphQL execution
- @apollo/server - Apollo Server
Other
- dns - DNS resolution
- fs - File system operations
- net - Network operations
- winston - Winston logger
- pino - Pino logger
- bunyan - Bunyan logger
For the complete and up-to-date list, see the auto-instrumentations-node repository.
Sampling Configuration
Control the volume of trace data using sampling strategies:
Ratio-Based Sampling
Sample a percentage of traces:
export OTEL_TRACES_SAMPLER="traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1" # Sample 10% of traces
Parent-Based Sampling (Recommended for Distributed Tracing)
Ensure child spans follow their parent’s sampling decision:
export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
export OTEL_TRACES_SAMPLER_ARG="0.1" # Sample 10% of root traces
This ensures:
- New traces (requests with no parent) are sampled at the specified rate
- Child spans inherit their parent’s sampling decision
- Distributed traces remain complete across service boundaries
Always Sample (Development)
For development and testing:
export OTEL_TRACES_SAMPLER="always_on"
Never Sample (Disable Tracing)
To disable trace collection:
export OTEL_TRACES_SAMPLER="always_off"
Verifying Instrumentation
1. Enable Debug Logging
Set debug logging to see instrumentation activity:
export OTEL_LOG_LEVEL="debug"
node --require @opentelemetry/auto-instrumentations-node/register app.js
Look for messages like:
@opentelemetry/instrumentation-http registered
@opentelemetry/instrumentation-express registered
Exporting traces to http://localhost:4318/v1/traces
2. Use Console Exporter for Testing
Test locally by exporting to console:
export OTEL_TRACES_EXPORTER="console"
export OTEL_METRICS_EXPORTER="console"
node --require @opentelemetry/auto-instrumentations-node/register app.js
3. Generate Test Traffic
Generate requests to your application:
# Simple curl test
curl http://localhost:3000/api/test
# Load testing with autocannon
npx autocannon -c 10 -d 5 http://localhost:3000/api/test
4. Verify in Edge Delta
- Log into your Edge Delta account
- Navigate to your pipeline with the OTLP input node
- Check for incoming traces with your service name
- Verify spans from instrumented libraries (HTTP, database, etc.)
Combining with Manual Instrumentation
You can combine zero-code instrumentation with manual spans for custom business logic:
app.js:
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('my-service', '1.0.0');
async function processOrder(orderId) {
// Manual span for custom business logic
return tracer.startActiveSpan('processOrder', async (span) => {
span.setAttribute('order.id', orderId);
try {
// This HTTP call is automatically instrumented
const user = await fetchUserDetails(orderId);
// This database call is automatically instrumented
const order = await database.getOrder(orderId);
span.setAttributes({
'user.id': user.id,
'order.total': order.total,
});
return order;
} finally {
span.end();
}
});
}
The zero-code instrumentation captures HTTP and database calls automatically, while your manual span captures business logic.
Best Practices
Set Service Name: Always set
OTEL_SERVICE_NAMEfor proper service identification.Use Environment Variables: Configure via environment variables for consistency across environments.
Configure Resource Detectors: Enable appropriate detectors for your platform (container, cloud provider).
Control Instrumentation: Disable unnecessary instrumentations to reduce overhead:
export OTEL_NODE_DISABLED_INSTRUMENTATIONS="fs,dns"Set Appropriate Log Level: Use
infoin production,debugonly when troubleshooting:export OTEL_LOG_LEVEL="info"Enable Sampling in Production: Use ratio-based sampling to control data volume:
export OTEL_TRACES_SAMPLER="parentbased_traceidratio" export OTEL_TRACES_SAMPLER_ARG="0.1"Add Resource Attributes: Include deployment and version information:
export OTEL_RESOURCE_ATTRIBUTES="service.version=1.2.3,deployment.environment=production"Test Locally First: Use console exporters for local testing before deploying.
Troubleshooting
No Telemetry Data Appearing
Check instrumentation is loaded:
export OTEL_LOG_LEVEL="debug"
node --require @opentelemetry/auto-instrumentations-node/register app.js
Look for “registered” messages for each instrumentation.
Verify endpoint configuration:
# Test endpoint connectivity
curl -X POST http://localhost:4318/v1/traces \
-H "Content-Type: application/json" \
-d '{"resourceSpans":[]}'
Check environment variables are set:
echo $OTEL_SERVICE_NAME
echo $OTEL_EXPORTER_OTLP_ENDPOINT
Instrumentation Not Working for Specific Library
Check library is supported:
- Review the supported libraries list
- Verify library version compatibility
Ensure instrumentation is enabled:
# Make sure library is not disabled
echo $OTEL_NODE_DISABLED_INSTRUMENTATIONS
Check load order:
- Auto-instrumentation must be loaded BEFORE the library
- Use
--requireflag, don’t import manually after libraries load
Performance Issues
High CPU usage:
- Disable file system instrumentation:
OTEL_NODE_DISABLED_INSTRUMENTATIONS="fs" - Reduce log level:
OTEL_LOG_LEVEL="warn" - Enable sampling:
OTEL_TRACES_SAMPLER="traceidratio"
High memory usage:
- Check for span leaks (spans not ended)
- Reduce batch size and export interval (requires custom configuration)
- Disable unnecessary instrumentations
Application startup slower:
- This is expected as modules are wrapped during initialization
- Consider trade-off between observability and startup time
- Profile with:
NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register" node --prof app.js
Error: Cannot find module
Error: “Cannot find module ‘@opentelemetry/auto-instrumentations-node’”
Solution: Ensure the package is installed:
npm install --save @opentelemetry/auto-instrumentations-node
For production deployments, verify package-lock.json includes the package.
TypeScript Issues
Using with tsx or ts-node:
For TypeScript applications, use the --require flag with the transpiler:
# With tsx
npx tsx --require @opentelemetry/auto-instrumentations-node/register app.ts
# With ts-node
node --require @opentelemetry/auto-instrumentations-node/register --require ts-node/register app.ts
Limitations
Load Order Critical: Auto-instrumentation must be loaded before application code. Using
require()after libraries are loaded won’t work.Native Modules: Some native modules cannot be instrumented automatically.
Dynamic Imports: Libraries loaded via dynamic
import()may not be instrumented.Custom Implementations: Non-standard library implementations may not be captured.
ESM Limitations: Experimental ESM support may have limitations with some libraries.
Performance Overhead: While minimal, there is some overhead from hooking into module loading and wrapping functions.
Next Steps
- Review supported libraries for your application
- Learn about code-based instrumentation for custom business logic
- Set up Edge Delta processing for your telemetry data
- Explore sampling strategies for production deployments
- Configure resource attributes for richer metadata and filtering
For more information on OpenTelemetry Node.js zero-code instrumentation, visit the official OpenTelemetry JavaScript documentation.