@gasbuddy/configured-prometheus-client
Version:
A configuration driven prometheus client
252 lines (201 loc) • 7.68 kB
JavaScript
"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;