Set up comprehensive Node.js application monitoring using Prometheus metrics collection with the prom-client library and create custom Grafana dashboards for performance insights and alerting.
Prerequisites
- Node.js 16+ installed
- sudo access
- 8GB+ RAM recommended
What this solves
Node.js applications in production need real-time monitoring to track performance metrics, identify bottlenecks, and respond to issues before they impact users. This tutorial sets up Prometheus metrics collection directly in your Node.js application using the prom-client library, then creates comprehensive Grafana dashboards to visualize application performance, HTTP request patterns, and custom business metrics with automated alerting.
Step-by-step implementation
Install Prometheus server
Start by installing Prometheus to collect and store metrics from your Node.js applications.
sudo apt update
sudo apt install -y prometheus prometheus-node-exporter
sudo systemctl enable --now prometheus
sudo systemctl enable --now prometheus-node-exporter
Configure Prometheus scraping
Configure Prometheus to scrape metrics from your Node.js application endpoints.
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "/etc/prometheus/nodejs_alerts.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['localhost:9100']
- job_name: 'nodejs-app'
static_configs:
- targets: ['localhost:3000']
metrics_path: '/metrics'
scrape_interval: 10s
- job_name: 'nodejs-app-custom'
static_configs:
- targets: ['localhost:3001', 'localhost:3002']
metrics_path: '/metrics'
scrape_interval: 10s
Install Grafana for visualization
Install Grafana to create dashboards and alerts for your Node.js metrics.
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
sudo systemctl enable --now grafana-server
Create Node.js application with metrics
Set up a sample Node.js application with comprehensive Prometheus metrics collection.
mkdir ~/nodejs-monitoring && cd ~/nodejs-monitoring
npm init -y
npm install express prom-client response-time
const express = require('express');
const client = require('prom-client');
const responseTime = require('response-time');
const app = express();
const port = process.env.PORT || 3000;
// Enable collection of default metrics
client.collectDefaultMetrics({
timeout: 5000,
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5]
});
// Custom metrics
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 5, 15, 50, 100, 500, 1000]
});
const httpRequestsTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new client.Gauge({
name: 'nodejs_active_connections',
help: 'Number of active connections',
});
const businessMetric = new client.Gauge({
name: 'business_orders_processed',
help: 'Number of orders processed',
});
const errorRate = new client.Counter({
name: 'nodejs_errors_total',
help: 'Total number of errors',
labelNames: ['type', 'endpoint']
});
// Middleware to collect HTTP metrics
app.use(responseTime((req, res, time) => {
const route = req.route ? req.route.path : req.path;
const labels = {
method: req.method,
route: route,
status_code: res.statusCode
};
httpRequestDuration.observe(labels, time);
httpRequestsTotal.inc(labels);
}));
// Middleware to track active connections
app.use((req, res, next) => {
activeConnections.inc();
res.on('finish', () => {
activeConnections.dec();
});
next();
});
// Sample routes
app.get('/', (req, res) => {
res.json({ message: 'Node.js monitoring demo', timestamp: new Date().toISOString() });
});
app.get('/api/users', (req, res) => {
// Simulate processing time
setTimeout(() => {
res.json({ users: ['alice', 'bob', 'charlie'], count: 3 });
}, Math.random() * 100);
});
app.get('/api/orders', (req, res) => {
// Simulate business metric
businessMetric.inc(Math.floor(Math.random() * 5) + 1);
res.json({ orders: ['order1', 'order2'], processed: true });
});
app.get('/api/error', (req, res) => {
// Simulate error for testing
errorRate.inc({ type: 'validation', endpoint: '/api/error' });
res.status(500).json({ error: 'Simulated error for testing' });
});
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
uptime: process.uptime(),
memory: process.memoryUsage(),
timestamp: new Date().toISOString()
});
});
// Metrics endpoint for Prometheus
app.get('/metrics', async (req, res) => {
try {
res.set('Content-Type', client.register.contentType);
const metrics = await client.register.metrics();
res.send(metrics);
} catch (error) {
res.status(500).send(error.message);
}
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
process.exit(0);
});
});
const server = app.listen(port, () => {
console.log(Node.js monitoring app listening on port ${port});
console.log(Metrics available at http://localhost:${port}/metrics);
console.log(Health check at http://localhost:${port}/health);
});
module.exports = app;
Create production-ready package.json
Configure proper Node.js application scripts and dependencies for production deployment.
{
"name": "nodejs-prometheus-monitoring",
"version": "1.0.0",
"description": "Node.js application with Prometheus metrics",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "jest",
"healthcheck": "curl -f http://localhost:3000/health || exit 1"
},
"dependencies": {
"express": "^4.18.2",
"prom-client": "^14.2.0",
"response-time": "^2.3.2"
},
"devDependencies": {
"nodemon": "^3.0.1",
"jest": "^29.0.0"
},
"keywords": ["nodejs", "prometheus", "monitoring", "metrics"],
"author": "DevOps Team",
"license": "MIT"
}
Create systemd service for Node.js app
Set up a systemd service to run your Node.js application with proper monitoring and restart policies.
[Unit]
Description=Node.js Monitoring Application
After=network.target
Requires=network.target
[Service]
Type=simple
User=nodejs
Group=nodejs
WorkingDirectory=/home/nodejs/nodejs-monitoring
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodejs-monitoring
Resource limits
LimitNOFILE=65536
MemoryMax=512M
Security settings
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/home/nodejs/nodejs-monitoring/logs
[Install]
WantedBy=multi-user.target
sudo useradd -r -s /bin/false nodejs
sudo mkdir -p /home/nodejs
sudo cp -r ~/nodejs-monitoring /home/nodejs/
sudo chown -R nodejs:nodejs /home/nodejs/nodejs-monitoring
sudo systemctl daemon-reload
sudo systemctl enable --now nodejs-monitoring
Configure Prometheus alerting rules
Create alerting rules for Node.js application monitoring with threshold-based notifications.
groups:
- name: nodejs_alerts
rules:
- alert: NodejsHighResponseTime
expr: histogram_quantile(0.95, rate(http_request_duration_ms_bucket[5m])) > 500
for: 2m
labels:
severity: warning
service: nodejs
annotations:
summary: "High response time detected"
description: "95th percentile response time is {{ $value }}ms for {{ $labels.instance }}"
- alert: NodejsHighErrorRate
expr: rate(http_requests_total{status_code=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
for: 5m
labels:
severity: critical
service: nodejs
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value | humanizePercentage }} for {{ $labels.instance }}"
- alert: NodejsHighMemoryUsage
expr: nodejs_memory_heap_used_bytes / nodejs_memory_heap_total_bytes > 0.85
for: 5m
labels:
severity: warning
service: nodejs
annotations:
summary: "High memory usage"
description: "Memory usage is {{ $value | humanizePercentage }} for {{ $labels.instance }}"
- alert: NodejsApplicationDown
expr: up{job="nodejs-app"} == 0
for: 1m
labels:
severity: critical
service: nodejs
annotations:
summary: "Node.js application is down"
description: "Node.js application on {{ $labels.instance }} is not responding"
- alert: NodejsHighActiveConnections
expr: nodejs_active_connections > 100
for: 5m
labels:
severity: warning
service: nodejs
annotations:
summary: "High number of active connections"
description: "{{ $value }} active connections on {{ $labels.instance }}"
- alert: NodejsEventLoopLag
expr: nodejs_eventloop_lag_seconds > 0.1
for: 3m
labels:
severity: warning
service: nodejs
annotations:
summary: "Event loop lag detected"
description: "Event loop lag is {{ $value }}s on {{ $labels.instance }}"
- alert: NodejsGCDuration
expr: rate(nodejs_gc_duration_seconds_sum[5m]) > 0.1
for: 5m
labels:
severity: warning
service: nodejs
annotations:
summary: "High garbage collection time"
description: "GC duration is {{ $value }}s/sec on {{ $labels.instance }}"
Install and configure Alertmanager
Set up Alertmanager to handle notifications from Prometheus alerts.
sudo apt install -y prometheus-alertmanager
global:
smtp_smarthost: 'localhost:587'
smtp_from: 'alerts@example.com'
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'nodejs-alerts'
routes:
- match:
severity: critical
receiver: 'critical-alerts'
continue: true
receivers:
- name: 'nodejs-alerts'
email_configs:
- to: 'devops@example.com'
subject: 'Node.js Alert: {{ .GroupLabels.alertname }}'
body: |
{{ range .Alerts }}
Alert: {{ .Annotations.summary }}
Description: {{ .Annotations.description }}
Instance: {{ .Labels.instance }}
Severity: {{ .Labels.severity }}
{{ end }}
- name: 'critical-alerts'
email_configs:
- to: 'oncall@example.com'
subject: 'CRITICAL: {{ .GroupLabels.alertname }}'
body: |
CRITICAL ALERT TRIGGERED
{{ range .Alerts }}
Alert: {{ .Annotations.summary }}
Description: {{ .Annotations.description }}
Instance: {{ .Labels.instance }}
Time: {{ .StartsAt }}
{{ end }}
sudo systemctl enable --now alertmanager
sudo systemctl restart prometheus
Create comprehensive Grafana dashboards
Import and configure detailed Grafana dashboards for Node.js application monitoring.
curl -X POST http://admin:admin@localhost:3000/api/datasources \
-H "Content-Type: application/json" \
-d '{
"name": "Prometheus",
"type": "prometheus",
"url": "http://localhost:9090",
"access": "proxy",
"isDefault": true
}'
{
"dashboard": {
"id": null,
"title": "Node.js Application Monitoring",
"tags": ["nodejs", "prometheus", "monitoring"],
"timezone": "browser",
"panels": [
{
"id": 1,
"title": "HTTP Request Rate",
"type": "stat",
"targets": [
{
"expr": "rate(http_requests_total[5m])",
"legendFormat": "Requests/sec"
}
],
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0}
},
{
"id": 2,
"title": "Response Time Percentiles",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.50, rate(http_request_duration_ms_bucket[5m]))",
"legendFormat": "50th percentile"
},
{
"expr": "histogram_quantile(0.95, rate(http_request_duration_ms_bucket[5m]))",
"legendFormat": "95th percentile"
},
{
"expr": "histogram_quantile(0.99, rate(http_request_duration_ms_bucket[5m]))",
"legendFormat": "99th percentile"
}
],
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0}
},
{
"id": 3,
"title": "Memory Usage",
"type": "graph",
"targets": [
{
"expr": "nodejs_memory_heap_used_bytes",
"legendFormat": "Heap Used"
},
{
"expr": "nodejs_memory_heap_total_bytes",
"legendFormat": "Heap Total"
},
{
"expr": "nodejs_memory_external_bytes",
"legendFormat": "External"
}
],
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 8}
},
{
"id": 4,
"title": "Error Rate by Endpoint",
"type": "graph",
"targets": [
{
"expr": "rate(http_requests_total{status_code=~\"5..\"}[5m]) by (route)",
"legendFormat": "{{route}}"
}
],
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 8}
},
{
"id": 5,
"title": "Active Connections",
"type": "singlestat",
"targets": [
{
"expr": "nodejs_active_connections",
"legendFormat": "Active Connections"
}
],
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 16}
},
{
"id": 6,
"title": "Event Loop Lag",
"type": "singlestat",
"targets": [
{
"expr": "nodejs_eventloop_lag_seconds",
"legendFormat": "Event Loop Lag (s)"
}
],
"gridPos": {"h": 4, "w": 6, "x": 6, "y": 16}
},
{
"id": 7,
"title": "GC Duration",
"type": "graph",
"targets": [
{
"expr": "rate(nodejs_gc_duration_seconds_sum[5m]) by (kind)",
"legendFormat": "{{kind}}"
}
],
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 16}
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "10s"
}
}
curl -X POST http://admin:admin@localhost:3000/api/dashboards/db \
-H "Content-Type: application/json" \
-d @nodejs_dashboard.json
Set up advanced custom metrics
Add business-specific metrics and advanced monitoring patterns to your Node.js application.
const client = require('prom-client');
// Business metrics
const userRegistrations = new client.Counter({
name: 'user_registrations_total',
help: 'Total number of user registrations',
labelNames: ['source', 'country']
});
const orderValue = new client.Histogram({
name: 'order_value_dollars',
help: 'Order value in dollars',
labelNames: ['category', 'payment_method'],
buckets: [10, 25, 50, 100, 250, 500, 1000, 2500]
});
const activeUsers = new client.Gauge({
name: 'active_users_current',
help: 'Current number of active users',
labelNames: ['session_type']
});
const databaseConnections = new client.Gauge({
name: 'database_connections_active',
help: 'Active database connections',
labelNames: ['database', 'type']
});
const cacheHitRate = new client.Gauge({
name: 'cache_hit_rate',
help: 'Cache hit rate percentage',
labelNames: ['cache_type']
});
// Queue metrics
const queueSize = new client.Gauge({
name: 'queue_size',
help: 'Current queue size',
labelNames: ['queue_name']
});
const jobProcessingTime = new client.Histogram({
name: 'job_processing_duration_seconds',
help: 'Job processing duration',
labelNames: ['job_type', 'status'],
buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
});
// Custom middleware for detailed request tracking
const requestTracker = (req, res, next) => {
const startTime = Date.now();
const originalSend = res.send;
res.send = function(data) {
const duration = Date.now() - startTime;
const contentLength = Buffer.byteLength(data || '', 'utf8');
// Track response size
const responseSizeHistogram = new client.Histogram({
name: 'http_response_size_bytes',
help: 'Size of HTTP responses',
labelNames: ['method', 'route', 'status_code'],
buckets: [100, 1000, 5000, 10000, 50000, 100000]
});
responseSizeHistogram.observe(
{
method: req.method,
route: req.route ? req.route.path : 'unknown',
status_code: res.statusCode
},
contentLength
);
originalSend.call(this, data);
};
next();
};
// Function to simulate business metrics
const updateBusinessMetrics = () => {
// Simulate user activity
activeUsers.set({ session_type: 'web' }, Math.floor(Math.random() * 100) + 50);
activeUsers.set({ session_type: 'mobile' }, Math.floor(Math.random() * 50) + 20);
// Simulate cache performance
cacheHitRate.set({ cache_type: 'redis' }, Math.random() * 0.3 + 0.7); // 70-100%
cacheHitRate.set({ cache_type: 'memory' }, Math.random() * 0.2 + 0.8); // 80-100%
// Simulate database connections
databaseConnections.set({ database: 'primary', type: 'read' }, Math.floor(Math.random() * 10) + 5);
databaseConnections.set({ database: 'primary', type: 'write' }, Math.floor(Math.random() * 5) + 2);
// Simulate queue sizes
queueSize.set({ queue_name: 'email' }, Math.floor(Math.random() * 20));
queueSize.set({ queue_name: 'analytics' }, Math.floor(Math.random() * 50));
};
// Update business metrics every 30 seconds
setInterval(updateBusinessMetrics, 30000);
module.exports = {
userRegistrations,
orderValue,
activeUsers,
databaseConnections,
cacheHitRate,
queueSize,
jobProcessingTime,
requestTracker
};
Configure Grafana alerting
Set up Grafana alerting rules with notification channels for comprehensive monitoring.
# Create notification channel for Slack
curl -X POST http://admin:admin@localhost:3000/api/alert-notifications \
-H "Content-Type: application/json" \
-d '{
"name": "slack-nodejs-alerts",
"type": "slack",
"settings": {
"url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"channel": "#nodejs-alerts",
"username": "Grafana",
"title": "Node.js Alert",
"text": "Alert: {{ range .Alerts }}{{ .Annotations.summary }}{{ end }}"
}
}'
Create email notification channel
curl -X POST http://admin:admin@localhost:3000/api/alert-notifications \
-H "Content-Type: application/json" \
-d '{
"name": "email-nodejs-alerts",
"type": "email",
"settings": {
"addresses": "devops@example.com;oncall@example.com",
"subject": "Node.js Application Alert"
}
}'
Verify your setup
Test your Node.js monitoring stack to ensure all components are working correctly.
# Check application is running and exposing metrics
curl http://localhost:3000/health
curl http://localhost:3000/metrics | head -20
Verify Prometheus is scraping metrics
curl http://localhost:9090/api/v1/query?query=up{job="nodejs-app"}
Check Grafana dashboard access
curl -I http://localhost:3000
Test alerting by triggering an error
curl http://localhost:3000/api/error
Check service status
sudo systemctl status nodejs-monitoring
sudo systemctl status prometheus
sudo systemctl status grafana-server
sudo systemctl status alertmanager
View application logs
sudo journalctl -u nodejs-monitoring -f
http://localhost:3000 (admin/admin), Prometheus at http://localhost:9090, and your Node.js app metrics at http://localhost:3000/metrics.Load testing your monitoring
Generate traffic to test your monitoring setup and verify metrics collection works under load.
# Install Apache Bench for load testing
sudo apt install -y apache2-utils
Generate load on different endpoints
ab -n 1000 -c 10 http://localhost:3000/
ab -n 500 -c 5 http://localhost:3000/api/users
ab -n 200 -c 3 http://localhost:3000/api/orders
Test error endpoint to trigger alerts
for i in {1..10}; do curl http://localhost:3000/api/error; done
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Metrics endpoint returns 404 | Route not configured properly | Check /metrics endpoint in app.js and restart service |
| Prometheus shows target down | Application not running on expected port | Verify sudo systemctl status nodejs-monitoring and port configuration |
| Grafana shows "No data" in panels | Prometheus datasource not configured | Add Prometheus datasource at http://localhost:9090 |
| High memory usage from metrics | Too many metric labels or high cardinality | Review and limit metric labels, use client.register.clear() |
| Alerts not firing | Alertmanager not receiving rules | Check /etc/prometheus/nodejs_alerts.yml syntax with promtool check rules |
| Permission denied writing logs | Service user lacks write access | sudo chown nodejs:nodejs /home/nodejs/nodejs-monitoring/logs |
Next steps
- Configure Prometheus Alertmanager with custom webhook integrations for Slack, Microsoft Teams, and PagerDuty notifications
- Set up centralized logging with Winston and Elasticsearch for Node.js applications
- Implement Kubernetes monitoring with Prometheus and Helm charts for comprehensive cluster observability
- Configure Node.js cluster monitoring with PM2 and advanced process management
- Set up distributed tracing for Node.js microservices with Jaeger and OpenTelemetry
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Node.js Monitoring with Prometheus and Grafana Installer
# Production-grade installation script for multiple distributions
# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Global variables
NODE_PORT=${1:-3000}
PROMETHEUS_PORT=9090
GRAFANA_PORT=3001
APP_USER="nodejs-monitor"
APP_DIR="/opt/nodejs-monitoring"
# Usage function
usage() {
echo "Usage: $0 [NODE_PORT]"
echo " NODE_PORT: Port for Node.js application (default: 3000)"
exit 1
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Rolling back...${NC}"
systemctl stop prometheus grafana-server node_exporter 2>/dev/null || true
systemctl disable prometheus grafana-server node_exporter 2>/dev/null || true
userdel -r "$APP_USER" 2>/dev/null || true
rm -rf "$APP_DIR" /etc/prometheus/nodejs_alerts.yml 2>/dev/null || true
exit 1
}
trap cleanup ERR
# Check if running as root or with sudo
check_privileges() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root or with sudo${NC}"
exit 1
fi
}
# Detect distribution
detect_distro() {
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Cannot detect distribution. /etc/os-release not found.${NC}"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
PROM_PKG="prometheus prometheus-node-exporter"
PROM_CONFIG="/etc/prometheus/prometheus.yml"
NODE_EXPORTER_SERVICE="prometheus-node-exporter"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PROM_PKG="prometheus2 node_exporter"
PROM_CONFIG="/etc/prometheus/prometheus.yml"
NODE_EXPORTER_SERVICE="node_exporter"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PROM_PKG="golang-github-prometheus prometheus-node-exporter"
PROM_CONFIG="/etc/prometheus/prometheus.yml"
NODE_EXPORTER_SERVICE="prometheus-node-exporter"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
echo -e "${GREEN}Detected distribution: $PRETTY_NAME${NC}"
}
# Install Node.js
install_nodejs() {
echo -e "${YELLOW}[1/8] Installing Node.js...${NC}"
if ! command -v node &> /dev/null; then
curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash - 2>/dev/null || {
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
}
$PKG_INSTALL nodejs
fi
echo -e "${GREEN}Node.js $(node --version) installed${NC}"
}
# Install Prometheus
install_prometheus() {
echo -e "${YELLOW}[2/8] Installing Prometheus...${NC}"
$PKG_UPDATE
if [[ "$ID" =~ ^(almalinux|rocky|centos|rhel|ol)$ ]]; then
$PKG_INSTALL epel-release
fi
$PKG_INSTALL $PROM_PKG
systemctl enable prometheus
systemctl enable $NODE_EXPORTER_SERVICE
echo -e "${GREEN}Prometheus installed successfully${NC}"
}
# Configure Prometheus
configure_prometheus() {
echo -e "${YELLOW}[3/8] Configuring Prometheus...${NC}"
cat > "$PROM_CONFIG" << EOF
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "/etc/prometheus/nodejs_alerts.yml"
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['localhost:9100']
- job_name: 'nodejs-app'
static_configs:
- targets: ['localhost:$NODE_PORT']
metrics_path: '/metrics'
scrape_interval: 10s
EOF
# Create alert rules
cat > /etc/prometheus/nodejs_alerts.yml << EOF
groups:
- name: nodejs_alerts
rules:
- alert: NodeJSHighMemoryUsage
expr: process_resident_memory_bytes > 500000000
for: 2m
labels:
severity: warning
annotations:
summary: "High memory usage detected"
- alert: NodeJSHighErrorRate
expr: rate(nodejs_errors_total[5m]) > 0.1
for: 1m
labels:
severity: critical
annotations:
summary: "High error rate detected"
EOF
chown -R prometheus:prometheus /etc/prometheus/
chmod 644 /etc/prometheus/*.yml
systemctl start prometheus
systemctl start $NODE_EXPORTER_SERVICE
echo -e "${GREEN}Prometheus configured and started${NC}"
}
# Install Grafana
install_grafana() {
echo -e "${YELLOW}[4/8] Installing Grafana...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
wget -q -O - https://packages.grafana.com/gpg.key | apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" > /etc/apt/sources.list.d/grafana.list
apt update
$PKG_INSTALL grafana
else
$PKG_INSTALL https://dl.grafana.com/oss/release/grafana-10.2.0-1.x86_64.rpm
fi
systemctl enable grafana-server
systemctl start grafana-server
echo -e "${GREEN}Grafana installed and started${NC}"
}
# Create application user and directory
create_app_user() {
echo -e "${YELLOW}[5/8] Creating application user and directory...${NC}"
useradd --system --shell /bin/false --home-dir "$APP_DIR" --create-home "$APP_USER" 2>/dev/null || true
mkdir -p "$APP_DIR"
chown -R "$APP_USER":"$APP_USER" "$APP_DIR"
chmod 755 "$APP_DIR"
echo -e "${GREEN}Application user $APP_USER created${NC}"
}
# Create Node.js monitoring application
create_nodejs_app() {
echo -e "${YELLOW}[6/8] Creating Node.js monitoring application...${NC}"
cd "$APP_DIR"
# Initialize npm project as app user
sudo -u "$APP_USER" npm init -y
sudo -u "$APP_USER" npm install express prom-client response-time
# Create main application file
cat > "$APP_DIR/app.js" << 'EOF'
const express = require('express');
const client = require('prom-client');
const responseTime = require('response-time');
const app = express();
const port = process.env.PORT || 3000;
client.collectDefaultMetrics({
timeout: 5000,
gcDurationBuckets: [0.001, 0.01, 0.1, 1, 2, 5]
});
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_ms',
help: 'Duration of HTTP requests in ms',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.1, 5, 15, 50, 100, 500, 1000]
});
const httpRequestsTotal = new client.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code']
});
const activeConnections = new client.Gauge({
name: 'nodejs_active_connections',
help: 'Number of active connections',
});
const errorRate = new client.Counter({
name: 'nodejs_errors_total',
help: 'Total number of errors',
labelNames: ['type', 'endpoint']
});
app.use(responseTime((req, res, time) => {
const route = req.route ? req.route.path : req.path;
const labels = {
method: req.method,
route: route,
status_code: res.statusCode
};
httpRequestDuration.observe(labels, time);
httpRequestsTotal.inc(labels);
}));
app.get('/metrics', (req, res) => {
res.set('Content-Type', client.register.contentType);
res.end(client.register.metrics());
});
app.get('/', (req, res) => {
res.json({ message: 'Node.js Monitoring App', uptime: process.uptime() });
});
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
app.listen(port, () => {
console.log(`Monitoring app listening on port ${port}`);
});
EOF
chown "$APP_USER":"$APP_USER" "$APP_DIR/app.js"
chmod 644 "$APP_DIR/app.js"
echo -e "${GREEN}Node.js application created${NC}"
}
# Create systemd service
create_systemd_service() {
echo -e "${YELLOW}[7/8] Creating systemd service...${NC}"
cat > /etc/systemd/system/nodejs-monitor.service << EOF
[Unit]
Description=Node.js Monitoring Application
After=network.target
[Service]
Type=simple
User=$APP_USER
WorkingDirectory=$APP_DIR
Environment=NODE_ENV=production
Environment=PORT=$NODE_PORT
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable nodejs-monitor
systemctl start nodejs-monitor
echo -e "${GREEN}Systemd service created and started${NC}"
}
# Configure firewall
configure_firewall() {
echo -e "${YELLOW}[8/8] Configuring firewall...${NC}"
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=$NODE_PORT/tcp
firewall-cmd --permanent --add-port=$PROMETHEUS_PORT/tcp
firewall-cmd --permanent --add-port=$GRAFANA_PORT/tcp
firewall-cmd --reload
elif command -v ufw &> /dev/null; then
ufw allow $NODE_PORT/tcp
ufw allow $PROMETHEUS_PORT/tcp
ufw allow $GRAFANA_PORT/tcp
fi
echo -e "${GREEN}Firewall configured${NC}"
}
# Verify installation
verify_installation() {
echo -e "${YELLOW}Verifying installation...${NC}"
sleep 5
if curl -s "http://localhost:$NODE_PORT/health" | grep -q "healthy"; then
echo -e "${GREEN}✓ Node.js app is running${NC}"
else
echo -e "${RED}✗ Node.js app is not responding${NC}"
fi
if curl -s "http://localhost:$PROMETHEUS_PORT" | grep -q "Prometheus"; then
echo -e "${GREEN}✓ Prometheus is running${NC}"
else
echo -e "${RED}✗ Prometheus is not responding${NC}"
fi
if curl -s "http://localhost:3000" | grep -q "Grafana"; then
echo -e "${GREEN}✓ Grafana is running${NC}"
else
echo -e "${RED}✗ Grafana is not responding${NC}"
fi
}
# Main execution
main() {
echo -e "${GREEN}Starting Node.js Monitoring Stack Installation${NC}"
check_privileges
detect_distro
install_nodejs
install_prometheus
configure_prometheus
install_grafana
create_app_user
create_nodejs_app
create_systemd_service
configure_firewall
verify_installation
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}Access URLs:${NC}"
echo -e "Node.js App: http://localhost:$NODE_PORT"
echo -e "Prometheus: http://localhost:$PROMETHEUS_PORT"
echo -e "Grafana: http://localhost:3000 (admin/admin)"
}
# Validate arguments
[[ $# -gt 1 ]] && usage
main "$@"
Review the script before running. Execute with: bash install.sh