UNPKG

@biorate/axios-prometheus

Version:

Axios-prometheus HTTP interface

215 lines (214 loc) 9.99 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a, _AxiosPrometheus_histogram; Object.defineProperty(exports, "__esModule", { value: true }); exports.AxiosPrometheus = void 0; const json_stringify_safe_1 = __importDefault(require("json-stringify-safe")); const fs_1 = require("fs"); const tools_1 = require("@biorate/tools"); const inversion_1 = require("@biorate/inversion"); const axios_1 = require("@biorate/axios"); const prometheus_1 = require("@biorate/prometheus"); const opentelemetry_1 = require("@biorate/opentelemetry"); const lodash_es_1 = require("lodash-es"); __exportStar(require("@biorate/axios"), exports); class AxiosPrometheus extends axios_1.Axios { static mockFileName(name) { return `Axios.${name}.snap`; } static checkMockDir(directory) { try { const dir = tools_1.path.create(process.cwd(), directory); const stats = (0, fs_1.statSync)(dir); if (!stats.isDirectory()) return null; return dir; } catch { return null; } } static mockFilePath(filename) { let directory = inversion_1.container.get(inversion_1.Types.Config).get('axios.mock.directory', null); if (!directory) directory = this.checkMockDir('test'); if (!directory) directory = this.checkMockDir('tests'); if (!directory) directory = process.cwd(); return tools_1.path.create(directory, '__snapshots__', filename ?? ''); } static getMockData(instance, filename) { return JSON.parse((0, fs_1.readFileSync)(this.mockFilePath(filename), 'utf8')); } static getMock(instance, options) { const filename = this.mockFileName(instance.constructor.name); try { return (0, lodash_es_1.get)(this.getMockData(instance, filename), `${instance.constructor.name}.${JSON.stringify(options)}`); } catch (e) { console.warn(`Axios mock snap file [${filename}] doesn't exists, or corrupted., because of [${e?.message}]`); } } static setMock(instance, result, options) { let data; const filename = this.mockFileName(instance.constructor.name); try { data = this.getMockData(instance, filename); } catch { data = {}; } (0, lodash_es_1.set)(data, `${instance.constructor.name}.${JSON.stringify(options)}`, (0, lodash_es_1.pick)(result, ...this.mockFields)); try { (0, fs_1.mkdirSync)(this.mockFilePath(), { recursive: true }); } catch { } try { (0, fs_1.writeFileSync)(this.mockFilePath(filename), JSON.stringify(data, null, ' '), 'utf8'); } catch (e) { console.warn(`Can't write Axios mock snap file [${filename}], because of [${e?.message}]`); } } get histogram() { if (!__classPrivateFieldGet(_a, _a, "f", _AxiosPrometheus_histogram)) __classPrivateFieldSet(_a, _a, new prometheus_1.Histogram({ name: 'http_client_requests_seconds', help: 'Http client requests seconds bucket', labelNames: ['method', 'uri', 'status'], buckets: this.config.get('AxiosPrometheus.histogram.buckets', [0.005, 0.01, 0.02, 0.05, 0.1, 0.3, 0.5, 1, 2, 3, 5, 10]), registers: [prometheus_1.Prometheus.registry], }), "f", _AxiosPrometheus_histogram); return __classPrivateFieldGet(_a, _a, "f", _AxiosPrometheus_histogram); } get config() { return inversion_1.container.get(inversion_1.Types.Config); } getStartTime() { return process.hrtime(); } log(statusCode, startTime) { const diff = process.hrtime(startTime); const time = diff[0] * 1e3 + diff[1] * 1e-6; const msTo = tools_1.time.msTo; this.counter .labels({ method: this.method, uri: this.baseURL + this.url, status: statusCode, }) .inc(); this.histogram .labels({ method: this.method, uri: this.baseURL + this.url, status: statusCode, }) .observe(msTo(time, 's')); } stringify(data) { return typeof data === 'object' ? (0, json_stringify_safe_1.default)(data) : String(data); } needTrace(url, span) { if (!span) return false; const excluded = this.config.get('AxiosPrometheus.tracing.excluded', []); for (const item of excluded) { if (typeof item === 'string' && url.startsWith(item)) return false; if (item instanceof RegExp && item.test(url)) return false; } return true; } fullUrl(params) { return params?.baseURL ?? '' + params?.url ?? ''; } async before(params) { await super.before(params); const span = opentelemetry_1.trace.getActiveSpan(); const url = this.fullUrl(params); if (!this.needTrace(url, span)) return; span.setAttribute('outgoing.request.url', this.stringify(url)); span.setAttribute('outgoing.request.body', this.stringify(params?.data)); span.setAttribute('outgoing.request.headers', this.stringify(params?.headers)); span.setAttribute('outgoing.request.method', this.stringify(params?.method)); span.setAttribute('outgoing.request.params', this.stringify(params?.path)); span.setAttribute('outgoing.request.query', this.stringify(params?.params)); span.setAttribute('SpanKind', 'CLIENT'); } async after(result, startTime, params) { await super.after(result, startTime, params); this.log(result.status, startTime); const span = opentelemetry_1.trace.getActiveSpan(); const url = this.fullUrl(params); if (!this.needTrace(url, span)) return; span.setAttribute('outgoing.response.headers', this.stringify(result.headers)); span.setAttribute('outgoing.response.statusCode', this.stringify(result.status)); span.setAttribute('outgoing.response.data', this.stringify(result.data)); } async catch(e, startTime, params) { await super.catch(e, startTime, params); if (!('response' in e)) return; this.log(e.response.status, startTime); const span = opentelemetry_1.trace.getActiveSpan(); const url = this.fullUrl(params); if (!this.needTrace(url, span)) return; span.setAttribute('outgoing.response.headers', this.stringify(e.response.headers)); span.setAttribute('outgoing.response.statusCode', this.stringify(e.response.status)); span.setAttribute('outgoing.response.data', this.stringify(e.response.data)); } } exports.AxiosPrometheus = AxiosPrometheus; _a = AxiosPrometheus; AxiosPrometheus.mockFields = ['data', 'status', 'statusText']; _AxiosPrometheus_histogram = { value: void 0 }; __decorate([ (0, prometheus_1.counter)({ name: 'http_client_requests_seconds_count', help: 'Http client requests count', labelNames: ['method', 'uri', 'status'], }), __metadata("design:type", prometheus_1.Counter) ], AxiosPrometheus.prototype, "counter", void 0);