UNPKG

@biorate/axios-prometheus

Version:

Axios-prometheus HTTP interface

194 lines (193 loc) 8.75 kB
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 __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 _a, _AxiosPrometheus_histogram; import stringify from 'json-stringify-safe'; import { readFileSync, writeFileSync, statSync, mkdirSync } from 'fs'; import { path, time as timeTools } from '@biorate/tools'; import { container, Types } from '@biorate/inversion'; import { Axios } from '@biorate/axios'; import { counter, Counter, Histogram, Prometheus } from '@biorate/prometheus'; import { trace } from '@biorate/opentelemetry'; import { get, set, pick } from 'lodash-es'; export * from '@biorate/axios'; export class AxiosPrometheus extends Axios { static mockFileName(name) { return `Axios.${name}.snap`; } static checkMockDir(directory) { try { const dir = path.create(process.cwd(), directory); const stats = statSync(dir); if (!stats.isDirectory()) return null; return dir; } catch { return null; } } static mockFilePath(filename) { let directory = container.get(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 path.create(directory, '__snapshots__', filename ?? ''); } static getMockData(instance, filename) { return JSON.parse(readFileSync(this.mockFilePath(filename), 'utf8')); } static getMock(instance, options) { const filename = this.mockFileName(instance.constructor.name); try { return 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 = {}; } set(data, `${instance.constructor.name}.${JSON.stringify(options)}`, pick(result, ...this.mockFields)); try { mkdirSync(this.mockFilePath(), { recursive: true }); } catch { } try { 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 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.registry], }), "f", _AxiosPrometheus_histogram); return __classPrivateFieldGet(_a, _a, "f", _AxiosPrometheus_histogram); } get config() { return container.get(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 = timeTools.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' ? stringify(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 = 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 = 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 = 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)); } } _a = AxiosPrometheus; AxiosPrometheus.mockFields = ['data', 'status', 'statusText']; _AxiosPrometheus_histogram = { value: void 0 }; __decorate([ counter({ name: 'http_client_requests_seconds_count', help: 'Http client requests count', labelNames: ['method', 'uri', 'status'], }), __metadata("design:type", Counter) ], AxiosPrometheus.prototype, "counter", void 0);