Monitoring & Metrics

Comprehensive monitoring and metrics collection to track performance, identify issues, and optimize application behavior.

Monitoring & Metrics

Implement comprehensive monitoring and metrics collection to maintain optimal performance and quickly identify issues.

Application Performance Monitoring (APM)

Core Metrics Collection

Track essential performance indicators:

// lib/monitoring/metrics.ts
export class PerformanceMetrics {
  private static metrics = new Map<string, number[]>()
  
  static recordMetric(name: string, value: number) {
    if (!this.metrics.has(name)) {
      this.metrics.set(name, [])
    }
    
    const values = this.metrics.get(name)!
    values.push(value)
    
    // Keep only last 1000 values
    if (values.length > 1000) {
      values.shift()
    }
  }
  
  static getMetric(name: string) {
    const values = this.metrics.get(name) || []
    if (values.length === 0) return null
    
    const sorted = [...values].sort((a, b) => a - b)
    return {
      count: values.length,
      avg: values.reduce((a, b) => a + b) / values.length,
      min: sorted[0],
      max: sorted[sorted.length - 1],
      p50: sorted[Math.floor(sorted.length * 0.5)],
      p95: sorted[Math.floor(sorted.length * 0.95)],
      p99: sorted[Math.floor(sorted.length * 0.99)],
    }
  }
  
  static getAllMetrics() {
    const result: Record<string, any> = {}
    for (const [name] of this.metrics) {
      result[name] = this.getMetric(name)
    }
    return result
  }
}

Request Tracking Middleware

Monitor API endpoint performance:

// middleware/performance-tracking.ts
import { NextRequest, NextResponse } from 'next/server'

export async function performanceMiddleware(request: NextRequest) {
  const startTime = Date.now()
  const url = new URL(request.url)
  
  // Process request
  const response = NextResponse.next()
  
  // Record metrics
  const duration = Date.now() - startTime
  const endpoint = `${request.method} ${url.pathname}`
  
  PerformanceMetrics.recordMetric(`request.${endpoint}.duration`, duration)
  PerformanceMetrics.recordMetric('request.total.count', 1)
  
  if (response.status >= 400) {
    PerformanceMetrics.recordMetric(`request.${endpoint}.errors`, 1)
  }
  
  // Add performance headers
  response.headers.set('X-Response-Time', `${duration}ms`)
  response.headers.set('X-Request-ID', crypto.randomUUID())
  
  return response
}

Database Monitoring

Query Performance Tracking

Monitor database query performance:

// lib/monitoring/db-monitoring.ts
import { Pool } from 'pg'

export class DatabaseMonitoring {
  private static pool: Pool
  
  static initializePool() {
    this.pool = new Pool({
      connectionString: process.env.DATABASE_URL,
    })
    
    // Monitor pool events
    this.pool.on('connect', () => {
      PerformanceMetrics.recordMetric('db.connections.created', 1)
    })
    
    this.pool.on('remove', () => {
      PerformanceMetrics.recordMetric('db.connections.removed', 1)
    })
    
    this.pool.on('error', (err) => {
      console.error('Database pool error:', err)
      PerformanceMetrics.recordMetric('db.errors', 1)
    })
  }
  
  static async query(text: string, params?: any[]) {
    const startTime = Date.now()
    
    try {
      const result = await this.pool.query(text, params)
      const duration = Date.now() - startTime
      
      // Record performance metrics
      PerformanceMetrics.recordMetric('db.query.duration', duration)
      PerformanceMetrics.recordMetric('db.query.count', 1)
      PerformanceMetrics.recordMetric('db.rows.returned', result.rowCount || 0)
      
      return result
    } catch (error) {
      PerformanceMetrics.recordMetric('db.query.errors', 1)
      throw error
    }
  }
  
  static async getPoolStats() {
    return {
      totalCount: this.pool.totalCount,
      idleCount: this.pool.idleCount,
      waitingCount: this.pool.waitingCount,
    }
  }
}

Slow Query Detection

Identify and log slow database queries:

// lib/monitoring/slow-query-detector.ts
export class SlowQueryDetector {
  private static slowQueryThreshold = 1000 // 1 second
  private static slowQueries: Array<{
    query: string
    duration: number
    timestamp: Date
    params?: any[]
  }> = []
  
  static setThreshold(milliseconds: number) {
    this.slowQueryThreshold = milliseconds
  }
  
  static recordQuery(query: string, duration: number, params?: any[]) {
    if (duration > this.slowQueryThreshold) {
      this.slowQueries.push({
        query,
        duration,
        timestamp: new Date(),
        params: params?.map(p => typeof p === 'string' && p.length > 100 ? `${p.substring(0, 100)}...` : p)
      })
      
      // Keep only last 100 slow queries
      if (this.slowQueries.length > 100) {
        this.slowQueries.shift()
      }
      
      console.warn(`Slow query detected (${duration}ms):`, query.substring(0, 200))
    }
  }
  
  static getSlowQueries() {
    return this.slowQueries
  }
  
  static clearSlowQueries() {
    this.slowQueries = []
  }
}

System Resource Monitoring

Memory Usage Tracking

Monitor Node.js memory usage:

// lib/monitoring/memory-monitoring.ts
export class MemoryMonitoring {
  static startMonitoring(intervalMs: number = 30000) {
    setInterval(() => {
      const usage = process.memoryUsage()
      
      PerformanceMetrics.recordMetric('memory.rss', usage.rss / 1024 / 1024) // MB
      PerformanceMetrics.recordMetric('memory.heapUsed', usage.heapUsed / 1024 / 1024)
      PerformanceMetrics.recordMetric('memory.heapTotal', usage.heapTotal / 1024 / 1024)
      PerformanceMetrics.recordMetric('memory.external', usage.external / 1024 / 1024)
      
      // Check for memory leaks
      const heapUsed = usage.heapUsed / 1024 / 1024
      if (heapUsed > 500) { // 500MB threshold
        console.warn(`High memory usage detected: ${heapUsed.toFixed(2)}MB`)
      }
    }, intervalMs)
  }
  
  static getMemoryReport() {
    const usage = process.memoryUsage()
    return {
      rss: `${(usage.rss / 1024 / 1024).toFixed(2)}MB`,
      heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)}MB`,
      heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(2)}MB`,
      external: `${(usage.external / 1024 / 1024).toFixed(2)}MB`,
    }
  }
}

Event Loop Monitoring

Track event loop performance:

// lib/monitoring/event-loop-monitoring.ts
export class EventLoopMonitoring {
  static startMonitoring(intervalMs: number = 10000) {
    setInterval(() => {
      const start = process.hrtime.bigint()
      
      setImmediate(() => {
        const delta = process.hrtime.bigint() - start
        const lagMs = Number(delta) / 1000000 // Convert to milliseconds
        
        PerformanceMetrics.recordMetric('eventloop.lag', lagMs)
        
        if (lagMs > 100) { // 100ms threshold
          console.warn(`Event loop lag detected: ${lagMs.toFixed(2)}ms`)
        }
      })
    }, intervalMs)
  }
}

Custom Metrics Dashboard

Metrics API Endpoint

Expose metrics through API:

// app/api/metrics/route.ts
import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {
  try {
    const metrics = {
      application: PerformanceMetrics.getAllMetrics(),
      database: await DatabaseMonitoring.getPoolStats(),
      memory: MemoryMonitoring.getMemoryReport(),
      slowQueries: SlowQueryDetector.getSlowQueries().slice(-10), // Last 10
      timestamp: new Date().toISOString(),
    }
    
    return NextResponse.json(metrics)
  } catch (error) {
    console.error('Metrics API error:', error)
    return NextResponse.json(
      { error: 'Failed to fetch metrics' },
      { status: 500 }
    )
  }
}

Real-time Metrics Dashboard

Create a dashboard component:

// components/monitoring/metrics-dashboard.tsx
'use client'

import { useEffect, useState } from 'react'

interface MetricsData {
  application: Record<string, any>
  database: any
  memory: any
  slowQueries: any[]
  timestamp: string
}

export function MetricsDashboard() {
  const [metrics, setMetrics] = useState<MetricsData | null>(null)
  const [loading, setLoading] = useState(true)
  
  useEffect(() => {
    const fetchMetrics = async () => {
      try {
        const response = await fetch('/api/metrics')
        const data = await response.json()
        setMetrics(data)
      } catch (error) {
        console.error('Failed to fetch metrics:', error)
      } finally {
        setLoading(false)
      }
    }
    
    fetchMetrics()
    const interval = setInterval(fetchMetrics, 30000) // Update every 30 seconds
    
    return () => clearInterval(interval)
  }, [])
  
  if (loading) return <div>Loading metrics...</div>
  if (!metrics) return <div>Failed to load metrics</div>
  
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
      {/* Request Metrics */}
      <div className="bg-white p-6 rounded-lg shadow">
        <h3 className="text-lg font-semibold mb-4">Request Performance</h3>
        {Object.entries(metrics.application)
          .filter(([key]) => key.includes('request'))
          .map(([key, value]: [string, any]) => (
            <div key={key} className="mb-2">
              <span className="text-sm text-gray-600">{key}:</span>
              <span className="ml-2 font-mono">
                {value?.avg ? `${value.avg.toFixed(2)}ms avg` : 'N/A'}
              </span>
            </div>
          ))}
      </div>
      
      {/* Database Metrics */}
      <div className="bg-white p-6 rounded-lg shadow">
        <h3 className="text-lg font-semibold mb-4">Database</h3>
        <div className="space-y-2">
          <div>Total Connections: {metrics.database.totalCount}</div>
          <div>Idle Connections: {metrics.database.idleCount}</div>
          <div>Waiting: {metrics.database.waitingCount}</div>
        </div>
      </div>
      
      {/* Memory Metrics */}
      <div className="bg-white p-6 rounded-lg shadow">
        <h3 className="text-lg font-semibold mb-4">Memory Usage</h3>
        <div className="space-y-2">
          <div>RSS: {metrics.memory.rss}</div>
          <div>Heap Used: {metrics.memory.heapUsed}</div>
          <div>Heap Total: {metrics.memory.heapTotal}</div>
        </div>
      </div>
    </div>
  )
}

Alerting System

Performance Alerts

Set up automatic alerts for performance issues:

// lib/monitoring/alerting.ts
export class AlertingSystem {
  private static thresholds = {
    responseTime: 1000, // 1 second
    errorRate: 0.05, // 5%
    memoryUsage: 512, // 512MB
    eventLoopLag: 100, // 100ms
  }
  
  static checkAlerts() {
    const metrics = PerformanceMetrics.getAllMetrics()
    
    // Check response time
    const responseTime = metrics['request.total.duration']
    if (responseTime?.avg > this.thresholds.responseTime) {
      this.sendAlert('High Response Time', `Average response time: ${responseTime.avg.toFixed(2)}ms`)
    }
    
    // Check error rate
    const totalRequests = metrics['request.total.count']?.count || 0
    const errorCount = Object.keys(metrics)
      .filter(key => key.includes('.errors'))
      .reduce((sum, key) => sum + (metrics[key]?.count || 0), 0)
      
    const errorRate = totalRequests > 0 ? errorCount / totalRequests : 0
    if (errorRate > this.thresholds.errorRate) {
      this.sendAlert('High Error Rate', `Error rate: ${(errorRate * 100).toFixed(2)}%`)
    }
    
    // Check memory usage
    const memoryUsage = metrics['memory.heapUsed']?.avg
    if (memoryUsage && memoryUsage > this.thresholds.memoryUsage) {
      this.sendAlert('High Memory Usage', `Memory usage: ${memoryUsage.toFixed(2)}MB`)
    }
  }
  
  private static async sendAlert(title: string, message: string) {
    console.error(`ALERT: ${title} - ${message}`)
    
    // Send to external monitoring service
    try {
      await fetch(process.env.WEBHOOK_URL || '', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title, message, timestamp: new Date() })
      })
    } catch (error) {
      console.error('Failed to send alert:', error)
    }
  }
}

Comprehensive monitoring and metrics collection enable proactive performance management and rapid issue identification.