UNPKG

@hemantwasthere/monitoring-sdk

Version:

Centralized monitoring SDK for Node.js applications with Prometheus, Loki, and Grafana integration

258 lines (257 loc) 10.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MetricsService = void 0; const client = __importStar(require("prom-client")); class MetricsService { constructor(config) { this.customMetrics = new Map(); this.config = config; this.initializeMetrics(); } static getInstance(config) { if (!MetricsService.instance) { if (!config) { throw new Error("Configuration required for first initialization"); } MetricsService.instance = new MetricsService(config); } else if (config) { // Update configuration if provided and different const currentConfigStr = JSON.stringify(MetricsService.instance.config); const newConfigStr = JSON.stringify(config); if (currentConfigStr !== newConfigStr) { MetricsService.instance.config = config; MetricsService.instance.initializeMetrics(); } } return MetricsService.instance; } initializeMetrics() { // Clear existing registry and stop any existing default metrics collection // client.register.clear(); // Stop any existing default metrics collection // const registry = client.register as any; // if (registry._defaultMetricsTimer) { // clearInterval(registry._defaultMetricsTimer); // registry._defaultMetricsTimer = undefined; // } // Enable default metrics if configured if (this.config.enableDefaultMetrics !== false) { // Use prefix for default metrics if prefixAllMetrics is enabled const sanitizeForMetrics = (str) => str.replace(/[^a-zA-Z0-9]/g, "_"); const defaultMetricsPrefix = this.config.prefixAllMetrics ? `${sanitizeForMetrics(this.config.projectName)}_` : ""; client.collectDefaultMetrics({ register: client.register, prefix: defaultMetricsPrefix, labels: { project: this.config.projectName, service: this.config.serviceName, technology: this.config.technology, environment: this.config.environment || "development", ...this.config.customLabels, }, }); } // Helper function for metric prefixing const sanitizeForMetrics = (str) => str.replace(/[^a-zA-Z0-9]/g, "_"); const getMetricName = (baseName) => { if (this.config.prefixAllMetrics) { return `${sanitizeForMetrics(this.config.projectName)}_${baseName}`; } return this.config.prefixCustomMetrics !== false ? `${sanitizeForMetrics(this.config.projectName)}_${baseName}` : baseName; }; // HTTP request duration histogram (now in seconds) this.httpRequestDuration = new client.Histogram({ name: this.config.prefixAllMetrics ? `${sanitizeForMetrics(this.config.projectName)}_http_request_duration_seconds` : "http_request_duration_seconds", help: "Duration of HTTP requests in seconds", labelNames: [ "method", "route", "status_code", "project", "service", "technology", ], buckets: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10], }); // Cron job metrics (prefixed with project name) this.cronJobDuration = new client.Histogram({ name: getMetricName("cron_job_duration_seconds"), help: "Duration of cron jobs in seconds", labelNames: ["job_name", "project", "service", "success"], buckets: [1, 5, 10, 30, 60, 300, 600, 1800, 3600], }); this.cronJobFailures = new client.Counter({ name: getMetricName("cron_job_failures_total"), help: "Total number of cron job failures", labelNames: ["job_name", "project", "service", "error_type"], }); // Register metrics client.register.registerMetric(this.httpRequestDuration); client.register.registerMetric(this.cronJobDuration); client.register.registerMetric(this.cronJobFailures); } recordHttpRequest(method, route, statusCode, duration) { // Convert duration from ms to seconds this.httpRequestDuration .labels(method, route, statusCode.toString(), this.config.projectName, this.config.serviceName, this.config.technology) .observe(duration / 1000); } recordCronJob(metric) { const durationInSeconds = metric.duration / 1000; this.cronJobDuration .labels(metric.jobName, this.config.projectName, this.config.serviceName, metric.success.toString()) .observe(durationInSeconds); if (!metric.success) { this.cronJobFailures .labels(metric.jobName, this.config.projectName, this.config.serviceName, metric.error || "unknown_error") .inc(); } } createCustomMetric(config) { let metric; const labelNames = [ ...(config.labelNames || []), "project", "service", "technology", ]; // Helper function for metric prefixing const sanitizeForMetrics = (str) => str.replace(/[^a-zA-Z0-9]/g, "_"); const prefixedName = this.config.prefixCustomMetrics !== false ? `${sanitizeForMetrics(this.config.projectName)}_${config.name}` : config.name; switch (config.type) { case "counter": metric = new client.Counter({ name: prefixedName, help: config.help, labelNames, }); break; case "gauge": metric = new client.Gauge({ name: prefixedName, help: config.help, labelNames, }); break; case "histogram": metric = new client.Histogram({ name: prefixedName, help: config.help, labelNames, buckets: config.buckets || client.exponentialBuckets(0.1, 1.5, 20), }); break; case "summary": metric = new client.Summary({ name: prefixedName, help: config.help, labelNames, percentiles: config.percentiles || [0.5, 0.9, 0.95, 0.99], }); break; default: throw new Error(`Unsupported metric type: ${config.type}`); } client.register.registerMetric(metric); this.customMetrics.set(prefixedName, metric); return metric; } getCustomMetric(name) { const sanitizeForMetrics = (str) => str.replace(/[^a-zA-Z0-9]/g, "_"); const prefixedName = this.config.prefixCustomMetrics !== false ? `${sanitizeForMetrics(this.config.projectName)}_${name}` : name; return this.customMetrics.get(prefixedName) || this.customMetrics.get(name); } async getMetrics() { return await client.register.metrics(); } getRegistry() { return client.register; } // Convenience methods for creating common metric types createCounter(name, help, labelNames) { const metric = this.createCustomMetric({ name, help, type: "counter", labelNames, }); return metric; } createGauge(name, help, labelNames) { const metric = this.createCustomMetric({ name, help, type: "gauge", labelNames, }); return metric; } createHistogram(name, help, labelNames, buckets) { const metric = this.createCustomMetric({ name, help, type: "histogram", labelNames, buckets, }); return metric; } static reset() { if (MetricsService.instance) { // Stop any existing default metrics collection const registry = client.register; if (registry._defaultMetricsTimer) { clearInterval(registry._defaultMetricsTimer); registry._defaultMetricsTimer = undefined; } // Clear the Prometheus registry client.register.clear(); } MetricsService.instance = undefined; } } exports.MetricsService = MetricsService;