Cloud Native 50 min read

Master OpenTelemetry: From Basics to Full‑Stack Tracing in Node.js

This comprehensive guide explains observability concepts, introduces OpenTelemetry’s three signals—traces, metrics, and logs—and walks through setting up automatic and manual instrumentation for Node.js applications, configuring the OpenTelemetry Collector, deploying with Docker Compose, and visualizing data in Zipkin or Jaeger.

MoonWebTeam
MoonWebTeam
MoonWebTeam
Master OpenTelemetry: From Basics to Full‑Stack Tracing in Node.js

Background

Micro‑service architectures accelerate development but make it hard to understand service dependencies, especially after deployment; observability provides the visibility needed to answer operational questions quickly.

What Is Observability?

Observability measures how well a system’s internal state can be inferred from external data such as logs, traces, and metrics, enabling rapid detection and resolution of issues.

OpenTelemetry Overview

OpenTelemetry (OTel) unifies the three signals—logs, traces, and metrics—into a single, vendor‑agnostic framework. It evolved from OpenTracing and OpenCensus and now offers standardized SDKs, APIs, and a Collector for data ingestion, processing, and export.

Core Concepts

Traces : Represent end‑to‑end request paths across services.

Metrics : Quantitative measurements such as latency, error rate, and CPU usage.

Logs : Structured text records that can be attached to spans.

Node.js Automatic Instrumentation

Install the SDK and auto‑instrumentation packages, create a tracing.js file, and configure a ConsoleSpanExporter or an OTLPTraceExporter to send data to the Collector.

npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
// 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({
  traceExporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
  instrumentations: [getNodeAutoInstrumentations()]
});
sdk.start();

Run the application with node --require ./tracing.js app.js to generate spans automatically for Express routes and HTTP calls.

Manual Instrumentation

For custom logic, create a tracer, start spans, set attributes, add events, and handle errors.

// tracer.js
const { trace, SpanStatusCode } = require("@opentelemetry/api");
const tracer = trace.getTracer("my-service");
function handler(req, res) {
  const span = tracer.startSpan("handler");
  // business logic …
  if (error) {
    span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
  }
  span.end();
}
module.exports = { tracer };

OpenTelemetry Collector Configuration

The Collector receives data via OTLP, processes it, and exports to back‑ends such as Zipkin or Jaeger. A minimal otel-collector-config.yaml might look like:

receivers:
  otlp:
    protocols:
      grpc:
      http:
exporters:
  zipkin:
    endpoint: "http://zipkin:9411/api/v2/spans"
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [zipkin]
      processors: [batch]

Deploy with Docker Compose

Combine Zipkin and the Collector in a docker-compose.yml file.

version: "2"
services:
  zipkin:
    image: openzipkin/zipkin
    ports: ["9411:9411"]
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.81.0
    command: ["--config=/conf/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/conf/otel-collector-config.yaml
    ports:
      - "4317:4317"
      - "4318:4318"
    depends_on: [zipkin]

After starting the stack ( docker-compose up -d), send requests to the Node.js service and view the traces in Zipkin or Jaeger.

Front‑End Instrumentation

Use @opentelemetry/sdk-trace-web to create a web tracer, inject the context into HTTP headers, and start spans around fetch calls.

// tracer.ts
import { WebTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-web";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { Resource } from "@opentelemetry/resources";
import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions";
const provider = new WebTracerProvider({
  resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: "frontend" })
});
provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({ url: "/otel/v1/traces" })));
provider.register();
export const tracer = provider.getTracer("frontend");

In a React component, start a span, inject the context into axios headers, and end the span after the request.

const span = tracer.startSpan("fetchBooks");
const headers = {};
propagation.inject(context.active(), headers);
axios.get("/api/catalog/books", { headers })
  .then(res => { setBooks(res.data); span.setStatus({ code: SpanStatusCode.OK }); })
  .catch(err => { span.setStatus({ code: SpanStatusCode.ERROR, message: err.message }); })
  .finally(() => span.end());

References

OpenTelemetry documentation: https://opentelemetry.io/

Cloud Tencent article on OpenTelemetry: https://cloud.tencent.com/developer/article/2327988

Node.jsOpenTelemetryTracing
MoonWebTeam
Written by

MoonWebTeam

Official account of MoonWebTeam. All members are former front‑end engineers from Tencent, and the account shares valuable team tech insights, reflections, and other information.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.