import client from 'prom-client'; import { env } from '../config/env'; const register = new client.Registry(); register.setDefaultLabels({ app: 'changemaker-v2-api', instance: env.INSTANCE_LABEL || env.DOMAIN || 'unknown', }); client.collectDefaultMetrics({ register }); // --- HTTP Metrics (existing) --- export const httpRequestDuration = new client.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status_code'], buckets: [0.01, 0.05, 0.1, 0.3, 0.5, 1, 3, 5, 10], registers: [register], }); export const httpRequestsTotal = new client.Counter({ name: 'http_requests_total', help: 'Total number of HTTP requests', labelNames: ['method', 'route', 'status_code'], registers: [register], }); // --- Email Metrics --- export const emailsSentTotal = new client.Counter({ name: 'cm_emails_sent_total', help: 'Total campaign emails sent successfully', labelNames: ['campaign_id'], registers: [register], }); export const emailsFailedTotal = new client.Counter({ name: 'cm_emails_failed_total', help: 'Total campaign emails that failed to send', labelNames: ['campaign_id', 'error_type'], registers: [register], }); export const emailQueueSize = new client.Gauge({ name: 'cm_email_queue_size', help: 'Current email queue size (waiting + active)', registers: [register], }); export const emailSendDuration = new client.Histogram({ name: 'cm_email_send_duration_seconds', help: 'Duration of individual email sends', buckets: [0.1, 0.5, 1, 2, 5, 10, 30], registers: [register], }); // --- Auth Metrics --- export const loginAttemptsTotal = new client.Counter({ name: 'cm_login_attempts_total', help: 'Total login attempts', labelNames: ['status'], registers: [register], }); export const activeSessions = new client.Gauge({ name: 'cm_active_sessions', help: 'Number of active refresh tokens (approximate session count)', registers: [register], }); // --- Campaign / Influence Metrics --- export const campaignEmailsTotal = new client.Counter({ name: 'cm_campaign_emails_total', help: 'Total campaign emails created (SMTP + mailto)', labelNames: ['campaign_id'], registers: [register], }); export const responseSubmissionsTotal = new client.Counter({ name: 'cm_response_submissions_total', help: 'Total response submissions from public', registers: [register], }); // --- Canvass / Map Metrics --- export const canvassVisitsTotal = new client.Counter({ name: 'cm_canvass_visits_total', help: 'Total canvass visits recorded', labelNames: ['outcome'], registers: [register], }); export const activeCanvassSessions = new client.Gauge({ name: 'cm_active_canvass_sessions', help: 'Number of active canvass sessions', registers: [register], }); export const shiftSignupsTotal = new client.Counter({ name: 'cm_shift_signups_total', help: 'Total shift signups', registers: [register], }); // --- Location Query Metrics --- export const locationQueryDuration = new client.Histogram({ name: 'cm_map_location_query_duration_seconds', help: 'Duration of location queries', labelNames: ['endpoint', 'has_bounds'], buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 1, 2, 5], registers: [register], }); export const locationQueryCount = new client.Counter({ name: 'cm_map_location_query_count_total', help: 'Total location queries', labelNames: ['endpoint', 'has_bounds'], registers: [register], }); export const locationResultCount = new client.Histogram({ name: 'cm_map_location_result_count', help: 'Number of locations returned per query', labelNames: ['endpoint'], buckets: [10, 50, 100, 500, 1000, 5000, 10000], registers: [register], }); // --- Geocoding Metrics --- export const cm_geocode_cache_hits = new client.Counter({ name: 'cm_geocode_cache_hits_total', help: 'Total geocoding cache hits', registers: [register], }); export const cm_geocode_cache_misses = new client.Counter({ name: 'cm_geocode_cache_misses_total', help: 'Total geocoding cache misses', registers: [register], }); export const cm_geocode_requests_total = new client.Counter({ name: 'cm_geocode_requests_total', help: 'Total geocoding requests', labelNames: ['provider', 'status'], registers: [register], }); export const cm_geocode_duration = new client.Histogram({ name: 'cm_geocode_duration_seconds', help: 'Duration of geocoding requests', labelNames: ['provider'], buckets: [0.1, 0.5, 1, 2, 5, 10, 30], registers: [register], }); // --- Email Template Metrics --- export const emailTemplatesUpdated = new client.Counter({ name: 'cm_email_templates_updated_total', help: 'Email templates updated', labelNames: ['template_key', 'user_role'], registers: [register], }); export const emailTestSent = new client.Counter({ name: 'cm_email_test_sent_total', help: 'Test emails sent', labelNames: ['template_key', 'success'], registers: [register], }); export const emailTemplateRollback = new client.Counter({ name: 'cm_email_template_rollback_total', help: 'Template rollbacks', labelNames: ['template_key'], registers: [register], }); export const emailTemplateCacheHit = new client.Counter({ name: 'cm_email_template_cache_hit_total', help: 'Template cache hits', labelNames: ['template_key'], registers: [register], }); export const emailTemplateCacheMiss = new client.Counter({ name: 'cm_email_template_cache_miss_total', help: 'Template cache misses', labelNames: ['template_key'], registers: [register], }); // --- Docs Editor Metrics --- export const cm_docs_cache_hits = new client.Counter({ name: 'cm_docs_cache_hits_total', help: 'Documentation cache hits', labelNames: ['type'], // 'tree' or 'file' registers: [register], }); export const cm_docs_cache_misses = new client.Counter({ name: 'cm_docs_cache_misses_total', help: 'Documentation cache misses', labelNames: ['type'], registers: [register], }); export const cm_docs_operations = new client.Counter({ name: 'cm_docs_operations_total', help: 'Documentation file operations', labelNames: ['operation'], // 'read', 'write', 'create', 'delete', 'rename' registers: [register], }); // --- External Service Health --- export const externalServiceUp = new client.Gauge({ name: 'cm_external_service_up', help: 'Whether an external service is reachable (1=up, 0=down)', labelNames: ['service'], registers: [register], }); // --- Helper Functions --- export function recordEmailSent(campaignId: string) { emailsSentTotal.inc({ campaign_id: campaignId }); } export function recordEmailFailed(campaignId: string, errorType: string = 'unknown') { emailsFailedTotal.inc({ campaign_id: campaignId, error_type: errorType }); } export function setEmailQueueSize(size: number) { emailQueueSize.set(size); } export function recordLoginAttempt(status: 'success' | 'failure') { loginAttemptsTotal.inc({ status }); } export function setActiveSessions(count: number) { activeSessions.set(count); } export function recordCampaignEmail(campaignId: string) { campaignEmailsTotal.inc({ campaign_id: campaignId }); } export function recordResponseSubmission() { responseSubmissionsTotal.inc(); } export function recordCanvassVisit(outcome: string) { canvassVisitsTotal.inc({ outcome }); } export function setActiveCanvassSessions(count: number) { activeCanvassSessions.set(count); } export function recordShiftSignup() { shiftSignupsTotal.inc(); } export function recordLocationQuery(endpoint: string, hasBounds: boolean, resultCount: number, durationSeconds: number) { locationQueryCount.inc({ endpoint, has_bounds: hasBounds.toString() }); locationQueryDuration.observe({ endpoint, has_bounds: hasBounds.toString() }, durationSeconds); locationResultCount.observe({ endpoint }, resultCount); } export function setServiceUp(service: string, up: boolean) { externalServiceUp.set({ service }, up ? 1 : 0); } export function recordEmailTemplateUpdate(templateKey: string, userRole: string) { emailTemplatesUpdated.inc({ template_key: templateKey, user_role: userRole }); } export function recordEmailTest(templateKey: string, success: boolean) { emailTestSent.inc({ template_key: templateKey, success: success.toString() }); } export function recordTemplateRollback(templateKey: string) { emailTemplateRollback.inc({ template_key: templateKey }); } export function recordTemplateCacheHit(templateKey: string) { emailTemplateCacheHit.inc({ template_key: templateKey }); } export function recordTemplateCacheMiss(templateKey: string) { emailTemplateCacheMiss.inc({ template_key: templateKey }); } export { register };