Website Monitoring and Uptime Strategies: A Developer's Guide
A practical Fusebox guide to website monitoring and uptime strategies.
Website Monitoring and Uptime Strategies: A Developer's Guide
Downtime is expensive. Amazon loses $220,000 per minute when their site is down. For smaller businesses, even brief outages can mean lost customers who never return. Yet many companies only discover they're offline when customers complain on social media. This guide provides comprehensive monitoring strategies to ensure you know about problems before your users do.
Why Monitoring Matters
In 2017, a typo brought down Amazon S3, taking with it a significant portion of the internet. Companies with proper monitoring detected the issue in seconds and switched to failover systems. Those without monitoring lost hours of revenue. The difference between 99.9% uptime (8.76 hours of downtime per year) and 99.99% (52.56 minutes) can mean millions in revenue and reputation.
Comprehensive Uptime Monitor
Build a robust uptime monitoring system:
// Comprehensive Uptime Monitoring System
class UptimeMonitor {
constructor(config) {
this.config = {
url: config.url,
interval: config.interval || 60000, // 1 minute default
timeout: config.timeout || 30000, // 30 seconds default
retries: config.retries || 3,
regions: config.regions || ['us-east', 'eu-west', 'asia-pacific']
};
this.status = {
current: 'unknown',
uptime: 0,
downtime: 0,
lastCheck: null,
history: [],
incidents: []
};
this.alertChannels = [];
this.checks = new Map();
}
// Start monitoring
start() {
console.log(`Starting uptime monitoring for ${this.config.url}`);
// Initial check
this.performCheck();
// Set up regular checks
this.interval = setInterval(() => {
this.performCheck();
}, this.config.interval);
// Set up multi-region checks
this.startRegionalChecks();
}
// Perform uptime check
async performCheck() {
const startTime = Date.now();
const checkResult = {
timestamp: new Date().toISOString(),
responseTime: null,
statusCode: null,
status: 'unknown',
error: null,
region: 'primary'
};
try {
const response = await this.checkEndpoint(this.config.url);
checkResult.responseTime = Date.now() - startTime;
checkResult.statusCode = response.status;
checkResult.status = response.ok ? 'up' : 'down';
// Additional checks
await this.performContentCheck(response, checkResult);
await this.performPerformanceCheck(checkResult);
} catch (error) {
checkResult.status = 'down';
checkResult.error = error.message;
checkResult.responseTime = Date.now() - startTime;
}
// Update status
this.updateStatus(checkResult);
// Store history
this.status.history.push(checkResult);
if (this.status.history.length > 1440) { // Keep 24 hours at 1-minute intervals
this.status.history.shift();
}
return checkResult;
}
// Check endpoint with retries
async checkEndpoint(url, retries = this.config.retries) {
for (let i = 0; i < retries; i++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
const response = await fetch(url, {
method: 'GET',
signal: controller.signal,
cache: 'no-cache',
headers: {
'User-Agent': 'UptimeMonitor/1.0'
}
});
clearTimeout(timeoutId);
return response;
} catch (error) {
if (i === retries - 1) throw error;
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}
// Check content validity
async performContentCheck(response, checkResult) {
try {
const text = await response.text();
// Check for expected content
if (this.config.expectedContent) {
checkResult.contentValid = text.includes(this.config.expectedContent);
if (!checkResult.contentValid) {
checkResult.status = 'degraded';
checkResult.error = 'Expected content not found';
}
}
// Check for error indicators
const errorIndicators = [
'500 Internal Server Error',
'503 Service Unavailable',
'Database connection failed',
'Maintenance mode'
];
errorIndicators.forEach(indicator => {
if (text.includes(indicator)) {
checkResult.status = 'error';
checkResult.error = `Error indicator found: ${indicator}`;
}
});
} catch (error) {
checkResult.contentError = error.message;
}
}
// Performance threshold check
async performPerformanceCheck(checkResult) {
// Response time thresholds
const thresholds = {
excellent: 200,
good: 1000,
acceptable: 3000
};
if (checkResult.responseTime > thresholds.acceptable) {
checkResult.performance = 'poor';
if (checkResult.status === 'up') {
checkResult.status = 'slow';
}
} else if (checkResult.responseTime > thresholds.good) {
checkResult.performance = 'acceptable';
} else if (checkResult.responseTime > thresholds.excellent) {
checkResult.performance = 'good';
} else {
checkResult.performance = 'excellent';
}
}
// Update monitoring status
updateStatus(checkResult) {
const previousStatus = this.status.current;
this.status.current = checkResult.status;
this.status.lastCheck = checkResult.timestamp;
// Update uptime/downtime
if (checkResult.status === 'up' || checkResult.status === 'slow') {
this.status.uptime += this.config.interval;
} else {
this.status.downtime += this.config.interval;
}
// Detect incidents
if (previousStatus === 'up' && checkResult.status !== 'up') {
this.startIncident(checkResult);
} else if (previousStatus !== 'up' && checkResult.status === 'up') {
this.endIncident(checkResult);
}
// Calculate uptime percentage
const totalTime = this.status.uptime + this.status.downtime;
this.status.uptimePercentage = totalTime > 0 ?
((this.status.uptime / totalTime) * 100).toFixed(3) : 100;
}
// Start incident tracking
startIncident(checkResult) {
const incident = {
id: Date.now(),
startTime: checkResult.timestamp,
endTime: null,
duration: null,
type: checkResult.status,
error: checkResult.error,
checks: [checkResult]
};
this.status.incidents.push(incident);
// Send alerts
this.sendAlert('down', incident);
}
// End incident
endIncident(checkResult) {
const currentIncident = this.status.incidents[this.status.incidents.length - 1];
if (currentIncident && !currentIncident.endTime) {
currentIncident.endTime = checkResult.timestamp;
currentIncident.duration = Date.now() - new Date(currentIncident.startTime).getTime();
// Send recovery alert
this.sendAlert('recovered', currentIncident);
}
}
// Multi-region monitoring
startRegionalChecks() {
this.config.regions.forEach(region => {
// Simulate regional checks
setInterval(async () => {
const checkResult = await this.performRegionalCheck(region);
this.checks.set(region, checkResult);
// Detect regional issues
this.analyzeRegionalHealth();
}, this.config.interval);
});
}
// Perform check from specific region
async performRegionalCheck(region) {
// In practice, this would use regional endpoints or services
const startTime = Date.now();
try {
// Simulate regional latency
const regionalLatency = {
'us-east': 50,
'eu-west': 150,
'asia-pacific': 250
};
await new Promise(resolve =>
setTimeout(resolve, regionalLatency[region] || 100)
);
const response = await this.checkEndpoint(this.config.url);
return {
region,
status: response.ok ? 'up' : 'down',
responseTime: Date.now() - startTime,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
region,
status: 'down',
error: error.message,
responseTime: Date.now() - startTime,
timestamp: new Date().toISOString()
};
}
}
// Analyze regional health
analyzeRegionalHealth() {
const regionalStatuses = Array.from(this.checks.values());
const downRegions = regionalStatuses.filter(check => check.status === 'down');
if (downRegions.length > 0 && downRegions.length < regionalStatuses.length) {
// Partial outage
this.sendAlert('partial-outage', {
affectedRegions: downRegions.map(r => r.region),
totalRegions: regionalStatuses.length
});
} else if (downRegions.length === regionalStatuses.length) {
// Global outage
this.sendAlert('global-outage', {
regions: downRegions.map(r => r.region)
});
}
}
// Send alerts
sendAlert(type, data) {
const alert = {
type,
timestamp: new Date().toISOString(),
url: this.config.url,
data
};
// Send to all configured channels
this.alertChannels.forEach(channel => {
channel.send(alert);
});
console.warn('Alert:', alert);
}
// Add alert channel
addAlertChannel(channel) {
this.alertChannels.push(channel);
}
// Get status report
getStatusReport() {
return {
url: this.config.url,
status: this.status.current,
uptimePercentage: this.status.uptimePercentage,
lastCheck: this.status.lastCheck,
incidents: {
total: this.status.incidents.length,
active: this.status.incidents.filter(i => !i.endTime).length,
recent: this.status.incidents.slice(-5)
},
performance: this.calculatePerformanceMetrics(),
regions: Array.from(this.checks.entries()).map(([region, check]) => ({
region,
status: check.status,
responseTime: check.responseTime
}))
};
}
// Calculate performance metrics
calculatePerformanceMetrics() {
const recentChecks = this.status.history.slice(-60); // Last hour
if (recentChecks.length === 0) return null;
const responseTimes = recentChecks
.filter(check => check.responseTime)
.map(check => check.responseTime);
return {
average: responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length,
min: Math.min(...responseTimes),
max: Math.max(...responseTimes),
p95: this.percentile(responseTimes, 0.95),
p99: this.percentile(responseTimes, 0.99)
};
}
// Calculate percentile
percentile(arr, p) {
const sorted = arr.slice().sort((a, b) => a - b);
const index = Math.ceil(sorted.length * p) - 1;
return sorted[index];
}
// Stop monitoring
stop() {
clearInterval(this.interval);
console.log('Uptime monitoring stopped');
}
}
// Alert channel implementations
class EmailAlertChannel {
constructor(config) {
this.config = config;
}
send(alert) {
// Email implementation
console.log(`Email alert: ${alert.type} for ${alert.url}`);
}
}
class SlackAlertChannel {
constructor(webhookUrl) {
this.webhookUrl = webhookUrl;
}
async send(alert) {
const message = {
text: `🚨 ${alert.type.toUpperCase()}: ${alert.url}`,
attachments: [{
color: alert.type.includes('down') ? 'danger' : 'good',
fields: [
{
title: 'Status',
value: alert.type,
short: true
},
{
title: 'Time',
value: alert.timestamp,
short: true
}
]
}]
};
// Send to Slack
console.log('Slack alert:', message);
}
}
// Usage
const monitor = new UptimeMonitor({
url: 'https://example.com',
interval: 60000,
expectedContent: 'Welcome'
});
monitor.addAlertChannel(new EmailAlertChannel({ to: 'alerts@example.com' }));
monitor.addAlertChannel(new SlackAlertChannel('https://hooks.slack.com/...'));
monitor.start();
Advanced Health Check System
Implement comprehensive health checks:
// Advanced Health Check System
class HealthCheckSystem {
constructor() {
this.checks = new Map();
this.results = new Map();
this.dependencies = new Map();
}
// Register health check
registerCheck(name, check, options = {}) {
this.checks.set(name, {
name,
check,
critical: options.critical || false,
timeout: options.timeout || 5000,
interval: options.interval || 30000,
dependencies: options.dependencies || []
});
// Start periodic check
this.startCheck(name);
}
// Start periodic health check
startCheck(name) {
const checkConfig = this.checks.get(name);
// Initial check
this.runCheck(name);
// Periodic checks
setInterval(() => {
this.runCheck(name);
}, checkConfig.interval);
}
// Run individual check
async runCheck(name) {
const checkConfig = this.checks.get(name);
const startTime = Date.now();
const result = {
name,
timestamp: new Date().toISOString(),
status: 'unknown',
responseTime: null,
details: {},
error: null
};
try {
// Check dependencies first
const depStatus = await this.checkDependencies(checkConfig.dependencies);
if (!depStatus.healthy) {
result.status = 'degraded';
result.error = `Dependencies unhealthy: ${depStatus.failed.join(', ')}`;
result.responseTime = Date.now() - startTime;
this.results.set(name, result);
return result;
}
// Run check with timeout
const checkPromise = checkConfig.check();
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Check timeout')), checkConfig.timeout)
);
const checkResult = await Promise.race([checkPromise, timeoutPromise]);
result.status = checkResult.healthy ? 'healthy' : 'unhealthy';
result.details = checkResult.details || {};
result.responseTime = Date.now() - startTime;
} catch (error) {
result.status = 'unhealthy';
result.error = error.message;
result.responseTime = Date.now() - startTime;
}
this.results.set(name, result);
return result;
}
// Check dependencies
async checkDependencies(dependencies) {
if (dependencies.length === 0) {
return { healthy: true, failed: [] };
}
const failed = [];
for (const dep of dependencies) {
const depResult = this.results.get(dep);
if (!depResult || depResult.status !== 'healthy') {
failed.push(dep);
}
}
return {
healthy: failed.length === 0,
failed
};
}
// Get overall health status
getOverallHealth() {
const allResults = Array.from(this.results.values());
const criticalChecks = Array.from(this.checks.entries())
.filter(([_, config]) => config.critical)
.map(([name, _]) => name);
// Check critical services
const criticalResults = allResults.filter(r => criticalChecks.includes(r.name));
const criticalHealthy = criticalResults.every(r => r.status === 'healthy');
// Calculate health score
const healthyCount = allResults.filter(r => r.status === 'healthy').length;
const totalCount = allResults.length;
const healthScore = totalCount > 0 ? (healthyCount / totalCount) * 100 : 0;
return {
status: !criticalHealthy ? 'unhealthy' :
healthScore >= 90 ? 'healthy' :
healthScore >= 70 ? 'degraded' : 'unhealthy',
score: healthScore.toFixed(2),
checks: {
total: totalCount,
healthy: healthyCount,
unhealthy: totalCount - healthyCount
},
critical: {
healthy: criticalResults.filter(r => r.status === 'healthy').length,
total: criticalResults.length
},
timestamp: new Date().toISOString()
};
}
// Get detailed report
getDetailedReport() {
const overall = this.getOverallHealth();
const checkResults = Array.from(this.results.entries()).map(([name, result]) => ({
...result,
critical: this.checks.get(name).critical
}));
return {
overall,
checks: checkResults.sort((a, b) => {
// Sort by status and criticality
if (a.critical && !b.critical) return -1;
if (!a.critical && b.critical) return 1;
if (a.status === 'unhealthy' && b.status !== 'unhealthy') return -1;
if (a.status !== 'unhealthy' && b.status === 'unhealthy') return 1;
return 0;
}),
recommendations: this.generateRecommendations()
};
}
// Generate recommendations
generateRecommendations() {
const recommendations = [];
this.results.forEach((result, name) => {
if (result.status === 'unhealthy') {
const config = this.checks.get(name);
recommendations.push({
check: name,
priority: config.critical ? 'critical' : 'high',
issue: result.error || 'Check failed',
action: `Investigate and fix ${name} service`
});
} else if (result.responseTime > 5000) {
recommendations.push({
check: name,
priority: 'medium',
issue: 'Slow response time',
action: `Optimize ${name} performance`
});
}
});
return recommendations;
}
}
// Example health checks
const healthSystem = new HealthCheckSystem();
// Database health check
healthSystem.registerCheck('database', async () => {
try {
// Simulate database query
const startTime = Date.now();
await new Promise(resolve => setTimeout(resolve, 100));
return {
healthy: true,
details: {
responseTime: Date.now() - startTime,
connections: 45,
maxConnections: 100
}
};
} catch (error) {
return {
healthy: false,
details: { error: error.message }
};
}
}, { critical: true });
// API health check
healthSystem.registerCheck('api', async () => {
try {
const response = await fetch('/api/health');
return {
healthy: response.ok,
details: {
statusCode: response.status,
version: response.headers.get('x-api-version')
}
};
} catch (error) {
return {
healthy: false,
details: { error: error.message }
};
}
}, { critical: true });
// Cache health check
healthSystem.registerCheck('cache', async () => {
try {
// Simulate cache check
const testKey = 'health-check-' + Date.now();
localStorage.setItem(testKey, 'test');
const value = localStorage.getItem(testKey);
localStorage.removeItem(testKey);
return {
healthy: value === 'test',
details: {
type: 'localStorage',
available: true
}
};
} catch (error) {
return {
healthy: false,
details: { error: error.message }
};
}
}, { dependencies: ['database'] });
Real User Monitoring (RUM)
Monitor actual user experience:
// Real User Monitoring System
class RealUserMonitoring {
constructor() {
this.metrics = {
navigation: [],
resources: [],
errors: [],
interactions: []
};
this.sessionId = this.generateSessionId();
this.userId = this.getUserId();
this.initializeMonitoring();
}
// Initialize monitoring
initializeMonitoring() {
// Navigation timing
this.collectNavigationTiming();
// Resource timing
this.collectResourceTiming();
// Error monitoring
this.setupErrorMonitoring();
// User interaction monitoring
this.setupInteractionMonitoring();
// Web Vitals
this.collectWebVitals();
// Send metrics periodically
this.startMetricReporting();
}
// Collect navigation timing
collectNavigationTiming() {
if ('performance' in window && 'getEntriesByType' in performance) {
const navigation = performance.getEntriesByType('navigation')[0];
if (navigation) {
const timing = {
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId,
url: window.location.href,
// Key metrics
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
// Detailed timing
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
ssl: navigation.secureConnectionStart > 0 ?
navigation.connectEnd - navigation.secureConnectionStart : 0,
ttfb: navigation.responseStart - navigation.requestStart,
download: navigation.responseEnd - navigation.responseStart,
domProcessing: navigation.domComplete - navigation.domInteractive,
// Total time
totalTime: navigation.loadEventEnd - navigation.fetchStart
};
this.metrics.navigation.push(timing);
}
}
}
// Collect resource timing
collectResourceTiming() {
if ('performance' in window && 'getEntriesByType' in performance) {
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
const timing = {
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
url: resource.name,
type: resource.initiatorType,
// Size
transferSize: resource.transferSize,
encodedBodySize: resource.encodedBodySize,
decodedBodySize: resource.decodedBodySize,
// Timing
duration: resource.duration,
dns: resource.domainLookupEnd - resource.domainLookupStart,
tcp: resource.connectEnd - resource.connectStart,
ttfb: resource.responseStart - resource.startTime,
download: resource.responseEnd - resource.responseStart
};
this.metrics.resources.push(timing);
});
}
}
// Setup error monitoring
setupErrorMonitoring() {
// JavaScript errors
window.addEventListener('error', (event) => {
this.metrics.errors.push({
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId,
type: 'javascript',
message: event.message,
filename: event.filename,
line: event.lineno,
column: event.colno,
stack: event.error?.stack,
url: window.location.href
});
});
// Promise rejections
window.addEventListener('unhandledrejection', (event) => {
this.metrics.errors.push({
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId,
type: 'unhandled-promise',
reason: event.reason,
promise: event.promise,
url: window.location.href
});
});
// Resource errors
window.addEventListener('error', (event) => {
if (event.target !== window) {
this.metrics.errors.push({
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId,
type: 'resource',
tagName: event.target.tagName,
src: event.target.src || event.target.href,
message: 'Resource failed to load',
url: window.location.href
});
}
}, true);
}
// Setup interaction monitoring
setupInteractionMonitoring() {
// Click tracking
document.addEventListener('click', (event) => {
const target = event.target;
const interaction = {
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId,
type: 'click',
target: {
tagName: target.tagName,
id: target.id,
className: target.className,
text: target.textContent?.substring(0, 50)
},
coordinates: {
x: event.clientX,
y: event.clientY
},
url: window.location.href
};
this.metrics.interactions.push(interaction);
});
// Form submissions
document.addEventListener('submit', (event) => {
const form = event.target;
this.metrics.interactions.push({
timestamp: new Date().toISOString(),
sessionId: this.sessionId,
userId: this.userId,
type: 'form-submit',
form: {
id: form.id,
action: form.action,
method: form.method
},
url: window.location.href
});
});
}
// Collect Web Vitals
collectWebVitals() {
// Largest Contentful Paint
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.metrics.webVitals = {
...this.metrics.webVitals,
lcp: {
value: entry.startTime,
element: entry.element?.tagName
}
};
}
}).observe({ entryTypes: ['largest-contentful-paint'] });
// First Input Delay
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
this.metrics.webVitals = {
...this.metrics.webVitals,
fid: {
value: entry.processingStart - entry.startTime,
eventType: entry.name
}
};
}
}).observe({ entryTypes: ['first-input'] });
// Cumulative Layout Shift
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
this.metrics.webVitals = {
...this.metrics.webVitals,
cls: { value: clsValue }
};
}).observe({ entryTypes: ['layout-shift'] });
}
// Send metrics to server
async sendMetrics() {
const payload = {
sessionId: this.sessionId,
userId: this.userId,
timestamp: new Date().toISOString(),
metrics: {
navigation: this.metrics.navigation,
resources: this.metrics.resources.slice(-50), // Last 50 resources
errors: this.metrics.errors,
interactions: this.metrics.interactions.slice(-20), // Last 20 interactions
webVitals: this.metrics.webVitals
},
context: {
userAgent: navigator.userAgent,
screenResolution: `${screen.width}x${screen.height}`,
viewport: `${window.innerWidth}x${window.innerHeight}`,
connection: navigator.connection?.effectiveType
}
};
// Send to monitoring endpoint
try {
await fetch('/api/rum', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
// Clear sent metrics
this.metrics.resources = [];
this.metrics.errors = [];
this.metrics.interactions = [];
} catch (error) {
console.error('Failed to send RUM metrics:', error);
}
}
// Start metric reporting
startMetricReporting() {
// Send initial metrics after page load
window.addEventListener('load', () => {
setTimeout(() => this.sendMetrics(), 1000);
});
// Send metrics periodically
setInterval(() => this.sendMetrics(), 30000);
// Send metrics before page unload
window.addEventListener('beforeunload', () => {
this.sendMetrics();
});
}
// Helper methods
generateSessionId() {
return 'session-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
}
getUserId() {
let userId = localStorage.getItem('rum-user-id');
if (!userId) {
userId = 'user-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('rum-user-id', userId);
}
return userId;
}
}
// Initialize RUM
const rum = new RealUserMonitoring();
Status Page Generator
Create public status pages:
// Status Page Generator
class StatusPageGenerator {
constructor(monitors) {
this.monitors = monitors;
this.incidents = [];
this.maintenances = [];
}
// Generate status page HTML
generateHTML() {
const overallStatus = this.calculateOverallStatus();
const serviceStatuses = this.getServiceStatuses();
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Status</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, sans-serif; background: #f5f5f5; }
.container { max-width: 1000px; margin: 0 auto; padding: 20px; }
.status-hero {
background: ${overallStatus.color};
color: white;
padding: 40px;
border-radius: 10px;
text-align: center;
margin-bottom: 30px;
}
.status-hero h1 { font-size: 36px; margin-bottom: 10px; }
.status-hero p { font-size: 18px; opacity: 0.9; }
.services {
background: white;
border-radius: 10px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.service-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #eee;
}
.service-item:last-child { border-bottom: none; }
.service-name { font-weight: 500; }
.service-status {
display: flex;
align-items: center;
gap: 10px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #4CAF50;
}
.status-indicator.degraded { background: #FF9800; }
.status-indicator.down { background: #f44336; }
.uptime-graph {
display: flex;
gap: 1px;
height: 40px;
margin: 20px 0;
}
.uptime-bar {
flex: 1;
background: #4CAF50;
position: relative;
cursor: pointer;
}
.uptime-bar.degraded { background: #FF9800; }
.uptime-bar.down { background: #f44336; }
.uptime-bar:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
white-space: nowrap;
font-size: 12px;
}
.incidents {
background: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.incident {
padding: 20px 0;
border-bottom: 1px solid #eee;
}
.incident:last-child { border-bottom: none; }
.incident-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.incident-title { font-weight: 600; }
.incident-status {
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.incident-status.resolved {
background: #e8f5e9;
color: #2e7d32;
}
.incident-status.ongoing {
background: #ffebee;
color: #c62828;
}
.incident-update {
margin-top: 10px;
padding-left: 20px;
border-left: 3px solid #e0e0e0;
}
.update-time {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="status-hero">
<h1>${overallStatus.message}</h1>
<p>${overallStatus.description}</p>
</div>
<div class="services">
<h2>Service Status</h2>
${serviceStatuses.map(service => `
<div class="service-item">
<div class="service-name">${service.name}</div>
<div class="service-status">
<span>${service.uptime}% uptime</span>
<div class="status-indicator ${service.status}"></div>
</div>
</div>
`).join('')}
<h3 style="margin-top: 30px;">90 Day Uptime</h3>
<div class="uptime-graph">
${this.generateUptimeGraph()}
</div>
</div>
<div class="incidents">
<h2>Recent Incidents</h2>
${this.incidents.length === 0 ?
'<p style="color: #666; margin-top: 20px;">No recent incidents</p>' :
this.incidents.map(incident => `
<div class="incident">
<div class="incident-header">
<div class="incident-title">${incident.title}</div>
<div class="incident-status ${incident.resolved ? 'resolved' : 'ongoing'}">
${incident.resolved ? 'Resolved' : 'Ongoing'}
</div>
</div>
${incident.updates.map(update => `
<div class="incident-update">
<div class="update-time">${update.time}</div>
<div>${update.message}</div>
</div>
`).join('')}
</div>
`).join('')
}
</div>
</div>
<script>
// Auto-refresh every 60 seconds
setTimeout(() => location.reload(), 60000);
</script>
</body>
</html>
`;
}
// Calculate overall status
calculateOverallStatus() {
const statuses = this.monitors.map(m => m.getStatusReport());
const hasDown = statuses.some(s => s.status === 'down');
const hasDegraded = statuses.some(s => s.status === 'degraded');
if (hasDown) {
return {
status: 'down',
message: 'System Outage',
description: 'Some services are currently experiencing issues',
color: '#f44336'
};
} else if (hasDegraded) {
return {
status: 'degraded',
message: 'Partial Outage',
description: 'Some services are operating in a degraded state',
color: '#FF9800'
};
} else {
return {
status: 'operational',
message: 'All Systems Operational',
description: 'All services are running normally',
color: '#4CAF50'
};
}
}
// Get service statuses
getServiceStatuses() {
return this.monitors.map(monitor => {
const report = monitor.getStatusReport();
return {
name: new URL(report.url).hostname,
status: report.status,
uptime: report.uptimePercentage || '100.00'
};
});
}
// Generate uptime graph
generateUptimeGraph() {
// Simulate 90 days of uptime data
const days = [];
for (let i = 89; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
// Random status for demo
const rand = Math.random();
const status = rand > 0.95 ? 'down' : rand > 0.9 ? 'degraded' : 'up';
days.push(`<div class="uptime-bar ${status}" data-tooltip="${date.toLocaleDateString()}: ${status}"></div>`);
}
return days.join('');
}
// Add incident
addIncident(incident) {
this.incidents.unshift(incident);
if (this.incidents.length > 10) {
this.incidents.pop();
}
}
// Schedule maintenance
scheduleMaintenance(maintenance) {
this.maintenances.push(maintenance);
}
}
// Usage
const statusPage = new StatusPageGenerator([monitor]);
// document.body.innerHTML = statusPage.generateHTML();
Best Practices for Monitoring
- Monitor from Multiple Locations: Single location monitoring can miss regional issues
- Set Realistic Thresholds: Avoid alert fatigue with appropriate limits
- Test Complete User Journeys: Not just homepage availability
- Include Dependencies: Monitor third-party services you rely on
- Implement Escalation: Different alerts for different severity levels
- Monitor Performance: Not just uptime, but response times
- Track Error Rates: High error rates indicate problems
- Use Synthetic Monitoring: Complement RUM with synthetic tests
- Create Status Pages: Keep users informed during incidents
- Practice Incident Response: Regular drills improve real incident handling
Common Monitoring Failures
- Single Point of Failure: Monitoring system goes down with the site
- Alert Fatigue: Too many false positives lead to ignored alerts
- Incomplete Coverage: Missing critical user paths
- No Performance Monitoring: Site is "up" but unusably slow
- Ignoring Dependencies: Third-party service failures
- No Historical Data: Can't identify trends or patterns
- Poor Alert Routing: Right alerts don't reach right people
- No Automated Response: Manual intervention for everything
- Missing Context: Alerts without enough information to act
- No Post-Mortems: Not learning from incidents
Remember: The best monitoring system is one that tells you about problems before your users notice them. Invest in comprehensive monitoring—the cost of downtime far exceeds the cost of prevention.