UNPKG

@substrate/api-sidecar

Version:

REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.

191 lines 8.44 kB
"use strict"; // Copyright 2022-2025 Parity Technologies (UK) Ltd. // This file is part of Substrate API Sidecar. // // Substrate API Sidecar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // as new metrics are added in routes they need to be registered here to make them available across the apps const express_1 = __importDefault(require("express")); const prom_client_1 = __importDefault(require("prom-client")); const Log_1 = require("../logging/Log"); const SidecarConfig_1 = require("../SidecarConfig"); const _1 = require("."); class Metrics_App { /** * @param appConfig configuration for app. */ constructor({ host, port }) { this.includeQueryParams = SidecarConfig_1.SidecarConfig.config.METRICS.INCLUDE_QUERYPARAMS; this.port = port; this.app = (0, express_1.default)(); this.host = host; this.registry = new prom_client_1.default.Registry(); this.metrics = {}; this.init(); } listen() { const { logger } = Log_1.Log; this.app.listen(this.port, this.host, () => { logger.info(`Metrics Server started at http://${this.host}:${this.port}/metrics`); }); } getRegisteredMetrics() { return this.metrics; } createMetricByType(prefix = 'sas', metric) { const prefixedName = prefix + '_' + metric.name; if (prefixedName in this.metrics) { return this.metrics[prefixedName]; } switch (metric.type) { case "counter" /* MetricType.Counter */: { const counter = new prom_client_1.default.Counter({ name: prefixedName, help: metric.help, labelNames: metric.labels || [], registers: [this.registry], }); this.registry.registerMetric(counter); this.metrics[prefixedName] = counter; return counter; } case "histogram" /* MetricType.Histogram */: { const histogram = new prom_client_1.default.Histogram({ name: prefixedName, help: metric.help, labelNames: metric.labels || [], registers: [this.registry], buckets: metric.buckets || [0.1, 0.5, 1, 1.5, 2, 3, 4, 5], }); this.metrics[prefixedName] = histogram; return histogram; } case "gauge" /* MetricType.Gauge */: throw new Error('Gauge not implemented'); case "summary" /* MetricType.Summary */: throw new Error('Summary not implemented'); default: throw new Error('Unknown metric type'); } } getRoute(req) { var _a, _b; let route = req.baseUrl; if (req.route) { if (req.route.path !== '/') { route = route ? route + ((_a = req.route) === null || _a === void 0 ? void 0 : _a.path) : (_b = req.route) === null || _b === void 0 ? void 0 : _b.path; } if (!route || route === '' || typeof route !== 'string') { route = req.originalUrl.split('?')[0]; } else { const splittedRoute = route.split('/'); const splittedUrl = req.originalUrl.split('?')[0].split('/'); const routeIndex = splittedUrl.length - splittedRoute.length + 1; const baseUrl = splittedUrl.slice(0, routeIndex).join('/'); route = baseUrl + route; } if (this.includeQueryParams === true && Object.keys(req.query).length > 0) { route = `${route}?${Object.keys(req.query) .sort() .map((queryParam) => `${queryParam}=<?>`) .join('&')}`; } } if (typeof req.params === 'object') { Object.keys(req.params).forEach((paramName) => { route = route.replace(req.params[paramName], ':' + paramName); }); } if (!route || route === '') { // if (!req.route && res && res.statusCode === 404) { route = 'N/A'; } return route; } preMiddleware() { return (req, res, next) => { const tot_requests = this.metrics['sas_http_requests']; // request count metrics if (req.originalUrl != '/favicon.ico') { tot_requests.inc(); } const request_duration_seconds = this.metrics['sas_request_duration_seconds']; const end = request_duration_seconds.startTimer(); res.locals.metrics = { timer: end, registry: this.metrics, }; const oldJson = res.json; res.json = (body) => { res.locals.body = body; return oldJson.call(res, body); }; res.once('finish', () => { if (res.statusCode >= 400 && req.originalUrl != '/favicon.ico') { const request_errors = this.metrics['sas_http_request_error']; request_errors.inc(); } else if (res.statusCode < 400) { const request_success = this.metrics['sas_http_request_success']; request_success.inc(); } let resContentLength = '0'; if ('_contentLength' in res && res['_contentLength'] != null) { resContentLength = res['_contentLength']; } else if (res.hasHeader('Content-Length')) { resContentLength = res.getHeader('Content-Length'); } // Generic Metrics per route (latency, response size, response size to latency ratio) // response size metrics const response_size_bytes = this.metrics['sas_response_size_bytes']; response_size_bytes .labels({ method: req.method, route: this.getRoute(req), status_code: res.statusCode }) .observe(parseFloat(resContentLength)); // latency metrics const latency = end({ method: req.method, route: this.getRoute(req), status_code: res.statusCode }); // response size to latency ratio const response_size_latency_ratio = this.metrics['sas_response_size_bytes_seconds']; response_size_latency_ratio .labels({ method: req.method, route: this.getRoute(req), status_code: res.statusCode }) .observe(parseFloat(resContentLength) / latency); }); next(); }; } init() { // Set up _1.config.metric_list.forEach((metric) => this.createMetricByType('sas', metric)); prom_client_1.default.collectDefaultMetrics({ register: this.registry, prefix: 'sas_' }); // Set up the metrics endpoint this.app.get('/metrics', (_req, res) => { void (async () => { res.set('Content-Type', this.registry.contentType); res.send(await this.registry.metrics()); })(); }); this.app.get('/metrics.json', (_req, res) => { void (async () => { res.set('Content-Type', this.registry.contentType); res.send(await this.registry.getMetricsAsJSON()); })(); }); } } exports.default = Metrics_App; //# sourceMappingURL=Metrics.js.map