@biorate/axios-prometheus
Version:
Axios-prometheus HTTP interface
194 lines (193 loc) • 8.75 kB
JavaScript
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);