UNPKG

@loopback/metrics

Version:

An extension exposes metrics for Prometheus with LoopBack 4

118 lines 4.94 kB
"use strict"; // 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