@loopback/metrics
Version:
An extension exposes metrics for Prometheus with LoopBack 4
118 lines • 4.94 kB
JavaScript
// Copyright IBM Corp. and LoopBack contributors 2019,2020. All Rights Reserved.
// Node module: @loopback/metrics
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
var MetricsInterceptor_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetricsInterceptor = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@loopback/core");
const prom_client_1 = require("prom-client");
const labelNames = ['targetName', 'method', 'path', 'statusCode'];
/**
* This interceptor captures metrics for method invocations,
* excluding sequence actions and middleware executed before
* a method is invoked. Please collect metrics at other places
* if you want to cover more than just method invocations.
*/
let MetricsInterceptor = MetricsInterceptor_1 = class MetricsInterceptor {
static setup() {
// Check if the gauge is registered
if (this.gauge &&
prom_client_1.register.getSingleMetric('loopback_invocation_duration_seconds'))
return;
// The constructor will register the metric with the global registry
this.gauge = new prom_client_1.Gauge({
name: 'loopback_invocation_duration_seconds',
help: 'method invocation',
labelNames,
});
this.histogram = new prom_client_1.Histogram({
name: 'loopback_invocation_duration_histogram',
help: 'method invocation histogram',
labelNames,
});
this.counter = new prom_client_1.Counter({
name: 'loopback_invocation_total',
help: 'method invocation count',
labelNames,
});
this.summary = new prom_client_1.Summary({
name: 'loopback_invocation_duration_summary',
help: 'method invocation summary',
labelNames,
});
}
constructor() { }
value() {
return this.intercept.bind(this);
}
async intercept(invocationCtx, next) {
MetricsInterceptor_1.setup();
const { source, parent } = invocationCtx;
const labelValues = {
targetName: invocationCtx.targetName,
};
if (isRouteSource(source)) {
labelValues.method = getRequestMethod(source);
labelValues.path = getPathPattern(source);
}
const endGauge = MetricsInterceptor_1.gauge.startTimer();
const endHistogram = MetricsInterceptor_1.histogram.startTimer();
const endSummary = MetricsInterceptor_1.summary.startTimer();
try {
const result = await next();
if (isRouteSource(source)) {
labelValues.statusCode = getStatusCodeFromResponse(
// parent context will be request context if invocation source is route
parent.response, result);
}
return result;
}
catch (err) {
if (isRouteSource(source)) {
labelValues.statusCode = getStatusCodeFromError(err);
}
throw err;
}
finally {
MetricsInterceptor_1.counter.inc(labelValues);
endGauge(labelValues);
endHistogram(labelValues);
endSummary(labelValues);
}
}
};
exports.MetricsInterceptor = MetricsInterceptor;
exports.MetricsInterceptor = MetricsInterceptor = MetricsInterceptor_1 = tslib_1.__decorate([
(0, core_1.injectable)((0, core_1.asGlobalInterceptor)('metrics'), { scope: core_1.BindingScope.SINGLETON }),
tslib_1.__metadata("design:paramtypes", [])
], MetricsInterceptor);
function getPathPattern(source) {
// make sure to use path pattern instead of raw path
// this is important since paths can contain unbounded sets of values
// such as IDs which would create a new time series for each unique value
return source.value.path;
}
function getRequestMethod(source) {
// request methods should be all-uppercase
return source.value.verb.toUpperCase();
}
function getStatusCodeFromResponse(response, result) {
// interceptors are invoked before result is written to response,
// the status code for 200 responses without a result should be 204
const noContent = response.statusCode === 200 && result === undefined;
return noContent ? 204 : response.statusCode;
}
function getStatusCodeFromError(err) {
var _a, _b;
// interceptors are invoked before error is written to response,
// it is required to retrieve the status code from the error
const notFound = err.code === 'ENTITY_NOT_FOUND';
return (_b = (_a = err.statusCode) !== null && _a !== void 0 ? _a : err.status) !== null && _b !== void 0 ? _b : (notFound ? 404 : 500);
}
function isRouteSource(source) {
return (source === null || source === void 0 ? void 0 : source.type) === 'route';
}
//# sourceMappingURL=metrics.interceptor.js.map
;