Zero-Code Instrumentation of Node.js using OpenTelemetry

Instrument Node.js applications using zero-code OpenTelemetry to automatically capture telemetry data from popular frameworks without code changes.

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:

  1. Hooks into Modules: Intercepts require() calls to wrap supported libraries
  2. Injects Instrumentation: Adds telemetry collection code automatically
  3. Captures Telemetry: Records spans, metrics, and context for each operation
  4. 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:

VariableDescriptionExample
OTEL_SERVICE_NAMEService name for identificationmy-nodejs-service
OTEL_EXPORTER_OTLP_ENDPOINTOTLP endpoint for all signalshttp://localhost:4318

Common Configuration Variables

VariableDescriptionDefaultExample
OTEL_TRACES_EXPORTERTrace exporter typeotlpotlp, console, none
OTEL_METRICS_EXPORTERMetrics exporter typeotlpotlp, console, none
OTEL_LOGS_EXPORTERLogs exporter typeotlpotlp, console, none
OTEL_EXPORTER_OTLP_PROTOCOLOTLP protocolhttp/protobufhttp/protobuf, grpc
OTEL_LOG_LEVELLogging verbosityinfonone, 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 variables
  • host - Host information
  • os - Operating system details
  • process - Process information
  • serviceinstance - Service instance ID
  • container - Container environment (Docker, Kubernetes)
  • alibaba - Alibaba Cloud
  • aws - Amazon Web Services
  • azure - Microsoft Azure
  • gcp - Google Cloud Platform
  • all - Enable all detectors
  • none - 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.

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

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

  1. Log into your Edge Delta account
  2. Navigate to your pipeline with the OTLP input node
  3. Check for incoming traces with your service name
  4. 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

  1. Set Service Name: Always set OTEL_SERVICE_NAME for proper service identification.

  2. Use Environment Variables: Configure via environment variables for consistency across environments.

  3. Configure Resource Detectors: Enable appropriate detectors for your platform (container, cloud provider).

  4. Control Instrumentation: Disable unnecessary instrumentations to reduce overhead:

    export OTEL_NODE_DISABLED_INSTRUMENTATIONS="fs,dns"
    
  5. Set Appropriate Log Level: Use info in production, debug only when troubleshooting:

    export OTEL_LOG_LEVEL="info"
    
  6. 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"
    
  7. Add Resource Attributes: Include deployment and version information:

    export OTEL_RESOURCE_ATTRIBUTES="service.version=1.2.3,deployment.environment=production"
    
  8. 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:

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 --require flag, 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

  1. Load Order Critical: Auto-instrumentation must be loaded before application code. Using require() after libraries are loaded won’t work.

  2. Native Modules: Some native modules cannot be instrumented automatically.

  3. Dynamic Imports: Libraries loaded via dynamic import() may not be instrumented.

  4. Custom Implementations: Non-standard library implementations may not be captured.

  5. ESM Limitations: Experimental ESM support may have limitations with some libraries.

  6. Performance Overhead: While minimal, there is some overhead from hooking into module loading and wrapping functions.

Next Steps

For more information on OpenTelemetry Node.js zero-code instrumentation, visit the official OpenTelemetry JavaScript documentation.