Sarıkaya Dev Logo

Implementing Observability in JavaScript with OpenTelemetry: Tracing, Metrics, and Logging

Mahmut Sarıkaya 4 min read 7 Views 0
Implementing Observability in JavaScript with OpenTelemetry: Tracing, Metrics, and Logging

Introduction

Ever wondered why a single slow API call can cripple an entire web experience? In modern single‑page applications, latency spikes, memory leaks, and silent failures often go unnoticed until users start complaining. OpenTelemetry offers a unified, vendor‑agnostic way to surface those hidden problems through tracing, metrics, and logging—all from within JavaScript code.

System Requirements and Installation

Before adding any instrumentation, ensure your environment meets these basics:

  • Node.js 14 or newer (LTS recommended)
  • npm 6+ or Yarn 1.22+
  • Access to an OTLP‑compatible collector (Jaeger, Prometheus, or a cloud service)

Install the core packages with a single command:

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

These modules provide the API surface, the Node SDK, automatic HTTP instrumentation, and OTLP exporters for both traces and metrics.

Setting Up a Tracer Provider

The first step is to create a tracer that records distributed spans. The following snippet configures a basic SDK, registers HTTP instrumentation, and sends data to a local Jaeger collector listening on port 4318.

const { NodeTracerProvider } = require("@opentelemetry/sdk-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http");
const { registerInstrumentations } = require("@opentelemetry/instrumentation");
const { HttpInstrumentation } = require("@opentelemetry/instrumentation-http");

const provider = new NodeTracerProvider();
provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" })));
provider.register();

registerInstrumentations({
instrumentations: [new HttpInstrumentation()],
});

const tracer = provider.getTracer("example-js-app");
tracer.startActiveSpan("main", span => {
// Application logic goes here
span.end();
});

Notice the use of SimpleSpanProcessor for quick demos; production systems typically prefer BatchSpanProcessor to reduce overhead.

Collecting Metrics with the Metrics SDK

Metrics give you a quantitative view of performance—request rates, latency percentiles, or memory usage. The code below creates a Meter, defines a counter for HTTP requests, and binds a value recorder for response time.

const { MeterProvider } = require("@opentelemetry/sdk-metrics");
const { OTLPMetricExporter } = require("@opentelemetry/exporter-metrics-otlp-http");

const meterProvider = new MeterProvider({
exporter: new OTLPMetricExporter({ url: "http://localhost:4318/v1/metrics" }),
interval: 60000,
});
const meter = meterProvider.getMeter("example-js-metrics");

const requestCounter = meter.createCounter("http_requests_total", {
description: "Total number of HTTP requests",
});
const latencyHistogram = meter.createHistogram("http_request_duration_ms", {
description: "Duration of HTTP requests in milliseconds",
});

function recordMetrics(labels, durationMs) {
requestCounter.add(1, labels);
latencyHistogram.record(durationMs, labels);
}

Hook recordMetrics into your HTTP middleware so every incoming request updates both the counter and the histogram. Over time you will see latency distributions that help you set realistic SLOs.

Integrating Structured Logging

OpenTelemetry does not replace a logger; it complements it by injecting trace identifiers into log records. Using a popular library like winston, you can enrich each log line with traceId and spanId retrieved from the current context.

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

const logger = winston.createLogger({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(info => {
const span = trace.getSpan(context.active());
const traceId = span ? span.spanContext().traceId : "—";
const spanId = span ? span.spanContext().spanId : "—";
return `${info.timestamp} [trace:${traceId}/${spanId}] ${info.level}: ${info.message}`;
})
),
transports: [new winston.transports.Console()],
});

logger.info("User profile fetched successfully");

This approach guarantees that any log entry can be correlated with its trace in a backend UI, turning isolated console output into a searchable artifact.

Putting It All Together in an Express Service

Below is a minimal Express server that demonstrates end‑to‑end observability. The server registers OpenTelemetry, instruments incoming HTTP calls, records a custom metric, and logs with trace context.

const express = require("express");
require("./otel-setup"); // contains tracer & meter initialization from above
const { recordMetrics } = require("./metrics");
const logger = require("./logger");

const app = express();
app.get("/api/users/:id", async (req, res) => {
const start = Date.now();
// Simulate async DB call
await new Promise(r => setTimeout(r, Math.random() * 200));
const duration = Date.now() - start;
recordMetrics({ route: "/api/users/:id" }, duration);
logger.info(`Fetched user ${req.params.id} in ${duration}ms`);
res.json({ id: req.params.id, name: "John Doe" });
});

app.listen(3000, () => {
logger.info("Server listening on port 3000");
});

Run the service, generate traffic with curl or a load tester, and watch traces appear in Jaeger, metrics in Prometheus, and logs in your preferred log aggregation tool. The three signals together provide a complete picture of health and performance.

Conclusion

Implementing observability in JavaScript no longer requires juggling disparate libraries. OpenTelemetry unifies tracing, metrics, and logging under a single API, making it possible to instrument a Node.js application in under an hour. By configuring a tracer provider, exposing key metrics, and enriching logs with context, teams can detect bottlenecks, verify SLAs, and debug production incidents with confidence.

Sources

OpenTelemetry JavaScript Documentation; Jaeger Tracing Official Guide; Prometheus Metrics Best Practices

Author: Mahmut Sarıkaya — sarikayadev.com

Tags: #OpenTelemetry #JavaScript observability #distributed tracing #metrics collection #logging
Share:
M

Written by

Mahmut Sarıkaya

Software Developer

Comments

No comments yet. Be the first to share your thoughts!

Leave a Comment

5 + 6 =