UNPKG

@talks.converse/js-monitoring

Version:

Express monitoring middleware with Datadog and Prometheus backends, plus conditional logging.

203 lines (182 loc) 6.82 kB
const client = require('prom-client'); const helper = require('./helper'); const prometheusEndpointsInclude = process.env.PROMETHEUS_ENDPOINTS_INCLUDE; const prometheusEndpointExclude = process.env.PROMETHEUS_ENDPOINTS_EXCLUDE; // ---- Registry & default metrics ---- const register = new client.Registry(); const prefix = helper.normalizeName(process.env.APP_NAME) + '_'; // ---- Dynamic metric caches (safe, bounded tags only) ---- const counters = new Map(); const gauges = new Map(); const histograms = new Map(); client.collectDefaultMetrics({ prefix, register }); /** * Main delegate to record metrics depending on environment variable. * Records either a histogram or a combination of counter and gauge. * @param {string} app_name - The application or metric base name. * @param {number} latencyMs - Latency in milliseconds. * @param {Array<string>|Object} tags - Tags or label object associated with the metric. */ const monitor = (app_name, latencyMs, tags) => { if (process.env.MONITORING_METRIC_TYPE === 'histogram'){ const latencyName = asSecondsName(`${app_name}_metrics`); recordHistogram(latencyName, latencyMs, tags); } else { const counterName = asCounterName(`${app_name}_count`); const latencyName = asSecondsName(`${app_name}_latency`); recordCounter(counterName, 1, tags); recordGauge(latencyName, latencyMs / 1000, tags); } }; /** * Registers an Express route to expose Prometheus metrics at the given path. * @param {object} app - Express app instance. * @param {string} [path='/metrics'] - Route path to serve metrics. */ const registerMetricsRoute = (app, path = '/metrics') => { app.get(path, async (_req, res) => { try { res.set('Content-Type', register.contentType); res.end(await register.metrics()); } catch (err) { res.status(500).end(`# metrics error: ${String(err)}`); } }); }; /** * No-op close function since Prometheus scrapes metrics; no connection to close. */ const closeConnection = () => { // nothing to close; Prometheus scrapes us }; // ---- Helpers: tags → tags, name normalization ---- /** * Ensures a metric name follows Prometheus convention for counters ending with '_total'. * @param {string} name - Metric name. * @returns {string} Normalized counter metric name. */ const asCounterName = (name) => name.endsWith('_total') ? name : `${name}_total`; /** * Ensures a metric name follows Prometheus convention for histograms/gauges ending with '_seconds'. * @param {string} name - Metric name. * @returns {string} Normalized seconds metric name. */ const asSecondsName = (name) => { return name.endsWith('_seconds') ? name : `${name}_seconds`; }; /** * Builds include and exclude regex configurations from environment variables * for filtering Prometheus monitoring endpoints. * @returns {object} Object with 'include' and 'exclude' regex lists. */ const buildPrometheusMonitoringEndpointsConfig = () => ({ include: helper.compileRegexList(prometheusEndpointsInclude), exclude: helper.compileRegexList(prometheusEndpointExclude), }); // ---- Metric recorders ---- /** * Increments a counter metric by the given value with associated tags. * @param {string} metric - Metric name. * @param {number} value - Increment value (default 1). * @param {Array|Object} tags - Tags or label object. */ const recordCounter = (metric, value = 1, tags = []) => { const counter = getOrCreateCounter(metric, Object.keys(tags)); counter.inc(tags, Number(value) || 1); }; /** * Retrieves a cached counter metric or creates and caches a new one if not present. * @param {string} name - Metric name. * @param {Array<string>} labelNames - List of label names. * @returns {client.Counter} Prometheus Counter metric instance. */ const getOrCreateCounter = (name, labelNames) => { const key = `${name}|${labelNames.sort().join(',')}`; if (!counters.has(key)) { counters.set( key, new client.Counter({ name, help: `Counter for ${name}`, labelNames, registers: [register], }) ); } return counters.get(key); }; /** * Sets a gauge metric to the given value with associated tags. * @param {string} metric - Metric name. * @param {number} value - Value to set. * @param {Array|Object} tags - Tags or label object. */ const recordGauge = (metric, value, tags = []) => { const gauge = getOrCreateGauge(metric, Object.keys(tags)); gauge.set(tags, Number(value) || 0); }; /** * Retrieves a cached gauge metric or creates and caches a new one if not present. * @param {string} name - Metric name. * @param {Array<string>} labelNames - List of label names. * @returns {client.Gauge} Prometheus Gauge metric instance. */ const getOrCreateGauge = (name, labelNames) => { const key = `${name}|${labelNames.sort().join(',')}`; if (!gauges.has(key)) { gauges.set( key, new client.Gauge({ name, help: `Gauge for ${name}`, labelNames, registers: [register], }) ); } return gauges.get(key); }; /** * Records an observation value (in milliseconds) into a histogram metric (stored in seconds). * @param {string} metric - Metric name. * @param {number} value - Numeric value in milliseconds. * @param {Array|Object} tags - Tags or label object. */ const recordHistogram = (metric, value, tags = []) => { const hist = getOrCreateHistogram(metric, Object.keys(tags)); hist.observe(tags, Number(value)); }; /** * Retrieves a cached histogram metric or creates and caches a new one if not present. * @param {string} name - Metric name. * @param {Array<string>} labelNames - List of label names. * @returns {client.Histogram} Prometheus Histogram metric instance. */ const getOrCreateHistogram = (name, labelNames) => { const key = `${name}|${labelNames.sort().join(',')}`; if (!histograms.has(key)) { histograms.set( key, new client.Histogram({ name, help: `Histogram for ${name}`, labelNames, // default buckets; override by defining a dedicated metric above if needed buckets: [0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], registers: [register], }) ); } return histograms.get(key); }; module.exports = { monitor, recordCounter, recordGauge, recordHistogram, closeConnection, registerMetricsRoute, buildPrometheusMonitoringEndpointsConfig, register, // exported in case you want to plug into other collectors };