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.
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
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
