@hemantwasthere/monitoring-sdk
Version:
Centralized monitoring SDK for Node.js applications with Prometheus, Loki, and Grafana integration
258 lines (257 loc) • 10.2 kB
JavaScript
"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;