UNPKG

@gasbuddy/configured-prometheus-client

Version:

A configuration driven prometheus client

252 lines (201 loc) 7.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _express = _interopRequireDefault(require("express")); var _PromiseTimer = _interopRequireDefault(require("./PromiseTimer")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function listen(context, cp) { if (cp.port < 0) { if (context.logger && context.logger.info) { context.logger.info('Metrics server disabled; metrics will not be available externally'); } else { // eslint-disable-next-line no-console console.log('Metrics server disabled; metrics will not be available externally'); } return; } cp.server = cp.app.listen(cp.port, () => { if (context.logger && context.logger.info) { context.logger.info('Metrics server listening', { port: cp.server.address().port }); } else { // eslint-disable-next-line no-console console.log('Metrics server listening on', cp.server.address().port); } }); cp.server.on('error', error => { if (context.logger && context.logger.error) { context.logger.error('Could not setup metrics server', { error: error.message }); } else { // eslint-disable-next-line no-console console.error('Could not setup metrics server', error.message); } }); } /** * Implement some of the argument detection logic so we can muck with the registers */ function pmArgs(registers, hasFourArgs, args) { if (typeof args[0] === 'object') { return _objectSpread({ registers }, args[0]); } if (hasFourArgs) { let obj; let labels = []; if (Array.isArray(args[2])) { obj = args[3] || {}; [,, labels] = args; } else { obj = args[2] || {}; } const finalArgs = { registers, name: args[0], help: args[1], labelNames: labels }; if (obj.buckets) { finalArgs.buckets = obj.buckets; } if (obj.percentiles) { finalArgs.percentiles = obj.percentiles; } return finalArgs; } return { registers, name: args[0], help: args[1], labelNames: args[2] || [] }; } class PrometheusClient { constructor(context, opts) { // eslint-disable-next-line global-require this.client = require('prom-client'); const { Registry } = this.client; this.register = new Registry(); this.port = opts.port === 0 ? 0 : opts.port || 3000; if (opts.defaultMetrics) { this.client.collectDefaultMetrics({ timeout: opts.defaultMetrics.interval, register: this.register }); } const registers = [this.register]; this.Counter = class Counter extends this.client.Counter { constructor(...args) { super(pmArgs(registers, false, args)); } }; this.Gauge = class Gauge extends this.client.Gauge { constructor(...args) { super(pmArgs(registers, false, args)); } }; this.Histogram = class Histogram extends this.client.Histogram { constructor(...args) { super(pmArgs(registers, true, args)); } }; this.Summary = class Summary extends this.client.Summary { constructor(...args) { super(pmArgs(registers, true, args)); } }; // Make pre-configured items this.counters = this.buildMetricsFromConfiguration(this.client.Counter, opts.counters); this.gauges = this.buildMetricsFromConfiguration(this.client.Gauge, opts.gauges); this.histograms = this.buildMetricsFromConfiguration(this.client.Histogram, opts.histograms); this.hists = this.histograms; this.summaries = this.buildMetricsFromConfiguration(this.client.Summary, opts.summaries); } buildMetricsFromConfiguration(MetricClass, configs) { if (!configs) { return undefined; } const metrics = {}; for (const [name, config] of Object.entries(configs)) { const { labels: labelNames = [], config: innerConfig = {} } = config, restConfig = _objectWithoutProperties(config, ["labels", "config"]); const finalConfig = _objectSpread({ labelNames, name }, restConfig, { registers: [this.register] }); // Old api allowed a "config" child with these things if (innerConfig.buckets && !finalConfig.buckets) { finalConfig.buckets = innerConfig.buckets; } if (innerConfig.percentiles && !finalConfig.percentiles) { finalConfig.percentiles = innerConfig.percentiles; } metrics[name] = new MetricClass(finalConfig); } return metrics; } find(metricName) { if (this.summaries && this.summaries[metricName]) { return this.summaries[metricName]; } if (this.histograms && this.histograms[metricName]) { return this.histograms[metricName]; } if (this.gauges && this.gauges[metricName]) { return this.gauges[metricName]; } if (this.counters && this.counters[metricName]) { return this.counters[metricName]; } return null; } /** * Log a time value to a prometheus metric based on a promise. * The returned object has the following methods: * * Add some labels to the timer: * .label({ some: 'label' }) * * Add some labels that are based on the RESULT of the promise (or error): * .label((error, result) => ({ otherLabel: result.value })) * * Return the promise and start the timer/execution: * .execute(someFunctionThatReturnsAPromise()); */ promiseTimer(metricNameOrInstance, labels) { let metric = metricNameOrInstance; if (typeof metricNameOrInstance === 'string') { metric = this.find(metricNameOrInstance); } return new _PromiseTimer.default(metric, labels); } start(context) { this.app = (0, _express.default)(); this.app.get('/metrics', (req, res) => res.end(this.register.metrics())); listen(context, this); return this; } stop() { if (this.server) { this.server.close(); delete this.server; } this.register.clear(); } } exports.default = PrometheusClient;