@dash0/sdk-web
Version:
Dash0's Web SDK to collect telemetry from end-users' web browsers
140 lines (139 loc) • 4.94 kB
JavaScript
import { hasOwnProperty, nowNanos, win } from "../../utils";
import { isErrorMessageIgnored } from "../../utils/ignore-rules";
import { sendLog } from "../../transport";
import { EVENT_NAME, EVENT_NAMES, EXCEPTION_COMPONENT_STACK, EXCEPTION_MESSAGE, EXCEPTION_STACKTRACE, EXCEPTION_TYPE, LOG_SEVERITIES, } from "../../semantic-conventions";
import { addAttribute, addAttributes } from "../../utils/otel";
import { addCommonAttributes } from "../../attributes";
const MAX_ERRORS_TO_REPORT = 100;
const MAX_STACK_SIZE = 30;
const MAX_NUMBER_OF_TRACKED_ERRORS = 20;
let reportedErrors = 0;
let numberOfDifferentErrorsSeen = 0;
let seenErrors = {};
let scheduledTransmissionTimeoutHandle;
// We are wrapping global listeners. In these, we are catching and rethrowing errors.
// In older browsers, rethrowing errors actually manipulates the error objects. As a
// result, it is not possible to just mark an error as reported. The simplest way to
// avoid double reporting is to temporarily disable the global onError handler…
let ignoreNextOnError = false;
export function ignoreNextOnErrorEvent() {
ignoreNextOnError = true;
}
export function startOnErrorInstrumentation() {
if (!win)
return;
const globalOnError = win.onerror;
win.onerror = function (message, fileName, lineNumber, columnNumber, error) {
if (ignoreNextOnError) {
ignoreNextOnError = false;
if (typeof globalOnError === "function") {
return globalOnError.apply(this, arguments);
}
return;
}
let stack = error && error.stack;
if (!stack) {
stack = "at " + fileName + " " + lineNumber;
if (columnNumber != null) {
stack += ":" + columnNumber;
}
}
onUnhandledError({ message: String(message), stack });
if (typeof globalOnError === "function") {
return globalOnError.apply(this, arguments);
}
};
}
export function reportUnhandledError(error, opts) {
if (!error) {
return;
}
if (typeof error === "string") {
onUnhandledError({ message: error, opts });
}
else {
onUnhandledError({ message: error.message, type: error.name, stack: error.stack, opts });
}
}
function onUnhandledError({ message, type, stack, opts }) {
if (!message || reportedErrors > MAX_ERRORS_TO_REPORT) {
return;
}
if (isErrorMessageIgnored(message)) {
return;
}
if (numberOfDifferentErrorsSeen >= MAX_NUMBER_OF_TRACKED_ERRORS) {
seenErrors = {};
numberOfDifferentErrorsSeen = 0;
}
message = String(message).substring(0, 300);
stack = shortenStackTrace(stack);
const location = win?.location.href;
const key = message + stack + location;
let trackedError = seenErrors[key];
if (trackedError) {
trackedError.seenCount++;
}
else {
const attributes = [];
addCommonAttributes(attributes);
addAttribute(attributes, EVENT_NAME, EVENT_NAMES.ERROR);
addAttribute(attributes, EXCEPTION_MESSAGE, message);
if (type) {
addAttribute(attributes, EXCEPTION_TYPE, type);
}
if (stack) {
addAttribute(attributes, EXCEPTION_STACKTRACE, stack);
}
if (opts?.componentStack) {
addAttribute(attributes, EXCEPTION_COMPONENT_STACK, opts?.componentStack.substring(0, 2048));
}
// Add custom attributes last to allow overrides
addAttributes(attributes, opts?.attributes);
trackedError = {
seenCount: 1,
transmittedCount: 0,
log: {
timeUnixNano: nowNanos(),
attributes: attributes,
severityNumber: LOG_SEVERITIES.ERROR,
severityText: "ERROR",
body: {
stringValue: message,
},
},
};
seenErrors[key] = trackedError;
numberOfDifferentErrorsSeen++;
}
scheduleTransmission();
}
export function shortenStackTrace(stack) {
return String(stack || "")
.split("\n")
.slice(0, MAX_STACK_SIZE)
.join("\n");
}
function scheduleTransmission() {
if (scheduledTransmissionTimeoutHandle) {
return;
}
scheduledTransmissionTimeoutHandle = setTimeout(send, 1000);
}
function send() {
if (scheduledTransmissionTimeoutHandle) {
clearTimeout(scheduledTransmissionTimeoutHandle);
scheduledTransmissionTimeoutHandle = null;
}
for (const key in seenErrors) {
if (hasOwnProperty(seenErrors, key)) {
const seenError = seenErrors[key];
if (seenError.seenCount > seenError.transmittedCount) {
sendLog(seenError.log);
reportedErrors++;
}
}
}
seenErrors = {};
numberOfDifferentErrorsSeen = 0;
}