Monitoring & APM
Logs tell you what happened on individual requests; monitoring tells you how the whole system is behaving right now and over time. APM — Application Performance Monitoring — goes further, tracing a single request across services, surfacing slow database queries, and pinpointing the line of code behind a latency spike. Together with logs and traces, metrics form the "three pillars of observability." This page covers metrics vs logs vs traces, the key signals to watch (the RED and USE methods), distributed tracing, APM tools, and how to monitor without drowning in noise.
The three pillars of observability
Pillar | Answers | Example |
|---|---|---|
Logs | What happened on this specific event? | "payment declined for order 9981, code card_declined" |
Metrics | How is the system behaving in aggregate? | Requests/sec, p95 latency, error rate, memory usage |
Traces | Where did the time go across services? | Request spent 12ms in API, 340ms in DB, 80ms in payment svc |
What to measure — RED and USE
RED method — for request-driven services (what users feel): Rate → requests per second Errors → failed requests per second (or error %) Duration → latency distribution (p50 / p95 / p99) USE method — for resources (what infrastructure shows): Utilization → % time the resource was busy (CPU, event loop) Saturation → queued/waiting work (event-loop lag, conn pool waits) Errors → error counts (failed allocations, dropped conns)
Node-specific signals to watch
Event-loop lag — the #1 Node health metric. Rising lag means the single thread is blocked and every request slows down.
Heap usage & GC — climbing heap that never drops signals a memory leak; frequent long GC pauses hurt latency.
Active handles/requests — leaking sockets, timers, or DB connections.
Connection pool saturation — requests waiting for a free DB connection is a common hidden bottleneck.
Process restarts / crashes — a process manager restarting often points to uncaught exceptions or OOM kills.
External dependency latency — DB, cache, and third-party API response times, since they dominate most request durations.
Distributed tracing with OpenTelemetry
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
tracing.js — load this BEFORE your app (node --require ./tracing.js app.js)
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 }),
// Auto-instruments http, express, pg, redis, etc. — no code changes:
instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()APM tools
Category | Examples |
|---|---|
Hosted APM | Datadog, New Relic, Dynatrace, Elastic APM, Sentry (errors + perf) |
Metrics + dashboards | Prometheus (scrape |
Tracing backends | Jaeger, Grafana Tempo, Zipkin, Honeycomb |
Instrumentation | OpenTelemetry (vendor-neutral), |
Exposing Prometheus metrics with prom-client
import client from 'prom-client'
client.collectDefaultMetrics() // CPU, heap, event-loop lag, GC...
const httpDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration',
labelNames: ['method', 'route', 'status'],
})
app.get('/metrics', async (req, res) => {
res.set('Content-Type', client.register.contentType)
res.end(await client.register.metrics()) // Prometheus scrapes this endpoint
})