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 Scout 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​
- For complete setup example refer to sample-full-stack application
- Official OpenTelemetry Node.js Documentation
- OpenTelemetry API Documentation
- OpenTelemetry Semantic Conventions
Related Guides​
- Docker Compose Setup - Set up collector for local development
- Kubernetes Helm Setup - Production deployment
- Express.js Auto-Instrumentation - Auto-instrumentation for Express.js applications