Skip to main content

Javascript Node

Implement OpenTelemetry custom instrumentation for Node.js applications to collect logs, metrics, and traces using the Node.js OTel SDK.

Overview

This guide demonstrates how to:

  • Set up OpenTelemetry custom instrumentation for Node.js
  • Configure manual tracing using spans
  • Create and manage custom metrics
  • Add semantic attributes and events
  • Export telemetry data to OpenTelemetry Collector

Prerequisites

Before starting, ensure you have:

  • Node.js 14 or later installed
  • A Node.js project set up
  • Access to package installation (npm/yarn)

Required Packages

Install the following necessary packages or add them to package.json:

npm install @opentelemetry/sdk-node
npm install @opentelemetry/exporter-trace-otlp-http
npm install @opentelemetry/resources
npm install @opentelemetry/sdk-trace-node
npm install @opentelemetry/sdk-trace-base
npm install @opentelemetry/exporter-metrics-otlp-http
npm install @opentelemetry/sdk-metrics
npm install @opentelemetry/sdk-logs
npm install @opentelemetry/exporter-logs-otlp-http
npm install @opentelemetry/api
npm install @opentelemetry/api-logs

Note: Ensure the scout collector is properly configured to receive and process the telemetry data before forwarding to Scout backend. Click to know more

Traces

To start tracing, first initialize the NodeSDK with trace configuration. A Resource is an immutable representation of entity producing telemetry.

Sample Reference code for Initialization

// instrumentation.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { Resource } = require('@opentelemetry/resources');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');

// Define your service information
const resource = new Resource({
'service.name': 'node-js-service',
'service.version': '1.0.0',
});

// Initialize NodeSDK with trace configuration
const sdk = new NodeSDK({
resource,
spanProcessor: new BatchSpanProcessor(new OTLPTraceExporter({
url: 'http://scout-collector:4318/v1/traces'
})),
});

// Start the SDK
sdk.start();

// Graceful shutdown
process.on('SIGTERM', () => {
sdk.shutdown()
.then(() => console.log('Tracing terminated'))
.catch((error) => console.log('Error terminating tracing', error))
.finally(() => process.exit(0));
});

Application Setup

Note: Import instrumentation before any other modules

// app.js
'use strict';
require('./instrumentation');

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

const app = express();
// ... rest of your application setup

Span

Creating Spans in Express Routes

const express = require('express');
const { trace, context } = require('@opentelemetry/api');
const tracer = trace.getTracer('node-js-service');

const router = express.Router();

router.get('/ping', async (req, res) => {
const span = tracer.startSpan('get-ping');
const ctx = trace.setSpan(context.active(), span);

try {
await context.with(ctx, ()) => {
// Your route logic here
//Your logs passed with trace context
res.json({ message: 'pong' });
});
} finally {
span.end();
}
});

Span Attributes, Events, and Status

Spans can be enriched with additional context using attributes, events and status indicators. These features help in better understanding and debugging the behavior of your application.

Complete Span Example

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

router.get('/ping', async (req, res) => {
// Start a new span
const span = tracer.startSpan('handle-ping');
const ctx = trace.setSpan(context.active(), span);

try {
// Add basic attributes to the span
span.setAttributes({
'http.method': req.method,
'http.route': '/ping',
'request.size': JSON.stringify(req.body).length
});

// Add an event for the request
span.addEvent('Received ping request', {
'timestamp': new Date().toISOString()
});

// Simple response with timestamp
const response = {
status: 'ok',
message: 'pong',
timestamp: new Date().toISOString()
};

// Log the successful response
span.addEvent('Sent pong response', {
'timestamp': response.timestamp
});

// Return the response
res.status(200).json(response);

// Set span status to OK
span.setStatus({ code: 1 }); // 1 = OK, 2 = Error

res.status(200).json(response);
} catch (error) {
// Handle error
res.status(400).json({ error: error.message });
} finally {
// Always end the span
span.end();
}
});

Metrics

To start collecting metrics, you'll need to initialize the NodeSDK with metrics configuration.

Sample Reference code for Metrics Initialization

// instrumentation.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http');
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics');

const sdk = new NodeSDK({
resource,
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://scout-collector:4318/v1/metrics'
}),
}),
});

sdk.start();

Application Setup

// index.js
const { metrics } = require('@opentelemetry/api');

const meter = metrics.getMeter('node-js-service');

Metrics types

Counter

Creating a Synchronous Counter
const { metrics } = require('@opentelemetry/api');

const meter = metrics.getMeter('node-js-service');

// Create a counter
const requestCounter = meter.createCounter('http_requests_total', {
description: 'Total number of HTTP requests',
});

// Middleware to count requests
app.use((req, res, next) => {
requestCounter.add(1, {
method: req.method,
route: req.route?.path || 'unknown',
});
next();
});

Histogram

Creating a Histogram
const meter = metrics.getMeter('node-js-service');

const requestDurationHistogram = meter.createHistogram(
'http_request_duration_seconds', {
description: 'HTTP request duration in seconds',
boundaries: [0.01, 0.05, 0.1, 0.5, 1, 5]
});

// Middleware to track request duration
app.use((req, res, next) => {
const startTime = performance.now();

res.on('finish', () => {
const duration = (performance.now() - startTime) / 1000; // Convert to seconds
requestDurationHistogram.record(duration, {
method: req.method,
route: req.route?.path || 'unknown',
status: res.statusCode,
});
});

next();
});

Logs

Configure logs export in your instrumentation setup.

// instrumentation.js
const { BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');

const sdk = new NodeSDK({
resource,
logRecordProcessor: new BatchLogRecordProcessor(new OTLPLogExporter({
url: 'http://scout-collector:4318/v1/logs'
})),
});

Creating Logs

const logAPI = require('@opentelemetry/api-logs');

const logger = logAPI.logs.getLogger('node-js-service');

// Health check endpoint with logging
router.get('/ping', async (req, res) => {
const span = tracer.startSpan('health-check');

try {
const healthStatus = {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
};

// Emit log with health check details
logger.emit({
body: 'Health check performed',
severityNumber: logAPI.SeverityNumber.INFO,
attributes: {
status: healthStatus.status,
uptime: healthStatus.uptime,
endpoint: req.url,
timestamp: healthStatus.timestamp
},
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
});

res.json(healthStatus);
});

View your complete telemetry data in the base14 Scout observability platform. Click to know more

References