Comparing version
@@ -27,106 +27,101 @@ // Copyright 2006-2008 the V8 project authors. All rights reserved. | ||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
module.exports = FormatStackTrace; | ||
module.exports = function(format, filter) { | ||
filter = Array.isArray(filter) ? filter : [] | ||
var path = require('path'), | ||
d = path.join('/'), | ||
node_modules = d + 'node_modules' + d; | ||
function FormatStackTrace(error, frames, filter, colors) { | ||
var lines = []; | ||
try { | ||
lines.push(error.toString()); | ||
} catch (e) { | ||
function FormatStackTrace(error, frames) { | ||
var lines = []; | ||
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, filter); | ||
lines.push(error.toString()); | ||
} catch (e) { | ||
try { | ||
line = "<error: " + e + ">"; | ||
lines.push("<error: " + e + ">"); | ||
} catch (ee) { | ||
// Any code that reaches this point is seriously nasty! | ||
line = "<error>"; | ||
lines.push("<error>"); | ||
} | ||
} | ||
if (line !== undefined) { | ||
line = " at " + line; | ||
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>"; | ||
} | ||
} | ||
if (line !== undefined) { | ||
line = " at " + line; | ||
if (colors) { | ||
if (line.indexOf(node_modules) >= 0) { | ||
line = colors.cyan(line); | ||
} else if (line.indexOf(d) >= 0) { | ||
line = colors.red(line); | ||
} else { | ||
line = colors.white(line); | ||
if ('function' === typeof format) { | ||
line = format(line) | ||
} | ||
if (line) { | ||
lines.push(line); | ||
} | ||
} | ||
lines.push(line); | ||
} | ||
return lines.join("\n"); | ||
} | ||
return lines.join("\n"); | ||
} | ||
function FormatSourcePosition(frame, filter) { | ||
var fileLocation = ""; | ||
if (frame.isNative()) { | ||
fileLocation = "native"; | ||
} else if (frame.isEval()) { | ||
fileLocation = "eval at " + frame.getEvalOrigin(); | ||
} else { | ||
var fileName = frame.getFileName(); | ||
if (fileName) { | ||
for(var i=0, l=filter.length; i<l; i++) { | ||
if (fileName.indexOf(filter[i]) !== -1) return; | ||
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) { | ||
for(var i=0, l=filter.length; i<l; i++) { | ||
if (fileName.indexOf(filter[i]) !== -1) return; | ||
} | ||
fileLocation += fileName; | ||
var lineNumber = frame.getLineNumber(); | ||
if (lineNumber != null) { | ||
fileLocation += ":" + lineNumber; | ||
var columnNumber = frame.getColumnNumber(); | ||
if (columnNumber) { | ||
fileLocation += ":" + columnNumber; | ||
} | ||
} | ||
} | ||
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>"; | ||
} | ||
} | ||
} | ||
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) { | ||
} else if (isConstructor) { | ||
line += "new " + (functionName || "<anonymous>"); | ||
} else if (functionName) { | ||
line += functionName; | ||
if (methodName && (methodName != functionName)) { | ||
line += " [as " + methodName + "]"; | ||
} | ||
} else { | ||
line += methodName || "<anonymous>"; | ||
line += fileLocation; | ||
addPrefix = false; | ||
} | ||
} else if (isConstructor) { | ||
line += "new " + (functionName || "<anonymous>"); | ||
} else if (functionName) { | ||
line += functionName; | ||
} else { | ||
line += fileLocation; | ||
addPrefix = false; | ||
if (addPrefix) { | ||
line += " (" + fileLocation + ")"; | ||
} | ||
return line; | ||
} | ||
if (addPrefix) { | ||
line += " (" + fileLocation + ")"; | ||
} | ||
return line; | ||
return FormatStackTrace | ||
} | ||
@@ -7,8 +7,19 @@ var util = require('util') | ||
, fileNameFilter = [__filename, require.resolve('./hook'), 'domain.js'] | ||
, d = require('path').join('/') | ||
, node_modules = d + 'node_modules' + d | ||
, options = { | ||
'long-stack-traces': null | ||
, 'colors': { | ||
'node': 'white', | ||
'node_modules': 'cyan', | ||
'default': 'red' | ||
} | ||
, 'format': defaultFormat | ||
, 'filter': fileNameFilter | ||
} | ||
// use colors module, if available | ||
try { trycatch.colors = require('colors') } catch(err) {} | ||
if (process.stdout.isTTY) { | ||
try { trycatch.colors = require('colors') } catch(err) {} | ||
} | ||
@@ -28,26 +39,60 @@ module.exports = trycatch | ||
trycatch.configure = configure | ||
trycatch.format = FormatStackTrace(defaultFormat, fileNameFilter) | ||
function configure(opts) { | ||
var resetFormat = false | ||
if ('undefined' !== typeof opts['long-stack-traces']) { | ||
options['long-stack-traces'] = Boolean(opts['long-stack-traces']) | ||
if (true === options['long-stack-traces']) { | ||
if (!hooked) { | ||
hooked = true | ||
// Replace built-in async functions, shim callbacks with generator | ||
hookit() | ||
} | ||
// findToken fails when _TOKEN_ deeper than Error.stackTraceLimit | ||
Error.stackTraceLimit = Infinity | ||
trycatch.begin = longStackTraceBegin | ||
trycatch.guard = longStackTraceGuard | ||
} else if (false === options['long-stack-traces'] && hooked) { | ||
console.warn('Turning long-stack-traces off can result in indeterminate behavior.') | ||
Error.stackTraceLimit = 10 | ||
trycatch.begin = domainTrycatch | ||
trycatch.guard = nopGuard | ||
} | ||
} | ||
if (true === options['long-stack-traces']) { | ||
if (!hooked) { | ||
hooked = true | ||
// Replace built-in async functions, shim callbacks with generator | ||
hookit() | ||
if (null != opts.colors && 'object' === typeof opts.colors) { | ||
if ('undefined' !== typeof opts.colors.node) { | ||
options.colors.node = opts.colors.node | ||
} | ||
if ('undefined' !== typeof opts.colors.node_modules) { | ||
options.colors.node_modules = opts.colors.node_modules | ||
} | ||
if ('undefined' !== typeof opts.colors.default) { | ||
options.colors.default = opts.colors.default | ||
} | ||
} | ||
// findToken fails when _TOKEN_ deeper than Error.stackTraceLimit | ||
Error.stackTraceLimit = Infinity | ||
trycatch.begin = longStackTraceBegin | ||
trycatch.guard = longStackTraceGuard | ||
} else if (false === options['long-stack-traces'] && hooked) { | ||
console.warn('Turning long-stack-traces off can result in indeterminate behavior.') | ||
Error.stackTraceLimit = 10 | ||
trycatch.begin = domainTrycatch | ||
trycatch.guard = nopGuard | ||
if ('undefined' !== typeof opts.format) { | ||
if ('function' === typeof opts.format) { | ||
options.format = opts.format | ||
resetFormat = true | ||
} else if (!Boolean(opts.format)) { | ||
options.format = defaultFormat | ||
resetFormat = true | ||
} | ||
} | ||
if (Array.isArray(opts.filter)) { | ||
options.filter = opts.filter | ||
resetFormat = true | ||
} | ||
if (resetFormat) { | ||
trycatch.format = FormatStackTrace(options.format, options.filter) | ||
} | ||
} | ||
@@ -57,3 +102,3 @@ | ||
hook(function generateShim(callback, fnName) { | ||
return trycatch.guard(callback, fnName) | ||
return trycatch.guard(callback, fnName) | ||
}) | ||
@@ -67,45 +112,47 @@ } | ||
function domainTrycatch (fn, cb, token) { | ||
var parentDomain = domain.active | ||
, d = domain.create() | ||
function domainTrycatch (fn, cb, isLongStackTrace) { | ||
var parentDomain = domain.active | ||
, d = domain.create() | ||
d.on('error', function onError(err) { | ||
var isError = err instanceof Error | ||
if (true === options['long-stack-traces'] && '_TOKEN_' === fn.name) { | ||
if (isError) { | ||
err = handleError(err) | ||
} else { | ||
console.warn('Unable to generate long-stack-trace for thrown non-Error') | ||
} | ||
} | ||
d.on('error', function onError(err) { | ||
err = buildError(err, Boolean(options['long-stack-traces'] && isLongStackTrace)) | ||
if (!isError) { | ||
err = new Error(err) | ||
} | ||
runInDomain(parentDomain, function() { | ||
cb(err) | ||
}) | ||
}) | ||
runInDomain(d, fn) | ||
runInDomain(parentDomain, function() { | ||
cb(err) | ||
}) | ||
}) | ||
runInDomain(d, fn) | ||
} | ||
function runInDomain(d, fn) { | ||
if (d && !d._disposed) { | ||
try { | ||
d.run(fn) | ||
} catch(e) { | ||
d.emit('error', e) | ||
} | ||
} else { | ||
fn() | ||
} | ||
if (d && !d._disposed) { | ||
try { | ||
d.run(fn) | ||
} catch(e) { | ||
d.emit('error', e) | ||
} | ||
} else { | ||
fn() | ||
} | ||
} | ||
function handleError(err) { | ||
function buildError(err, isLongStackTrace) { | ||
var parent | ||
, token | ||
, skip = false | ||
, isError = err instanceof Error | ||
// Coerce to error | ||
if (!isError) { | ||
err = new Error(err) | ||
} | ||
if (!isLongStackTrace) { | ||
return generateStack(err) | ||
} else if (!isError) { | ||
console.warn('Unable to generate long-stack-trace for thrown non-Error') | ||
return generateStack(err) | ||
} | ||
if (err.token) { | ||
@@ -159,3 +206,3 @@ skip = true | ||
domainTrycatch(_TOKEN_, catchFn) | ||
domainTrycatch(_TOKEN_, catchFn, true) | ||
} | ||
@@ -165,8 +212,3 @@ | ||
if ('function' !== typeof fn) { | ||
fn = function(error, structuredStackTrace) { | ||
return FormatStackTrace(error | ||
, structuredStackTrace | ||
, fileNameFilter | ||
, trycatch.colors) | ||
} | ||
fn = trycatch.format | ||
} | ||
@@ -207,9 +249,9 @@ if (!err) err = new Error | ||
function stackSearch(error, structuredStackTrace) { | ||
var stack | ||
var stack, fn, i, l | ||
if (!structuredStackTrace) return | ||
stack = FormatStackTrace(error, structuredStackTrace, fileNameFilter, trycatch.colors) | ||
stack = trycatch.format(error, structuredStackTrace) | ||
for (var fn, i=0, l=structuredStackTrace.length; i<l; i++) { | ||
for (i=0, l=structuredStackTrace.length; i<l; i++) { | ||
fn = structuredStackTrace[i].fun | ||
@@ -224,2 +266,26 @@ if ('_TOKEN_' === fn.name) { | ||
return {stack: stack} | ||
} | ||
} | ||
function defaultFormat(line) { | ||
var type, color | ||
if (trycatch.colors) { | ||
if (line.indexOf(node_modules) >= 0) { | ||
type = "node_modules"; | ||
} else if (line.indexOf(d) >= 0) { | ||
type = "default"; | ||
} else { | ||
type = "node" | ||
} | ||
color = options.colors[type] | ||
if ('none' === color || !Boolean(color)) { | ||
return | ||
} | ||
if (trycatch.colors[color]) { | ||
return trycatch.colors[color](line); | ||
} | ||
} | ||
return line | ||
} |
{ | ||
"name": "trycatch", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "An asynchronous domain-based exception handler with long stack traces for node.js", | ||
@@ -5,0 +5,0 @@ "homepage": "http://github.com/CrabDude/trycatch", |
trycatch | ||
======= | ||
An asynchronous try catch exception handler with long stack traces for node.js | ||
An asynchronous domain-based try/catch exception handler with (optional) long stack traces for node.js | ||
**Now PRODUCTION Ready!** | ||
With the update to 0.2.0 | ||
* error-handling is now [domain-based](http://nodejs.org/api/domain.html) | ||
* long-stack-traces are optional (off by default) | ||
* long-stack-traces are lazy | ||
With the update to 0.1.0, stack traces are now lazy, and all try/catch blocks conform to [V8 best practices](https://github.com/joyent/node/wiki/Best-practices-and-gotchas-with-v8). | ||
Also, trycatch conforms to try/catch [V8 best practices](https://github.com/joyent/node/wiki/Best-practices-and-gotchas-with-v8). | ||
@@ -20,7 +23,42 @@ | ||
Because trycatch shims all native I/O calls, it must be required before any other modules. | ||
```javascript | ||
var trycatch = require('trycatch') | ||
trycatch(fnTry, fnCatch) | ||
``` | ||
var trycatch = require('trycatch') | ||
trycatch(fnTry, fnCatch) | ||
Optional Long-Stack-Traces: | ||
```javascript | ||
// Because trycatch shims all native I/O calls, | ||
// it must be required & configured with 'long-stack-traces' before any other modules. | ||
var trycatch = require('trycatch') | ||
trycatch.configure({'long-stack-traces': true}) | ||
trycatch(fnTry, fnCatch) | ||
``` | ||
Colors: | ||
```javascript | ||
var trycatch = require('trycatch') | ||
trycatch.configure({ | ||
colors: { | ||
// 'none' or falsy values will omit | ||
'node': 'none', | ||
'node_modules': false, | ||
'default': 'yellow' | ||
} | ||
}) | ||
trycatch(fnTry, fnCatch) | ||
``` | ||
Advanced Formatting: | ||
```javascript | ||
var trycatch = require('trycatch') | ||
trycatch.configure({ | ||
format: function(line) { | ||
// Alter final output (falsy values will omit) | ||
return line | ||
} | ||
}) | ||
trycatch(fnTry, fnCatch) | ||
``` | ||
Basic Example | ||
@@ -30,15 +68,14 @@ ------------- | ||
```javascript | ||
var trycatch = require("trycatch"), | ||
_ = require('underscore')._ | ||
var trycatch = require("trycatch"), | ||
_ = require('underscore')._ | ||
trycatch(function() { | ||
_.map(['Error 1', 'Error 2'], function foo(v) { | ||
setTimeout(function() { | ||
throw new Error(v) | ||
}, 10) | ||
}) | ||
}, function(err) { | ||
console.log("Async error caught!\n", err.stack); | ||
}); | ||
trycatch(function() { | ||
_.map(['Error 1', 'Error 2'], function foo(v) { | ||
setTimeout(function() { | ||
throw new Error(v) | ||
}, 10) | ||
}) | ||
}, function(err) { | ||
console.log("Async error caught!\n", err.stack); | ||
}); | ||
``` | ||
@@ -60,12 +97,12 @@ | ||
```javascript | ||
http.createServer(function(req, res) { | ||
trycatch(function() { | ||
setTimeout(function() { | ||
throw new Error('Baloney!'); | ||
}, 1000); | ||
}, function(err) { | ||
res.writeHead(500); | ||
res.end(err.stack); | ||
}); | ||
}).listen(8000); | ||
http.createServer(function(req, res) { | ||
trycatch(function() { | ||
setTimeout(function() { | ||
throw new Error('Baloney!'); | ||
}, 1000); | ||
}, function(err) { | ||
res.writeHead(500); | ||
res.end(err.stack); | ||
}); | ||
}).listen(8000); | ||
``` | ||
@@ -80,2 +117,2 @@ | ||
Special thanks to [Tom Robinson](https://github.com/tlrobinson) for his [long-stack-traces](https://github.com/tlrobinson/long-stack-traces) module and [Tim Caswell](https://github.com/creationix) who built out the initial hook.js code. | ||
Special thanks to [Tom Robinson](https://github.com/tlrobinson) for his [long-stack-traces](https://github.com/tlrobinson/long-stack-traces) module and [Tim Caswell](https://github.com/creationix) who built out the initial hook.js code. |
@@ -21,3 +21,3 @@ var trycatch = require('../lib/trycatch') | ||
assert.throws(function() { | ||
trycatch(null, function() {}) | ||
trycatch(null, function() {}) | ||
}, Error) | ||
@@ -24,0 +24,0 @@ }) |
51315
5.75%16
6.67%848
9.84%115
49.35%3
50%