@dynatrace/react-native-plugin
Version:
This plugin gives you the ability to use the Dynatrace Mobile agent in your react native application.
197 lines (196 loc) • 8.77 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ConsoleLogger_1 = require("../../core/logging/ConsoleLogger");
const TimestampProvider_1 = require("../provider/TimestampProvider");
const SendEventValidation_1 = require("./modifier/SendEventValidation");
const logger = new ConsoleLogger_1.ConsoleLogger('Dynatrace');
class HttpRequestEventBuilder {
constructor(url, requestMethod) {
this.url = url;
this.requestMethod = requestMethod;
this.duration = 0;
this.rawEventProperties = {};
this.traceContextHint = "api_unused";
this.hasDroppedCustomProperties = false;
this.hasNfnValues = false;
this.triedToOverwriteDuration = false;
this.requestMethod =
requestMethod.toUpperCase();
}
withDuration(duration) {
this.duration = duration;
this.triedToOverwriteDuration = true;
return this;
}
withStatusCode(statusCode) {
this.statusCode = statusCode;
return this;
}
withReasonPhrase(reasonPhrase) {
this.reasonPhrase = reasonPhrase;
return this;
}
withError(error) {
this.error = error;
return this;
}
withBytesSent(bytesSent) {
this.bytesSent = bytesSent;
return this;
}
withBytesReceived(bytesReceived) {
this.bytesReceived = bytesReceived;
return this;
}
withTraceparentHeader(traceparentHeader) {
this.traceparentHeader = traceparentHeader;
this.traceContextHint = "api_set";
return this;
}
addEventProperty(key, value) {
this.rawEventProperties[key] = value;
return this;
}
build() {
if (!this.hasValidMandatoryAttriutes()) {
logger.debug('HttpRequestEventBuilder dropped invalid event');
return null;
}
this.sanitizeDuration();
this.sanitizeStatusCode();
this.sanitizeReasonPhrase();
this.sanitizeBytesReceived();
this.sanitizeBytesSent();
const parsedTraceparentHeader = this.traceparentHeader &&
this.parseTraceparent(this.traceparentHeader);
if (!!this.traceparentHeader && !parsedTraceparentHeader) {
this.traceContextHint = "invalid";
}
const hasFailedRequest = this.isStatusCodeError() || !!this.error;
const filteredEventProperties = Object.fromEntries(Object.entries(this.rawEventProperties).filter(this.isEventPropertyKey, this));
const sanitizedEventProperties = SendEventValidation_1.SendEventValidation.modifyEvent(filteredEventProperties);
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, sanitizedEventProperties), { ["url.full"]: this.url, ["network.protocol.name"]: 'http', ["http.request.method"]: this.requestMethod, ["request.trace_context_hint"]: this.traceContextHint, ["duration"]: this.duration, ["start_time"]: TimestampProvider_1.defaultTimestampProvider.getCurrentTimestamp() - this.duration }), (this.triedToOverwriteDuration && {
["dt.support.api.overridden_fields"]: [
'duration',
],
})), this.includeIfDefined("http.response.status_code", this.statusCode)), this.includeIfDefined("http.response.reason_phrase", this.reasonPhrase)), this.includeIfDefined("request.bytes_sent", this.bytesSent)), this.includeIfDefined("request.bytes_received", this.bytesReceived)), { ["characteristics.has_request"]: true, ["characteristics.is_api_reported"]: true }), this.includeIfTrue("characteristics.has_failed_request", hasFailedRequest)), this.includeIfTrue("characteristics.has_error", hasFailedRequest)), (this.error !== undefined && Object.assign({ ["characteristics.has_exception"]: true, ["exception.type"]: this.error.name, ["exception.message"]: this.error.message }, this.includeIfDefined("exception.stack_trace", this.error.stack)))), (parsedTraceparentHeader && {
["trace.id"]: parsedTraceparentHeader.traceId,
["span.id"]: parsedTraceparentHeader.spanId,
})), this.includeIfTrue("dt.support.api.has_dropped_custom_properties", this.hasDroppedCustomProperties)), this.includeIfTrue("dt.support.has_nfn_values", this.hasNfnValues));
}
hasValidMandatoryAttriutes() {
let isValid = true;
if (this.isInvalidUrl(this.url)) {
logger.debug(`HttpRequestEventBuilder: dropped event since given URL is malformed: ${this.url}`);
isValid = false;
}
if (!HttpRequestEventBuilder.allowedRequestMethods.includes(this.requestMethod)) {
logger.debug(`HttpRequestEventBuilder: dropped event since given Request Method is invalid: ${this.requestMethod}`);
isValid = false;
}
return isValid;
}
isInvalidUrl(url) {
try {
if (!url.match(/^(https?):\/\/[^\s/$.?#-][^\s]*$/i)) {
return true;
}
new URL(url);
return false;
}
catch (_a) {
return true;
}
}
sanitizeStatusCode() {
if (this.statusCode == undefined && this.error === undefined) {
this.statusCode = 0;
}
if (this.statusCode && this.statusCode < 0) {
logger.debug('HttpRequestEventBuilder: overriding invalid Status Code with 0');
this.statusCode = 0;
this.hasDroppedCustomProperties = true;
}
}
sanitizeReasonPhrase() {
if (this.reasonPhrase &&
this.reasonPhrase.length >
HttpRequestEventBuilder.maxReasonPhraseLength) {
logger.debug(`HttpRequestEventBuilder: trimming too long Reason Phrase to a length of ${HttpRequestEventBuilder.maxReasonPhraseLength}`);
this.reasonPhrase = this.reasonPhrase.slice(0, HttpRequestEventBuilder.maxReasonPhraseLength);
}
}
sanitizeDuration() {
this.hasNfnValues || (this.hasNfnValues = !Number.isFinite(this.duration));
if (!Number.isFinite(this.duration) || this.duration < 0) {
logger.debug('HttpRequestEventBuilder: overriding invalid Duration with 0');
this.duration = 0;
this.hasDroppedCustomProperties = true;
}
}
sanitizeBytesSent() {
if (this.bytesSent && this.bytesSent < 0) {
logger.debug(`HttpRequestEventBuilder: dropping invalid value for Bytes Sent: ${this.bytesSent}`);
this.bytesSent = undefined;
this.hasDroppedCustomProperties = true;
}
}
sanitizeBytesReceived() {
if (this.bytesReceived && this.bytesReceived < 0) {
logger.debug(`HttpRequestEventBuilder: dropping invalid value for Bytes Received: ${this.bytesReceived}`);
this.bytesReceived = undefined;
this.hasDroppedCustomProperties = true;
}
}
isStatusCodeError() {
return (this.statusCode && 400 <= this.statusCode && this.statusCode <= 599);
}
isEventPropertyKey([key, _]) {
if (key.startsWith("event_properties")) {
return true;
}
logger.debug(`HttpRequestEventBuilder: dropped event property '$key' as it did not start with prefix ${"event_properties"}`);
this.hasDroppedCustomProperties = true;
return false;
}
includeIfDefined(key, value) {
return value !== undefined ? { [key]: value } : {};
}
includeIfTrue(key, value) {
return value === true ? { [key]: value } : {};
}
parseTraceparent(header) {
const traceparentRegex = /^00-([0-9a-f]{32})-([0-9a-f]{16})-0[01]$/;
const match = header.match(traceparentRegex);
if (!match) {
logger.debug("The provided traceparent header does not match the format: '00-<trace-id-32-HEXDIG>-<parent-id-16-HEXDIG>-<trace-flags-2-HEXDIG>'");
return null;
}
const [, traceId, spanId] = match;
if (this.allZeros(traceId)) {
logger.debug('Trace ID in traceparent header must not be all zeros');
return null;
}
if (this.allZeros(spanId)) {
logger.debug('Parent ID in traceparent header must not be all zeros');
return null;
}
return { traceId, spanId };
}
allZeros(str) {
return /^0*$/.test(str);
}
}
exports.default = HttpRequestEventBuilder;
HttpRequestEventBuilder.allowedRequestMethods = [
'GET',
'HEAD',
'POST',
'PUT',
'DELETE',
'CONNECT',
'OPTIONS',
'TRACE',
'PATCH',
];
HttpRequestEventBuilder.maxReasonPhraseLength = 5000;