Monitoring Node.js Applications: APM, Logging, and Error Tracking | SoniNow Blog

Limited TimeLearn More

node.jsmonitoringapmerror trackingobservability

Monitoring Node.js Applications: APM, Logging, and Error Tracking

Published

2026-06-23

Read Time

4 mins

Monitoring Node.js Applications: APM, Logging, and Error Tracking

Production Node.js applications fail in ways that local testing never reveals. Memory leaks accumulate over hours. Third-party API latency spikes cascade through your request chain. Uncaught exceptions terminate the process without a trace. A proper monitoring stack—combining APM, structured logging, and error tracking—provides the observability needed to detect, diagnose, and resolve issues before they impact users.

APM: Application Performance Monitoring

Application Performance Monitoring tools like Datadog, New Relic, and OpenTelemetry provide end-to-end visibility into request traces, database query performance, and external API calls. OpenTelemetry is the open standard that many modern monitoring platforms build on:

// instrumentation.ts (Next.js or generic Node.js)
import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'my-app',
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV,
  }),
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
  }),
  instrumentations: [getNodeAutoInstrumentations()],
})

sdk.start()

With auto-instrumentation, every incoming HTTP request, outgoing fetch call, database query, and Redis operation is automatically traced without modifying your application code. Traces reveal the exact millisecond breakdown of each request, showing whether the bottleneck is your database, an external API, or your own logic.

Structured Logging with Pino

Console logs are insufficient for production debugging. Structured logging outputs each log entry as a JSON object with consistent fields for indexing, filtering, and searching in log aggregation tools:

import pino from 'pino'

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport:
    process.env.NODE_ENV === 'development'
      ? { target: 'pino-pretty' }
      : undefined,
  redact: {
    paths: ['req.headers.authorization', 'req.body.password', 'user.email'],
    censor: '[REDACTED]',
  },
})

// Usage
logger.info({ userId, action: 'checkout.completed', orderTotal: 49.99 }, 'Order processed')
logger.error({ err, requestId }, 'Payment processing failed')

Key practices for structured logging:

  • Include requestId or traceId in every log entry to correlate logs across services
  • Never log raw SQL queries or user passwords—use Pino's redact configuration
  • Use consistent field names (userId, err, durationMs) across your codebase
  • Set appropriate log levels: error for failures, warn for degraded states, info for business events

Error Tracking with Sentry

Sentry captures unhandled exceptions and rejected promises, enriches them with source maps, and groups similar errors automatically. Integration with Next.js is straightforward:

// sentry.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 0.25, // sample 25% of transactions
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [Sentry.httpIntegration(), Sentry.prismaIntegration()],
})

Sentry's performance tracing shows the exact line of code that threw an exception, along with the request context, browser information, and the state of the application at the time of the error. Use Sentry.captureException for caught errors you want to track without crashing:

try {
  await processPayment(paymentIntent)
} catch (error) {
  Sentry.captureException(error, {
    extra: { paymentIntentId: paymentIntent.id, userId },
  })
  return NextResponse.json({ error: 'Payment failed. Please try again.' }, { status: 500 })
}

Performance Profiling and Memory Leak Detection

Node.js memory leaks are subtle and often take hours to manifest. Use heap snapshots to diagnose growing memory usage:

import v8 from 'v8'
import fs from 'fs'

export function takeHeapSnapshot() {
  const snapshotStream = v8.getHeapSnapshot()
  const fileStream = fs.createWriteStream(`/tmp/heap-${Date.now()}.heapsnapshot`)
  snapshotStream.pipe(fileStream)
}

Trigger heap snapshots automatically when RSS memory exceeds a threshold:

const MEMORY_THRESHOLD_MB = 512

setInterval(() => {
  const usage = process.memoryUsage()
  if (usage.rss > MEMORY_THRESHOLD_MB * 1024 * 1024) {
    logger.warn({ rss: usage.rss }, 'Memory threshold exceeded')
    takeHeapSnapshot()
  }
}, 60_000)

Load the .heapsnapshot file into Chrome DevTools to inspect retained objects, detached DOM nodes, and closure variables that prevent garbage collection.

Alerting Strategies

Monitoring without alerting is just logging. Define tiered alert rules:

  • Critical (SMS/call): 5xx error rate above 5% for 5 minutes, process down, p99 latency > 10s
  • Warning (email): Memory > 80%, error rate > 1%, slow queries > 1s
  • Info (dashboard): Deployments, traffic spikes, cache hit ratio changes

Use OpenTelemetry metrics to power your alerting:

import { metrics } from '@opentelemetry/api'

const meter = metrics.getMeter('my-app')
const requestDuration = meter.createHistogram('http.request.duration_ms', {
  description: 'HTTP request duration in milliseconds',
  unit: 'ms',
})

// In middleware
requestDuration.record(duration, {
  method: req.method,
  route: req.url,
  status: res.statusCode,
})

A well-instrumented Node.js application gives you visibility into every layer of the stack. APM traces reveal request bottlenecks, structured logs provide debug context, error tracking captures production failures, and profiling detects memory issues before they cause outages.

<a href="/services/web-development">Our team builds and monitors production Node.js applications</a> with observability best practices baked in. Contact us to discuss your monitoring strategy.