- 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.
277 lines
7.7 KiB
JavaScript
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;
|