* temp: plugin API * fix a bug in the replay handler and rename some type names. * update integration test * improve plugin types and handle legacy log data * use different naming in record and replay bundles * delete unreferenced types Co-authored-by: Lucky Feng <294889365@qq.com>
256 lines
8.1 KiB
TypeScript
256 lines
8.1 KiB
TypeScript
// tslint:disable
|
|
/**
|
|
* Class StackFrame is a fork of https://github.com/stacktracejs/stackframe/blob/master/stackframe.js
|
|
* I fork it because:
|
|
* 1. There are some build issues when importing this package.
|
|
* 2. Rewrites into typescript give us a better type interface.
|
|
* 3. StackFrame contains some functions we don't need.
|
|
*/
|
|
export class StackFrame {
|
|
private fileName: string;
|
|
private functionName: string;
|
|
private lineNumber?: number;
|
|
private columnNumber?: number;
|
|
|
|
constructor(obj: {
|
|
fileName?: string;
|
|
functionName?: string;
|
|
lineNumber?: number;
|
|
columnNumber?: number;
|
|
}) {
|
|
this.fileName = obj.fileName || '';
|
|
this.functionName = obj.functionName || '';
|
|
this.lineNumber = obj.lineNumber;
|
|
this.columnNumber = obj.columnNumber;
|
|
}
|
|
|
|
toString() {
|
|
const lineNumber = this.lineNumber || '';
|
|
const columnNumber = this.columnNumber || '';
|
|
if (this.functionName) {
|
|
return (
|
|
this.functionName +
|
|
' (' +
|
|
this.fileName +
|
|
':' +
|
|
lineNumber +
|
|
':' +
|
|
columnNumber +
|
|
')'
|
|
);
|
|
}
|
|
return this.fileName + ':' + lineNumber + ':' + columnNumber;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ErrorStackParser is a fork of https://github.com/stacktracejs/error-stack-parser/blob/master/error-stack-parser.js
|
|
* I fork it because:
|
|
* 1. There are some build issues when importing this package.
|
|
* 2. Rewrites into typescript give us a better type interface.
|
|
*/
|
|
const FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/;
|
|
const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
|
|
const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/;
|
|
export const ErrorStackParser = {
|
|
/**
|
|
* Given an Error object, extract the most information from it.
|
|
*
|
|
* @param {Error} error object
|
|
* @return {Array} of StackFrames
|
|
*/
|
|
parse: function (error: Error): StackFrame[] {
|
|
if (
|
|
// @ts-ignore
|
|
typeof error.stacktrace !== 'undefined' ||
|
|
// @ts-ignore
|
|
typeof error['opera#sourceloc'] !== 'undefined'
|
|
) {
|
|
return this.parseOpera(
|
|
error as {
|
|
stacktrace?: string;
|
|
message: string;
|
|
stack?: string;
|
|
},
|
|
);
|
|
} else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) {
|
|
return this.parseV8OrIE(error as { stack: string });
|
|
} else if (error.stack) {
|
|
return this.parseFFOrSafari(error as { stack: string });
|
|
} else {
|
|
throw new Error('Cannot parse given Error object');
|
|
}
|
|
},
|
|
// Separate line and column numbers from a string of the form: (URI:Line:Column)
|
|
extractLocation: function (urlLike: string) {
|
|
// Fail-fast but return locations like "(native)"
|
|
if (urlLike.indexOf(':') === -1) {
|
|
return [urlLike];
|
|
}
|
|
|
|
const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
|
|
const parts = regExp.exec(urlLike.replace(/[()]/g, ''));
|
|
if (!parts) throw new Error(`Cannot parse given url: ${urlLike}`);
|
|
return [parts[1], parts[2] || undefined, parts[3] || undefined];
|
|
},
|
|
parseV8OrIE: function (error: { stack: string }) {
|
|
const filtered = error.stack.split('\n').filter(function (line) {
|
|
return !!line.match(CHROME_IE_STACK_REGEXP);
|
|
}, this);
|
|
|
|
return filtered.map(function (line) {
|
|
if (line.indexOf('(eval ') > -1) {
|
|
// Throw away eval information until we implement stacktrace.js/stackframe#8
|
|
line = line
|
|
.replace(/eval code/g, 'eval')
|
|
.replace(/(\(eval at [^()]*)|(\),.*$)/g, '');
|
|
}
|
|
let sanitizedLine = line.replace(/^\s+/, '').replace(/\(eval code/g, '(');
|
|
|
|
// capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in
|
|
// case it has spaces in it, as the string is split on \s+ later on
|
|
const location = sanitizedLine.match(/ (\((.+):(\d+):(\d+)\)$)/);
|
|
|
|
// remove the parenthesized location from the line, if it was matched
|
|
sanitizedLine = location
|
|
? sanitizedLine.replace(location[0], '')
|
|
: sanitizedLine;
|
|
|
|
const tokens = sanitizedLine.split(/\s+/).slice(1);
|
|
// if a location was matched, pass it to extractLocation() otherwise pop the last token
|
|
const locationParts = this.extractLocation(
|
|
location ? location[1] : tokens.pop(),
|
|
);
|
|
const functionName = tokens.join(' ') || undefined;
|
|
const fileName =
|
|
['eval', '<anonymous>'].indexOf(locationParts[0]) > -1
|
|
? undefined
|
|
: locationParts[0];
|
|
|
|
return new StackFrame({
|
|
functionName,
|
|
fileName,
|
|
lineNumber: locationParts[1],
|
|
columnNumber: locationParts[2],
|
|
});
|
|
}, this);
|
|
},
|
|
parseFFOrSafari: function (error: { stack: string }) {
|
|
const filtered = error.stack.split('\n').filter(function (line) {
|
|
return !line.match(SAFARI_NATIVE_CODE_REGEXP);
|
|
}, this);
|
|
|
|
return filtered.map(function (line) {
|
|
// Throw away eval information until we implement stacktrace.js/stackframe#8
|
|
if (line.indexOf(' > eval') > -1) {
|
|
line = line.replace(
|
|
/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,
|
|
':$1',
|
|
);
|
|
}
|
|
|
|
if (line.indexOf('@') === -1 && line.indexOf(':') === -1) {
|
|
// Safari eval frames only have function names and nothing else
|
|
return new StackFrame({
|
|
functionName: line,
|
|
});
|
|
} else {
|
|
const functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/;
|
|
const matches = line.match(functionNameRegex);
|
|
const functionName = matches && matches[1] ? matches[1] : undefined;
|
|
const locationParts = this.extractLocation(
|
|
line.replace(functionNameRegex, ''),
|
|
);
|
|
|
|
return new StackFrame({
|
|
functionName,
|
|
fileName: locationParts[0],
|
|
lineNumber: locationParts[1],
|
|
columnNumber: locationParts[2],
|
|
});
|
|
}
|
|
}, this);
|
|
},
|
|
parseOpera: function (e: {
|
|
stacktrace?: string;
|
|
message: string;
|
|
stack?: string;
|
|
}): StackFrame[] {
|
|
if (
|
|
!e.stacktrace ||
|
|
(e.message.indexOf('\n') > -1 &&
|
|
e.message.split('\n').length > e.stacktrace.split('\n').length)
|
|
) {
|
|
return this.parseOpera9(e as { message: string });
|
|
} else if (!e.stack) {
|
|
return this.parseOpera10(e as { stacktrace: string });
|
|
} else {
|
|
return this.parseOpera11(e as { stack: string });
|
|
}
|
|
},
|
|
parseOpera9: function (e: { message: string }) {
|
|
const lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
|
|
const lines = e.message.split('\n');
|
|
const result = [];
|
|
|
|
for (let i = 2, len = lines.length; i < len; i += 2) {
|
|
const match = lineRE.exec(lines[i]);
|
|
if (match) {
|
|
result.push(
|
|
new StackFrame({
|
|
fileName: match[2],
|
|
lineNumber: parseFloat(match[1]),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
parseOpera10: function (e: { stacktrace: string }) {
|
|
const lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
|
|
const lines = e.stacktrace.split('\n');
|
|
const result = [];
|
|
|
|
for (let i = 0, len = lines.length; i < len; i += 2) {
|
|
const match = lineRE.exec(lines[i]);
|
|
if (match) {
|
|
result.push(
|
|
new StackFrame({
|
|
functionName: match[3] || undefined,
|
|
fileName: match[2],
|
|
lineNumber: parseFloat(match[1]),
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
// Opera 10.65+ Error.stack very similar to FF/Safari
|
|
parseOpera11: function (error: { stack: string }) {
|
|
const filtered = error.stack.split('\n').filter(function (line) {
|
|
return (
|
|
!!line.match(FIREFOX_SAFARI_STACK_REGEXP) &&
|
|
!line.match(/^Error created at/)
|
|
);
|
|
}, this);
|
|
|
|
return filtered.map(function (line: string) {
|
|
const tokens = line.split('@');
|
|
const locationParts = this.extractLocation(tokens.pop());
|
|
const functionCall = tokens.shift() || '';
|
|
const functionName =
|
|
functionCall
|
|
.replace(/<anonymous function(: (\w+))?>/, '$2')
|
|
.replace(/\([^)]*\)/g, '') || undefined;
|
|
return new StackFrame({
|
|
functionName,
|
|
fileName: locationParts[0],
|
|
lineNumber: locationParts[1],
|
|
columnNumber: locationParts[2],
|
|
});
|
|
}, this);
|
|
},
|
|
};
|