admin e5c32ad25a Add health check utility, logger, metrics, backup, and SMTP toggle scripts
- Implemented a comprehensive health check utility to monitor system dependencies including NocoDB, SMTP, Represent API, disk space, and memory usage.
- Created a logger utility using Winston for structured logging with daily rotation and various log levels.
- Developed a metrics utility using Prometheus client to track application performance metrics such as email sends, HTTP requests, and user activity.
- Added a backup script for automated backups of NocoDB data, uploaded files, and environment configurations with optional S3 support.
- Introduced a toggle script to switch between development (MailHog) and production (ProtonMail) SMTP configurations.
2025-10-23 11:33:00 -06:00

277 lines
7.7 KiB
JavaScript

const client = require('prom-client');
// Create a Registry to register the metrics
const register = new client.Registry();
// Add default metrics (CPU, memory, etc.)
client.collectDefaultMetrics({
register,
prefix: 'influence_app_'
});
// Custom metrics for the Influence application
// Email metrics
const emailsSentTotal = new client.Counter({
name: 'influence_emails_sent_total',
help: 'Total number of emails sent successfully',
labelNames: ['campaign_id', 'representative_level'],
registers: [register]
});
const emailsFailedTotal = new client.Counter({
name: 'influence_emails_failed_total',
help: 'Total number of emails that failed to send',
labelNames: ['campaign_id', 'error_type'],
registers: [register]
});
const emailQueueSize = new client.Gauge({
name: 'influence_email_queue_size',
help: 'Current number of emails in the queue',
registers: [register]
});
const emailSendDuration = new client.Histogram({
name: 'influence_email_send_duration_seconds',
help: 'Time taken to send an email',
labelNames: ['campaign_id'],
buckets: [0.1, 0.5, 1, 2, 5, 10],
registers: [register]
});
// API metrics
const httpRequestDuration = new client.Histogram({
name: 'influence_http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
registers: [register]
});
const httpRequestsTotal = new client.Counter({
name: 'influence_http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code'],
registers: [register]
});
// User metrics
const activeUsersGauge = new client.Gauge({
name: 'influence_active_users_total',
help: 'Number of currently active users',
registers: [register]
});
const userRegistrationsTotal = new client.Counter({
name: 'influence_user_registrations_total',
help: 'Total number of user registrations',
registers: [register]
});
const loginAttemptsTotal = new client.Counter({
name: 'influence_login_attempts_total',
help: 'Total number of login attempts',
labelNames: ['status'],
registers: [register]
});
// Campaign metrics
const campaignCreationsTotal = new client.Counter({
name: 'influence_campaign_creations_total',
help: 'Total number of campaigns created',
registers: [register]
});
const campaignParticipationTotal = new client.Counter({
name: 'influence_campaign_participation_total',
help: 'Total number of campaign participations',
labelNames: ['campaign_id'],
registers: [register]
});
const activeCampaignsGauge = new client.Gauge({
name: 'influence_active_campaigns_total',
help: 'Number of currently active campaigns',
registers: [register]
});
const campaignConversionRate = new client.Gauge({
name: 'influence_campaign_conversion_rate',
help: 'Campaign conversion rate (participants / visitors)',
labelNames: ['campaign_id'],
registers: [register]
});
// Representative metrics
const representativeLookupTotal = new client.Counter({
name: 'influence_representative_lookup_total',
help: 'Total number of representative lookups',
labelNames: ['lookup_type'],
registers: [register]
});
const representativeResponsesTotal = new client.Counter({
name: 'influence_representative_responses_total',
help: 'Total number of representative responses received',
labelNames: ['representative_level'],
registers: [register]
});
const representativeResponseRate = new client.Gauge({
name: 'influence_representative_response_rate',
help: 'Representative response rate (responses / emails sent)',
labelNames: ['representative_level'],
registers: [register]
});
// Rate limiting metrics
const rateLimitHitsTotal = new client.Counter({
name: 'influence_rate_limit_hits_total',
help: 'Total number of rate limit hits',
labelNames: ['endpoint', 'limit_type'],
registers: [register]
});
// External service metrics
const externalServiceRequestsTotal = new client.Counter({
name: 'influence_external_service_requests_total',
help: 'Total number of requests to external services',
labelNames: ['service', 'status'],
registers: [register]
});
const externalServiceLatency = new client.Histogram({
name: 'influence_external_service_latency_seconds',
help: 'Latency of external service requests',
labelNames: ['service'],
buckets: [0.1, 0.5, 1, 2, 5, 10],
registers: [register]
});
// Geographic metrics
const participationByPostalCode = new client.Counter({
name: 'influence_participation_by_postal_code_total',
help: 'Participation count by postal code prefix',
labelNames: ['postal_prefix'],
registers: [register]
});
// Middleware to track HTTP requests
const metricsMiddleware = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const route = req.route ? req.route.path : req.path;
const labels = {
method: req.method,
route: route,
status_code: res.statusCode
};
httpRequestDuration.observe(labels, duration);
httpRequestsTotal.inc(labels);
});
next();
};
// Helper functions for common metric operations
const metrics = {
// Email metrics
recordEmailSent: (campaignId, representativeLevel) => {
emailsSentTotal.inc({ campaign_id: campaignId, representative_level: representativeLevel });
},
recordEmailFailed: (campaignId, errorType) => {
emailsFailedTotal.inc({ campaign_id: campaignId, error_type: errorType });
},
setEmailQueueSize: (size) => {
emailQueueSize.set(size);
},
observeEmailSendDuration: (campaignId, durationSeconds) => {
emailSendDuration.observe({ campaign_id: campaignId }, durationSeconds);
},
// User metrics
setActiveUsers: (count) => {
activeUsersGauge.set(count);
},
recordUserRegistration: () => {
userRegistrationsTotal.inc();
},
recordLoginAttempt: (success) => {
loginAttemptsTotal.inc({ status: success ? 'success' : 'failed' });
},
// Campaign metrics
recordCampaignCreation: () => {
campaignCreationsTotal.inc();
},
recordCampaignParticipation: (campaignId) => {
campaignParticipationTotal.inc({ campaign_id: campaignId });
},
setActiveCampaigns: (count) => {
activeCampaignsGauge.set(count);
},
setCampaignConversionRate: (campaignId, rate) => {
campaignConversionRate.set({ campaign_id: campaignId }, rate);
},
// Representative metrics
recordRepresentativeLookup: (lookupType) => {
representativeLookupTotal.inc({ lookup_type: lookupType });
},
recordRepresentativeResponse: (representativeLevel) => {
representativeResponsesTotal.inc({ representative_level: representativeLevel });
},
setRepresentativeResponseRate: (representativeLevel, rate) => {
representativeResponseRate.set({ representative_level: representativeLevel }, rate);
},
// Rate limiting metrics
recordRateLimitHit: (endpoint, limitType) => {
rateLimitHitsTotal.inc({ endpoint, limit_type: limitType });
},
// External service metrics
recordExternalServiceRequest: (service, success) => {
externalServiceRequestsTotal.inc({ service, status: success ? 'success' : 'failed' });
},
observeExternalServiceLatency: (service, durationSeconds) => {
externalServiceLatency.observe({ service }, durationSeconds);
},
// Geographic metrics
recordParticipationByPostalCode: (postalCode) => {
const prefix = postalCode.substring(0, 3).toUpperCase();
participationByPostalCode.inc({ postal_prefix: prefix });
},
// Get metrics endpoint handler
getMetrics: async () => {
return await register.metrics();
},
// Get content type for metrics
getContentType: () => {
return register.contentType;
},
// Middleware
middleware: metricsMiddleware
};
module.exports = metrics;