Skip to content

Node.js

Node.js OpenTelemetry SDK

This guide covers how to instrument Node.js applications with OpenTelemetry to send traces to Sematext Tracing.

Auto-instrumentation automatically instruments popular frameworks and libraries without requiring code changes.

1. Install Dependencies

npm install @opentelemetry/auto-instrumentations-node
npm install @opentelemetry/exporter-trace-otlp-http
npm install @opentelemetry/sdk-node

2. Create Tracing Configuration File

Create a file called tracing.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({
  serviceName: 'your-service-name',
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4338',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

3. Run Your Application

node -r ./tracing.js your-app.js

Or add it to your package.json:

{
  "scripts": {
    "start": "node -r ./tracing.js app.js"
  }
}

Framework-Specific Considerations

Express.js

  • Automatically instrumented out of the box
  • Captures HTTP requests, responses, and route information

Koa

  • Automatically instrumented
  • Supports Koa v2+ applications

Fastify

  • Automatically instrumented
  • Captures request/response cycles

HTTP/HTTPS

  • Client and server requests automatically traced
  • Works with built-in http/https modules

Manual Instrumentation (Advanced)

For more control over instrumentation, you can manually configure OpenTelemetry.

1. Install Core Packages

npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http

2. Initialize Tracing

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'your-service-name',
    [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
  }),
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4338',
  }),
});

sdk.start();

// Get tracer for manual instrumentation
const { trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('your-service-name');

module.exports = { tracer };

3. Create Spans

const { trace, SpanStatusCode } = require('@opentelemetry/api');

async function processUserData(userId) {
  const tracer = trace.getActiveTracer();
  
  const span = tracer.startSpan('process-user-data');
  
  try {
    // Add attributes to the span
    span.setAttributes({
      'user.id': userId,
      'operation.type': 'data-processing'
    });
    
    // Your business logic here
    const userData = await fetchUserData(userId);
    const result = await transformData(userData);
    
    // Add more attributes based on the result
    span.setAttributes({
      'data.size': result.length,
      'operation.success': true
    });
    
    span.setStatus({ code: SpanStatusCode.OK });
    return result;
    
  } catch (error) {
    // Record the exception
    span.recordException(error);
    span.setStatus({
      code: SpanStatusCode.ERROR,
      message: error.message
    });
    throw error;
  } finally {
    span.end();
  }
}

async function fetchUserData(userId) {
  const tracer = trace.getActiveTracer();
  
  return tracer.startActiveSpan('fetch-user-data', async (span) => {
    span.setAttributes({
      'user.id': userId,
      'database.operation': 'SELECT'
    });
    
    try {
      // Simulate database call
      await new Promise(resolve => setTimeout(resolve, 100));
      return { id: userId, name: 'John Doe' };
    } finally {
      span.end();
    }
  });
}

Express.js Integration

Basic Setup with Auto-Instrumentation

// tracing.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({
  serviceName: 'express-api',
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4338',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
// app.js
const express = require('express');
const { trace } = require('@opentelemetry/api');

const app = express();
const tracer = trace.getTracer('express-api');

app.get('/users/:id', async (req, res) => {
  const span = tracer.startSpan('get-user-handler');
  
  try {
    span.setAttributes({
      'user.id': req.params.id,
      'http.method': req.method,
      'http.route': req.route.path
    });
    
    // Your business logic
    const user = await getUserById(req.params.id);
    
    res.json({ user });
    span.setStatus({ code: SpanStatusCode.OK });
  } catch (error) {
    span.recordException(error);
    span.setStatus({
      code: SpanStatusCode.ERROR,
      message: error.message
    });
    res.status(500).json({ error: 'Internal server error' });
  } finally {
    span.end();
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Run the Express App

node -r ./tracing.js app.js

Koa Integration

// app.js with Koa
const Koa = require('koa');
const Router = require('@koa/router');
const { trace } = require('@opentelemetry/api');

const app = new Koa();
const router = new Router();
const tracer = trace.getTracer('koa-api');

router.get('/users/:id', async (ctx) => {
  const span = tracer.startSpan('get-user-koa');
  
  try {
    span.setAttributes({
      'user.id': ctx.params.id,
      'http.method': ctx.method,
      'http.path': ctx.path
    });
    
    // Your business logic
    const user = await getUserById(ctx.params.id);
    
    ctx.body = { user };
    span.setStatus({ code: SpanStatusCode.OK });
  } catch (error) {
    span.recordException(error);
    span.setStatus({
      code: SpanStatusCode.ERROR,
      message: error.message
    });
    ctx.status = 500;
    ctx.body = { error: 'Internal server error' };
  } finally {
    span.end();
  }
});

app.use(router.routes());
app.listen(3000);

Database Instrumentation

MongoDB

npm install @opentelemetry/instrumentation-mongodb
const { MongoDBInstrumentation } = require('@opentelemetry/instrumentation-mongodb');

const sdk = new NodeSDK({
  serviceName: 'my-app',
  instrumentations: [
    getNodeAutoInstrumentations(),
    new MongoDBInstrumentation()
  ],
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4338',
  }),
});

PostgreSQL (pg)

npm install @opentelemetry/instrumentation-pg
const { PgInstrumentation } = require('@opentelemetry/instrumentation-pg');

const sdk = new NodeSDK({
  instrumentations: [
    getNodeAutoInstrumentations(),
    new PgInstrumentation()
  ],
  // ... other configuration
});

Redis

npm install @opentelemetry/instrumentation-redis
const { RedisInstrumentation } = require('@opentelemetry/instrumentation-redis');

const sdk = new NodeSDK({
  instrumentations: [
    getNodeAutoInstrumentations(),
    new RedisInstrumentation()
  ],
  // ... other configuration
});

Configuration Options

Environment Variables

# Service configuration
export OTEL_SERVICE_NAME=my-node-service
export OTEL_SERVICE_VERSION=1.0.0
export OTEL_RESOURCE_ATTRIBUTES=environment=production,team=backend

# OTLP exporter configuration
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4338
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

# Sampling configuration
export OTEL_TRACES_SAMPLER=traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1

Advanced Configuration

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { TraceIdRatioBasedSampler } = require('@opentelemetry/sdk-trace-base');

const sdk = new NodeSDK({
  serviceName: 'advanced-service',
  sampler: new TraceIdRatioBasedSampler(0.1), // 10% sampling
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4338',
    headers: {
      'authorization': 'Bearer your-token'
    },
    timeout: 30000, // 30 seconds
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-fs': {
        enabled: false, // Disable file system instrumentation
      },
      '@opentelemetry/instrumentation-http': {
        enabled: true,
        ignoreLinkLocalAddresses: true,
      }
    })
  ]
});

gRPC Configuration

const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4337',
  }),
  // ... other configuration
});

TypeScript Support

Install TypeScript Dependencies

npm install --save-dev typescript @types/node
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node

TypeScript Configuration

// tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';

const sdk = new NodeSDK({
  serviceName: 'typescript-service',
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4338',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();
// app.ts
import express from 'express';
import { trace, SpanStatusCode } from '@opentelemetry/api';

const app = express();
const tracer = trace.getTracer('typescript-service');

interface User {
  id: string;
  name: string;
  email: string;
}

app.get('/users/:id', async (req, res) => {
  const span = tracer.startSpan('get-user-typescript');
  
  try {
    span.setAttributes({
      'user.id': req.params.id,
      'operation.type': 'user-fetch'
    });
    
    const user: User = await fetchUser(req.params.id);
    
    res.json({ user });
    span.setStatus({ code: SpanStatusCode.OK });
  } catch (error) {
    span.recordException(error as Error);
    span.setStatus({
      code: SpanStatusCode.ERROR,
      message: (error as Error).message
    });
    res.status(500).json({ error: 'Internal server error' });
  } finally {
    span.end();
  }
});

async function fetchUser(id: string): Promise<User> {
  return tracer.startActiveSpan('fetch-user-db', async (span) => {
    try {
      span.setAttributes({
        'user.id': id,
        'database.operation': 'SELECT'
      });
      
      // Simulate database call
      await new Promise(resolve => setTimeout(resolve, 100));
      
      return {
        id,
        name: 'John Doe',
        email: 'john@example.com'
      };
    } finally {
      span.end();
    }
  });
}

app.listen(3000, () => {
  console.log('TypeScript server running on port 3000');
});

Troubleshooting

Common Issues

No Traces Appearing

  • Ensure the Sematext Agent is running and accessible
  • Check the OTLP endpoint configuration
  • Verify service name is set correctly

Module Not Found Errors

# Make sure all dependencies are installed
npm install @opentelemetry/auto-instrumentations-node @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-http

Performance Issues

// Configure batch span processor
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');

const sdk = new NodeSDK({
  spanProcessor: new BatchSpanProcessor(exporter, {
    maxQueueSize: 2048,
    scheduledDelayMillis: 5000,
    maxExportBatchSize: 512,
    exportTimeoutMillis: 30000,
  })
});

Debug Configuration

// Enable debug logging
process.env.OTEL_LOG_LEVEL = 'debug';

// Or programmatically
const sdk = new NodeSDK({
  // ... configuration
});

sdk.start();
console.log('Tracing initialized');

Verification Script

// test-tracing.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { ConsoleSpanExporter } = require('@opentelemetry/sdk-trace-base');
const { trace } = require('@opentelemetry/api');

const sdk = new NodeSDK({
  serviceName: 'test-service',
  traceExporter: new ConsoleSpanExporter(),
});

sdk.start();

const tracer = trace.getTracer('test-service');

const span = tracer.startSpan('test-span');
span.setAttributes({
  'test.attribute': 'test-value',
  'test.number': 42
});
span.end();

console.log('Test span created - check console output');

Best Practices

Span Management

// Use startActiveSpan for automatic context management
tracer.startActiveSpan('operation-name', async (span) => {
  try {
    // Your code here
    span.setAttributes({ key: 'value' });
  } catch (error) {
    span.recordException(error);
    throw error;
  } finally {
    span.end();
  }
});

Error Handling

try {
  // Business logic
} catch (error) {
  span.recordException(error);
  span.setStatus({
    code: SpanStatusCode.ERROR,
    message: error.message
  });
  throw error;
}

Async/Await Patterns

async function asyncOperation() {
  return tracer.startActiveSpan('async-op', async (span) => {
    try {
      const result = await someAsyncWork();
      span.setAttributes({ 'result.count': result.length });
      return result;
    } catch (error) {
      span.recordException(error);
      throw error;
    } finally {
      span.end();
    }
  });
}

Resource Configuration

const { Resource } = require('@opentelemetry/resources');
const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');

const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: 'my-service',
  [SemanticResourceAttributes.SERVICE_VERSION]: '1.2.3',
  [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
  [SemanticResourceAttributes.SERVICE_NAMESPACE]: 'ecommerce'
});

Next Steps