Observability Basics
8 examples to get you started with Observability for Node.js backends - 6 basic and 2 intermediate.
Search across all documentation pages
8 examples to get you started with Observability for Node.js backends - 6 basic and 2 intermediate.
npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-nodePin Node 24.18.0 and load the OTel SDK before other imports in production entrypoints.
Logs, metrics, and traces describe the same incident from different angles.
// Log (Pino)
logger.info({ requestId, statusCode: 200, durationMs: 45 }, "request_complete");
// Metric (counter)
requestCounter.add(1, { method: "GET", route: "/users", status: "200" });
// Trace (span attribute)
span.setAttribute("http.status_code", 200);Related: Metrics that Matter - RED method
Liveness and readiness serve different orchestrator probes.
app.get("/health", (_req, res) => res.json({ ok: true }));
app.get("/ready", async (_req, res) => {
const dbOk = await db.ping();
res.status(dbOk ? 200 : 503).json({ ready: dbOk });
});/health - process up (liveness). No dependency checks./ready - can serve traffic (readiness). Fails when DB down.error on every kube probe - sample or use metrics.Related: Resilience Basics - fail fast on boot
Rate, Errors, Duration - minimum HTTP service metrics.
import { metrics } from "@opentelemetry/api";
const meter = metrics.getMeter("orders-api");
const requestDuration = meter.createHistogram("http.server.duration", { unit: "ms" });
const requestCount = meter.createCounter("http.server.requests");
function recordRequest(method: string, route: string, status: number, ms: number) {
requestCount.add(1, { method, route, status: String(status) });
requestDuration.record(ms, { method, route, status: String(status) });
}http.server.requests counter.status=5xx.Related: SLOs & Error Budgets - SLO from histograms
Correlate logs to traces in APM UIs.
import { trace } from "@opentelemetry/api";
const span = trace.getActiveSpan();
const traceId = span?.spanContext().traceId;
logger.info({ trace_id: traceId, event: "payment_captured" }, "payment ok");trace_id.trace_id) across services.mixin can inject trace_id automatically.Related: Request Correlation IDs - requestId vs trace_id
Metrics are not only HTTP - track domain events.
const ordersCreated = meter.createCounter("orders.created");
ordersCreated.add(1, { plan: "pro", region: "eu" });userId on every metric.Related: Logging Basics - event field naming
Auto-instrumentation misses your business logic boundaries.
import { trace } from "@opentelemetry/api";
const tracer = trace.getTracer("billing");
async function capturePayment(orderId: string) {
return tracer.startActiveSpan("capturePayment", async (span) => {
span.setAttribute("order.id", orderId);
try {
return await stripeCapture(orderId);
} catch (err) {
span.recordException(err as Error);
span.setStatus({ code: 2 });
throw err;
} finally {
span.end();
}
});
}capturePayment), not function internals.recordException attaches error to trace for APM error tracking.finally - avoid leaked spans on early return.Related: OpenTelemetry Node SDK - SDK setup
Load instrumentation before Express/Fastify imports.
// src/instrumentation.ts - import first in main.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
const sdk = new NodeSDK({
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://localhost:4318/v1/traces",
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();instrumentation.ts must load before import express in entry file.fetch, and common DB drivers.Related: OpenTelemetry Node SDK - full configuration
Page on SLO burn, not every stack trace.
# Pseudo-alert rule
alert: HighErrorRate
expr: rate(http_server_requests{status=~"5.."}[5m]) / rate(http_server_requests[5m]) > 0.05
for: 10mERROR log line from one pod restart.Related: Observability Best Practices - alerting rules
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026