@grafana/faro-transport-otlp-http
Version:
Faro transport which converts the Faro data model to the Otlp data model.
123 lines • 5.92 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { BaseTransport, createPromiseBuffer, isArray, noop, VERSION } from '@grafana/faro-core';
import { OtelPayload } from './payload';
const DEFAULT_BUFFER_SIZE = 30;
const DEFAULT_CONCURRENCY = 5; // chrome supports 10 total, firefox 17
const DEFAULT_RATE_LIMIT_BACKOFF_MS = 5000;
const BEACON_BODY_SIZE_LIMIT = 60000;
const TOO_MANY_REQUESTS = 429;
export class OtlpHttpTransport extends BaseTransport {
constructor(options) {
var _a, _b, _c;
super();
this.options = options;
this.name = '@grafana/faro-web-sdk:transport-otlp-http';
this.version = VERSION;
this.sendingTracesDisabledUntil = new Date();
this.sendingLogsDisabledUntil = new Date();
this.rateLimitBackoffMs = (_a = options.defaultRateLimitBackoffMs) !== null && _a !== void 0 ? _a : DEFAULT_RATE_LIMIT_BACKOFF_MS;
this.promiseBuffer = createPromiseBuffer({
size: (_b = options === null || options === void 0 ? void 0 : options.bufferSize) !== null && _b !== void 0 ? _b : DEFAULT_BUFFER_SIZE,
concurrency: (_c = options === null || options === void 0 ? void 0 : options.concurrency) !== null && _c !== void 0 ? _c : DEFAULT_CONCURRENCY,
});
}
getIgnoreUrls() {
var _a;
const { tracesURL = '', logsURL = '' } = this.options;
return [tracesURL, logsURL].filter(Boolean).concat((_a = this.config.ignoreUrls) !== null && _a !== void 0 ? _a : []);
}
isBatched() {
return true;
}
send(items) {
const otelPayload = new OtelPayload({
internalLogger: this.internalLogger,
customOtlpTransform: this.options.otlpTransform,
});
items.forEach((item) => otelPayload.addResourceItem(item));
this.sendPayload(otelPayload.getPayload());
}
sendPayload(payload) {
try {
const { tracesURL = '', logsURL = '' } = this.options;
for (const [key, value] of Object.entries(payload)) {
if (!(isArray(value) && value.length > 0)) {
continue;
}
let disabledUntil;
let updateDisabledUntil = (_) => { };
let url = '';
switch (key) {
case 'resourceSpans':
url = tracesURL;
disabledUntil = this.sendingTracesDisabledUntil;
updateDisabledUntil = (retryAfterDate) => {
this.sendingTracesDisabledUntil = retryAfterDate;
};
break;
case 'resourceLogs':
url = logsURL;
disabledUntil = this.sendingLogsDisabledUntil;
updateDisabledUntil = (retryAfterDate) => {
this.sendingLogsDisabledUntil = retryAfterDate;
};
break;
}
if (disabledUntil && disabledUntil > new Date(Date.now())) {
this.logWarn(`Dropping transport item due to too many requests. Backoff until ${disabledUntil}`);
return undefined;
}
const body = JSON.stringify({ [key]: value });
const { requestOptions, apiKey } = this.options;
const _a = requestOptions !== null && requestOptions !== void 0 ? requestOptions : {}, { headers } = _a, restOfRequestOptions = __rest(_a, ["headers"]);
if (!url) {
continue;
}
this.promiseBuffer.add(() => {
return fetch(url, Object.assign({ method: 'POST', headers: Object.assign(Object.assign({ 'Content-Type': 'application/json' }, (headers !== null && headers !== void 0 ? headers : {})), (apiKey ? { 'x-api-key': apiKey } : {})), body, keepalive: body.length <= BEACON_BODY_SIZE_LIMIT }, (restOfRequestOptions !== null && restOfRequestOptions !== void 0 ? restOfRequestOptions : {})))
.then((response) => {
if (response.status === TOO_MANY_REQUESTS) {
updateDisabledUntil(this.getRetryAfterDate(response));
this.logWarn(`Too many requests, backing off until ${disabledUntil}`);
}
// read the body so the connection can be closed
response.text().catch(noop);
return response;
})
.catch((error) => {
this.logError('Failed sending payload to the receiver\n', JSON.parse(body), error);
});
});
}
}
catch (error) {
this.logError(error);
}
}
getRetryAfterDate(response) {
const now = Date.now();
const retryAfterHeader = response.headers.get('Retry-After');
if (retryAfterHeader) {
const delay = Number(retryAfterHeader);
if (!isNaN(delay)) {
return new Date(delay * 1000 + now);
}
const date = Date.parse(retryAfterHeader);
if (!isNaN(date)) {
return new Date(date);
}
}
return new Date(now + this.rateLimitBackoffMs);
}
}
//# sourceMappingURL=transport.js.map