UNPKG

@dash0/sdk-web

Version:

Dash0's Web SDK to collect telemetry from end-users' web browsers

140 lines (139 loc) 4.94 kB
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; }