Skip to content

Instantly share code, notes, and snippets.

@CrabDude
Forked from creationix/formatStackTrace.js
Created September 22, 2011 19:07
Show Gist options
  • Save CrabDude/1235711 to your computer and use it in GitHub Desktop.
Save CrabDude/1235711 to your computer and use it in GitHub Desktop.
Beginning of an event-source hook for system-wide stack handling hooks.
// Copyright 2006-2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
module.exports = FormatStackTrace;
function FormatStackTrace(error, frames) {
var lines = [];
try {
lines.push(error.toString());
} catch (e) {
try {
lines.push("<error: " + e + ">");
} catch (ee) {
lines.push("<error>");
}
}
for (var i = 0; i < frames.length; i++) {
var frame = frames[i];
var line;
try {
line = FormatSourcePosition(frame);
} catch (e) {
try {
line = "<error: " + e + ">";
} catch (ee) {
// Any code that reaches this point is seriously nasty!
line = "<error>";
}
}
lines.push(" at " + line);
}
return lines.join("\n");
}
function FormatSourcePosition(frame) {
var fileLocation = "";
if (frame.isNative()) {
fileLocation = "native";
} else if (frame.isEval()) {
fileLocation = "eval at " + frame.getEvalOrigin();
} else {
var fileName = frame.getFileName();
if (fileName) {
fileLocation += fileName;
var lineNumber = frame.getLineNumber();
if (lineNumber != null) {
fileLocation += ":" + lineNumber;
var columnNumber = frame.getColumnNumber();
if (columnNumber) {
fileLocation += ":" + columnNumber;
}
}
}
}
if (!fileLocation) {
fileLocation = "unknown source";
}
var line = "";
var functionName = frame.getFunction().name;
var addPrefix = true;
var isConstructor = frame.isConstructor();
var isMethodCall = !(frame.isToplevel() || isConstructor);
if (isMethodCall) {
var methodName = frame.getMethodName();
line += frame.getTypeName() + ".";
if (functionName) {
line += functionName;
if (methodName && (methodName != functionName)) {
line += " [as " + methodName + "]";
}
} else {
line += methodName || "<anonymous>";
}
} else if (isConstructor) {
line += "new " + (functionName || "<anonymous>");
} else if (functionName) {
line += functionName;
} else {
line += fileLocation;
addPrefix = false;
}
if (addPrefix) {
line += " (" + fileLocation + ")";
}
return line;
}
// This hook function is called in the stack of the function that registers the
// async callback. It has access to the raw callback provided by the user and
// can replace it by returning a wrapped version. This provides access to both the
// event source that caused the async event to happen as well as the stack when
// the event happens. It's easy to implement something like long-stack traces
// or light-weight domains using this hook.
module.exports = function (hook) {
if (alreadyRequired) throw new Error("This should only be required and used once");
alreadyRequired = true;
// Wrap setTimeout and setInterval
["setTimeout", "setInterval"].forEach(function (name) {
var original = this[name];
this[name] = function (callback) {
arguments[0] = hook(callback);
return original.apply(this, arguments);
};
});
// Wrap process.nextTick
var nextTick = process.nextTick;
process.nextTick = function wrappedNextTick(callback) {
arguments[0] = hook(callback);
return nextTick.apply(this, arguments);
}
// Wrap FS module async functions
var FS = require('fs');
Object.keys(FS).forEach(function (name) {
// If it has a *Sync counterpart, it's probably async
if (!FS.hasOwnProperty(name + "Sync")) return;
var original = FS[name];
FS[name] = function () {
var i = arguments.length - 1;
if (typeof arguments[i] === 'function') {
arguments[i] = hook(arguments[i]);
}
return original.apply(this, arguments);
};
});
// Wrap EventEmitters
var EventEmitter = require('events').EventEmitter;
var onEvent = EventEmitter.prototype.on;
EventEmitter.prototype.on = EventEmitter.prototype.addListener = function (type, callback) {
var newCallback = hook(callback);
if (newCallback !== callback) {
callback.wrappedCallback = newCallback;
arguments[1] = newCallback;
}
return onEvent.apply(this, arguments);
};
var removeEvent = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeListener = function (type, callback) {
if (callback && callback.hasOwnProperty("wrappedCallback")) {
arguments[1] = callback.wrappedCallback;
}
return removeEvent.apply(this, arguments);
};
}
var alreadyRequired;
module.exports = scoper;
var FormatStackTrace = require('./formatStackTrace');
// Use the event-source hook to propigate tokens to new stacks
require('./hook')(function (next) {
var token = findToken();
if (!token) return next;
return (function _TOKEN_() {
try {
return next.apply(this, arguments);
} catch (err) {
var token = findToken(err);
if (!token) throw err;
err.stack = token.stack;
token(err);
}
}).bind(token);
});
// Tags a stack and all decendent stacks with a token
function scoper(errorHandler, next) {
(function _TOKEN_() {
next();
}).call(errorHandler);
}
// Looks for a token in the current stack using the V8 stack trace API
function findToken(err) {
if (!err) err = new Error();
var original = Error.prepareStackTrace;
Error.prepareStackTrace = stackSearch
var token = err.stack;
Error.prepareStackTrace = original;
return token;
}
function stackSearch(error, structuredStackTrace) {
if (!structuredStackTrace) return;
for (var i = 0, l = structuredStackTrace.length; i < l; i++) {
var callSite = structuredStackTrace[i];
if (callSite.fun.name === '_TOKEN_') {
var token = callSite.getThis();
token.stack = FormatStackTrace(error, structuredStackTrace);
return token;
}
}
}
var Scoper = require('./scoper');
// Embed a token and test it by starting a stack that has an async link in it.
// The exception in C should
Scoper(function (err) {
console.log("This is a scoped error handler!", err.stack);
}, function () {
a();
});
function a() {
setTimeout(b, 10);
}
function b() {
c();
}
function c() {
throw new Error("ME?");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment