181 lines
4.6 KiB
JavaScript
181 lines
4.6 KiB
JavaScript
|
|
// use a already existing formater or fallback to the default v8 formater
|
|
var defaultFormater = require('./format.js');
|
|
var originalCallSiteSymbol = Symbol('originalCallSite');
|
|
var mutatedCallSiteSymbol = Symbol('mutatedCallSite');
|
|
|
|
// public define API
|
|
function stackChain() {
|
|
this.extend = new TraceModifier();
|
|
this.filter = new TraceModifier();
|
|
this.format = new StackFormater();
|
|
this.version = require('./package.json').version;
|
|
}
|
|
|
|
|
|
var SHORTCIRCUIT_CALLSITE = false;
|
|
stackChain.prototype.callSite = function collectCallSites(options) {
|
|
if (!options) options = {};
|
|
|
|
// Get CallSites
|
|
SHORTCIRCUIT_CALLSITE = true;
|
|
var obj = {};
|
|
Error.captureStackTrace(obj, collectCallSites);
|
|
var callSites = obj.stack;
|
|
SHORTCIRCUIT_CALLSITE = false;
|
|
|
|
// Slice
|
|
callSites = callSites.slice(options.slice || 0);
|
|
|
|
// Modify CallSites
|
|
if (options.extend) callSites = this.extend._modify(obj, callSites);
|
|
if (options.filter) callSites = this.filter._modify(obj, callSites);
|
|
|
|
// Done
|
|
return callSites;
|
|
};
|
|
|
|
stackChain.prototype.originalCallSite = function (error) {
|
|
error.stack;
|
|
return error[originalCallSiteSymbol];
|
|
};
|
|
|
|
stackChain.prototype.mutatedCallSite = function (error) {
|
|
error.stack;
|
|
return error[mutatedCallSiteSymbol];
|
|
};
|
|
|
|
var chain = new stackChain();
|
|
|
|
function TraceModifier() {
|
|
this._modifiers = [];
|
|
}
|
|
|
|
TraceModifier.prototype._modify = function (error, frames) {
|
|
for (var i = 0, l = this._modifiers.length; i < l; i++) {
|
|
frames = this._modifiers[i](error, frames);
|
|
}
|
|
|
|
return frames;
|
|
};
|
|
|
|
TraceModifier.prototype.attach = function (modifier) {
|
|
this._modifiers.push(modifier);
|
|
};
|
|
|
|
TraceModifier.prototype.deattach = function (modifier) {
|
|
var index = this._modifiers.indexOf(modifier);
|
|
|
|
if (index === -1) return false;
|
|
|
|
this._modifiers.splice(index, 1);
|
|
return true;
|
|
};
|
|
|
|
function StackFormater() {
|
|
this._formater = defaultFormater;
|
|
this._previous = undefined;
|
|
}
|
|
|
|
StackFormater.prototype.replace = function (formater) {
|
|
if (formater) {
|
|
this._formater = formater;
|
|
} else {
|
|
this.restore();
|
|
}
|
|
};
|
|
|
|
StackFormater.prototype.restore = function () {
|
|
this._formater = defaultFormater;
|
|
this._previous = undefined;
|
|
};
|
|
|
|
StackFormater.prototype._backup = function () {
|
|
this._previous = this._formater;
|
|
};
|
|
|
|
StackFormater.prototype._roolback = function () {
|
|
if (this._previous === defaultFormater) {
|
|
this.replace(undefined);
|
|
} else {
|
|
this.replace(this._previous);
|
|
}
|
|
|
|
this._previous = undefined;
|
|
};
|
|
|
|
|
|
//
|
|
// Set Error.prepareStackTrace thus allowing stack-chain
|
|
// to take control of the Error().stack formating.
|
|
//
|
|
|
|
// If there already is a custom stack formater, then set
|
|
// that as the stack-chain formater.
|
|
if (Error.prepareStackTrace) {
|
|
chain.format.replace(Error.prepareStackTrace);
|
|
}
|
|
|
|
var SHORTCIRCUIT_FORMATER = false;
|
|
function prepareStackTrace(error, originalFrames) {
|
|
if (SHORTCIRCUIT_CALLSITE) return originalFrames;
|
|
if (SHORTCIRCUIT_FORMATER) return defaultFormater(error, originalFrames);
|
|
|
|
// Make a loss copy of originalFrames
|
|
var frames = originalFrames.concat();
|
|
|
|
// extend frames
|
|
frames = chain.extend._modify(error, frames);
|
|
|
|
// filter frames
|
|
frames = chain.filter._modify(error, frames);
|
|
|
|
// reduce frames to match Error.stackTraceLimit
|
|
frames = frames.slice(0, Error.stackTraceLimit);
|
|
|
|
// Set the callSite property
|
|
// But only if it hasn't been explicitly set, otherwise
|
|
// error.stack would have unintended side effects. Check also for
|
|
// non-extensible/sealed objects, such as those from Google's Closure Library
|
|
if (Object.isExtensible(error)) {
|
|
error[originalCallSiteSymbol] = originalFrames;
|
|
error[mutatedCallSiteSymbol] = frames;
|
|
}
|
|
|
|
// format frames
|
|
SHORTCIRCUIT_FORMATER = true;
|
|
var format = chain.format._formater(error, frames);
|
|
SHORTCIRCUIT_FORMATER = false;
|
|
|
|
return format;
|
|
}
|
|
|
|
// Replace the v8 stack trace creator
|
|
Object.defineProperty(Error, 'prepareStackTrace', {
|
|
'get': function () {
|
|
return prepareStackTrace;
|
|
},
|
|
|
|
'set': function (formater) {
|
|
// If formater is prepareStackTrace it means that someone ran
|
|
// var old = Error.prepareStackTrace;
|
|
// Error.prepareStackTrace = custom
|
|
// new Error().stack
|
|
// Error.prepareStackTrace = old;
|
|
// The effect of this, should be that the old behaviour is restored.
|
|
if (formater === prepareStackTrace) {
|
|
chain.format._roolback();
|
|
}
|
|
// Error.prepareStackTrace was set, this means that someone is
|
|
// trying to take control of the Error().stack format. Make
|
|
// them belive they succeeded by setting them up as the stack-chain
|
|
// formater.
|
|
else {
|
|
chain.format._backup();
|
|
chain.format.replace(formater);
|
|
}
|
|
}
|
|
});
|
|
|
|
module.exports = chain;
|