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.