Monitoring Node.js Applications

Why Monitor?

Monitoring helps detect issues, track performance, understand user behavior, and ensure application health in production.

Application Performance Monitoring (APM)

New Relic

require('newrelic');
const express = require('express');
const app = express();

// Automatic instrumentation
app.get('/api/users', async (req, res) => {
  const users = await User.find();
  res.json(users);
});

Datadog

const tracer = require('dd-trace').init();

app.get('/api/products', async (req, res) => {
  const span = tracer.startSpan('get.products');
  
  try {
    const products = await Product.find();
    res.json(products);
  } finally {
    span.finish();
  }
});

Logging

Winston

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

// Usage
logger.info('Server started', { port: 3000 });
logger.error('Database error', { error: err.message });

Morgan (HTTP Logging)

const morgan = require('morgan');

// Predefined formats
app.use(morgan('combined')); // Apache combined format
app.use(morgan('dev')); // Development format

// Custom format
app.use(morgan(':method :url :status :response-time ms'));

// Log to file
const fs = require('fs');
const accessLogStream = fs.createWriteStream(
  path.join(__dirname, 'access.log'),
  { flags: 'a' }
);

app.use(morgan('combined', { stream: accessLogStream }));

Metrics Collection

prom-client (Prometheus)

const client = require('prom-client');

// Create a Registry
const register = new client.Registry();

// Add default metrics
client.collectDefaultMetrics({ register });

// Custom metrics
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  registers: [register]
});

// Middleware
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestDuration
      .labels(req.method, req.route?.path || req.path, res.statusCode)
      .observe(duration);
  });
  
  next();
});

// Metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

Health Checks

Comprehensive Health Endpoint

app.get('/health', async (req, res) => {
  const health = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    status: 'healthy',
    checks: {}
  };
  
  try {
    // Database check
    await mongoose.connection.db.admin().ping();
    health.checks.database = 'healthy';
  } catch (err) {
    health.checks.database = 'unhealthy';
    health.status = 'unhealthy';
  }
  
  try {
    // Redis check
    await redis.ping();
    health.checks.redis = 'healthy';
  } catch (err) {
    health.checks.redis = 'unhealthy';
    health.status = 'unhealthy';
  }
  
  // Memory check
  const used = process.memoryUsage();
  health.checks.memory = {
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`
  };
  
  const statusCode = health.status === 'healthy' ? 200 : 503;
  res.status(statusCode).json(health);
});

Error Tracking

Sentry

const Sentry = require('@sentry/node');

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: 1.0
});

// Error handler
app.use(Sentry.Handlers.errorHandler());

// Manual error capture
try {
  await riskyOperation();
} catch (err) {
  Sentry.captureException(err);
  throw err;
}

Performance Monitoring

Memory Monitoring

setInterval(() => {
  const used = process.memoryUsage();
  
  logger.info('Memory usage', {
    rss: `${Math.round(used.rss / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    external: `${Math.round(used.external / 1024 / 1024)}MB`
  });
}, 60000);

CPU Monitoring

const os = require('os');

setInterval(() => {
  const cpus = os.cpus();
  const usage = cpus.map(cpu => {
    const total = Object.values(cpu.times).reduce((a, b) => a + b);
    const idle = cpu.times.idle;
    return ((total - idle) / total) * 100;
  });
  
  logger.info('CPU usage', {
    average: usage.reduce((a, b) => a + b) / usage.length,
    cores: usage
  });
}, 60000);

Request Tracking

Request ID Middleware

const { v4: uuidv4 } = require('uuid');

app.use((req, res, next) => {
  req.id = req.headers['x-request-id'] || uuidv4();
  res.setHeader('X-Request-ID', req.id);
  next();
});

// Use in logging
app.use((req, res, next) => {
  logger.info('Request received', {
    requestId: req.id,
    method: req.method,
    url: req.url,
    ip: req.ip
  });
  next();
});

Alerting

Custom Alerts

const alertThresholds = {
  errorRate: 0.05, // 5%
  responseTime: 1000, // 1 second
  memoryUsage: 0.9 // 90%
};

function checkAlerts() {
  const metrics = getMetrics();
  
  if (metrics.errorRate > alertThresholds.errorRate) {
    sendAlert('High error rate', metrics.errorRate);
  }
  
  if (metrics.avgResponseTime > alertThresholds.responseTime) {
    sendAlert('Slow response time', metrics.avgResponseTime);
  }
  
  const memUsage = process.memoryUsage().heapUsed / process.memoryUsage().heapTotal;
  if (memUsage > alertThresholds.memoryUsage) {
    sendAlert('High memory usage', memUsage);
  }
}

setInterval(checkAlerts, 60000);

Dashboard

Express Status Monitor

const expressStatusMonitor = require('express-status-monitor');

app.use(expressStatusMonitor({
  title: 'API Status',
  path: '/status',
  spans: [{
    interval: 1,
    retention: 60
  }],
  chartVisibility: {
    cpu: true,
    mem: true,
    load: true,
    responseTime: true,
    rps: true,
    statusCodes: true
  }
}));

Best Practices

  1. Log everything important but avoid sensitive data
  2. Use structured logging (JSON format)
  3. Implement health checks for all dependencies
  4. Track custom metrics relevant to your business
  5. Set up alerts for critical issues
  6. Monitor resource usage (CPU, memory, disk)
  7. Track error rates and response times
  8. Use APM tools in production

Interview Tips

  • Explain monitoring importance: Detect issues, track performance
  • Show logging: Winston, Morgan for HTTP logs
  • Demonstrate metrics: Prometheus, custom metrics
  • Discuss health checks: Database, Redis, memory
  • Mention APM tools: New Relic, Datadog
  • Show error tracking: Sentry integration

Summary

Monitor Node.js applications using logging (Winston, Morgan), metrics collection (Prometheus), health checks, APM tools (New Relic, Datadog), and error tracking (Sentry). Track memory, CPU, response times, and error rates. Set up alerts for critical issues.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Node.js Knowledge

Ready to put your skills to the test? Take our interactive Node.js quiz and get instant feedback on your answers.