UNPKG

@dynatrace/opentelemetry-core

Version:

Dynatrace OpenTelemetry core package

191 lines (188 loc) 7.23 kB
"use strict"; /* Copyright 2022 Dynatrace LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.addErrorAttributes = addErrorAttributes; exports.getCallSites = getCallSites; const SemConvTrace = require("../../gen/dynatrace/odin/semconv/v1/SemConvTrace"); // ---------------------------------------------------------------------------- /** * Sanitizes strings for exception attributes by using percent encoding for \n * and optional \t. * @param s the string to sanitize * @param inclTabs escape also tabs */ function sanitize(s, inclTabs) { if (s.length > 0) { s = s.replace(/%/g, "%25").replace(/\n/g, "%0a"); if (inclTabs) { s = s.replace(/\t/g, "%09"); } } return s; } // ---------------------------------------------------------------------------- /** * Add attributes for an error to span. * This will overwrite any exiting exception attributes. * @param span The span to add the error attributes * @param error The error to add */ function addErrorAttributes(span, error) { // don't try to serialize non Error objects if (!(error instanceof Error)) { return; } span.setAttribute(SemConvTrace.DT_EXCEPTION_TYPES, sanitize(error.name, false)); const errMessage = `${error.message}`; const lineCnt = errMessage.split("\n").length; span.setAttribute(SemConvTrace.DT_EXCEPTION_MESSAGES, `${lineCnt}\n${errMessage}`); const callSites = getCallSites(error); const lines = []; const fullLines = []; for (const callSite of callSites) { const method = sanitize(callSite.getFunctionName() || "", true); const className = sanitize(callSite.getTypeName() || "", true); let line = `${className}\t${method}`; const fileName = callSite.getFileName(); if (fileName != null) { line += `\t${sanitize(fileName, true)}`; const lineNumber = callSite.getLineNumber(); if (lineNumber != null) { line += `\t${lineNumber}`; } } // use line reference is possible const refLine = fullLines.indexOf(line); if (refLine !== -1) { lines.push(refLine.toString()); } else { fullLines.push(line); lines.push(line); } } span.setAttribute(SemConvTrace.DT_EXCEPTION_SERIALIZED_STACKTRACES, lines.join("\n") + "\n"); } // ---------------------------------------------------------------------------- /** * Returns an array of callsites of the stack of the given error object * @param error The error object * @returns an Array of callsites */ function getCallSites(error) { let result = []; let oldHandlerRestored = false; // cache the original/user code prepareStackTrace function const oldPrepareStackTrace = Error.prepareStackTrace; try { // provide our custom function Error.prepareStackTrace = (err, callSites) => { result = callSites; /* * restore the prepareStackTrace function to the original function to prevent * infinite recursion just in case the user land code has rewritten the setter * see ONE-30021 */ Error.prepareStackTrace = oldPrepareStackTrace; oldHandlerRestored = true; // forward callsites to previous handler if present, otherwise stringify const prepareStackTrace = oldPrepareStackTrace || stackFramesToString; return prepareStackTrace(err, callSites); }; // trigger calling prepareStackTrace by calling the "getter" for the stack property void error.stack; // in case our prepareStackTrace function was not tirggered we parse the stack property on the error object if (result.length === 0) { result = convertStackStringToCallSites(error.stack); } } finally { if (!oldHandlerRestored) { // restore the old handler Error.prepareStackTrace = oldPrepareStackTrace; } } return result; } // ---------------------------------------------------------------------------- /** * Converts an Error and its corresponding stack into a string representation. * Used to serialize a stack in case no prepareStackTrace function was present so it's * up to us to serialize to a string. * @param err The error object * @param stackFrames the stackFrames * @returns string representation of err.stack */ function stackFramesToString(err, stackFrames) { const resultStack = []; if (err) { resultStack.push(err.toString()); } if (Array.isArray(stackFrames)) { for (const callSite of stackFrames) { resultStack.push(` at ${callSite.toString()}`); } } return resultStack.join("\n"); } // ---------------------------------------------------------------------------- /** * Converts a string representation of a callstack into an array of CallSites. * As v8 may endocde function names with '.' it's not possible to extract the typename. * As a result we set typename to null here as function name in serialized stack includes the type * and we don't want to duplicate it. * @param stack string representation of callstack * @return CallSites parsed from stringified stack */ function convertStackStringToCallSites(stack) { if (typeof (stack) !== "string") { return []; } const result = []; // split the array and remove the first element which just contains the error message const stringFramesArray = stack.split("\n").slice(1); for (const stringFrame of stringFramesArray) { const frameMatch = /at (?:(.+)\s+)?\(?(?:(.+?):(\d+):(\d+)|([^)]+))\)?/.exec(stringFrame); if (frameMatch) { const cs = new CallSiteObject(frameMatch[2] || null, frameMatch[1] || null, parseInt(frameMatch[3], 10) || null); result.push(cs); } } return result; } // ============================================================================ /** * Internal implementation of a CallSite. * Follows the NodeJS.CallSite interface but stripped down to what we need. */ class CallSiteObject { constructor(fileName, functionName, lineNumber) { this.fileName = fileName; this.functionName = functionName; this.lineNumber = lineNumber; } getFileName() { return this.fileName; } getLineNumber() { return this.lineNumber; } getFunctionName() { return this.functionName; } getTypeName() { return null; } } //# sourceMappingURL=ErrorUtils.js.map