Socket
Socket
Sign inDemoInstall

rollbar

Package Overview
Dependencies
Maintainers
3
Versions
151
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rollbar - npm Package Compare versions

Comparing version 0.4.5 to 0.5.0

21

CHANGELOG.md
# Change Log
**0.5.0**
- Refactored most of the notifier code to use the async library.
- Fixed myriad JSLint warnings/errors.
- Fixed various bugs related to the different `handler` options.
- Added `X-Rollbar-Access-Token` header for faster responses from the server.
- Fix bug where the app could shut down before all data was sent to Rollbar and/or the callbacks called, ([#34](https://github.com/rollbar/node_rollbar/issues/34))
### Backwards incompatible changes
- Removed `handler`, `handlerInterval` options
- There is only a single handler type now which will send the error/message
as soon as it can. The communication is all done via asynchronous calls and will
not block IO.
- Removed `batchSize` option
- The notifier no longer batches together data before sending it. This was error prone and could lead to loss of
errors if there was a problem reporting any in the batch.
- Removed rollbar.shutdown()
- Now that there are no longer any `setInterval()` calls, there is no need to wait for anything from the Rollbar
library in order to shutdown cleanly.
**0.4.5**

@@ -4,0 +25,0 @@ - Fix a bug that was causing the notifier to not catch all uncaught exceptions. ([#36](https://github.com/rollbar/node_rollbar/pull/36))

3

examples/express.js

@@ -11,6 +11,5 @@ var express = require('express');

app.use(rollbar.errorHandler("ACCESS_TOKEN",
{environment: 'playground'}));
app.use(rollbar.errorHandler("ACCESS_TOKEN", {environment: 'playground'}));
console.log('browse to http://localhost:9876/ then go to your rollbar account: http://rollbar.com/');
app.listen(9876);

@@ -11,6 +11,5 @@ var express = require('express');

app.use(rollbar.errorHandler("ACCESS_TOKEN",
{environment: 'playground'}));
app.use(rollbar.errorHandler("ACCESS_TOKEN", {environment: 'playground'}));
console.log('browse to http://localhost:9876/ then go to your rollbar account: http://rollbar.com/');
app.listen(9876);

@@ -0,1 +1,6 @@

/*jslint devel: true, nomen: true, plusplus: true, regexp: true, indent: 2, maxlen: 100 */
"use strict";
var async = require('async');
var url = require('url');

@@ -10,2 +15,3 @@ var http = require('http');

var SETTINGS = {

@@ -19,71 +25,67 @@ accessToken: null,

/*
* Public API
* Internal
*/
exports.init = function(accessToken, options) {
options = options || {};
exports.accessToken = accessToken;
exports.endpoint = options.endpoint || exports.endpoint;
for (var opt in options) {
SETTINGS[opt] = options[opt];
}
function transportOpts(path, method) {
var port;
port = SETTINGS.port ||
(SETTINGS.protocol === 'http' ? 80 : (SETTINGS.protocol === 'https' ? 443 : undefined));
SETTINGS.endpointOpts = url.parse(exports.endpoint);
SETTINGS.protocol = SETTINGS.endpointOpts.protocol.split(':')[0];
SETTINGS.transport = {http: http, https: https}[SETTINGS.protocol];
SETTINGS.proxy = options.proxy;
return {
host: SETTINGS.endpointOpts.host,
port: port,
path: SETTINGS.endpointOpts.path + path,
method: method
};
}
var portCheck = SETTINGS.endpointOpts.host.split(':');
if (portCheck.length > 1) {
SETTINGS.endpointOpts.host = portCheck[0];
SETTINGS.port = parseInt(portCheck[1]);
function parseApiResponse(respData, callback) {
try {
respData = JSON.parse(respData);
} catch (e) {
console.error('[Rollbar] Could not parse api response, err: ' + e);
return callback(e);
}
};
if (respData.err) {
console.error('[Rollbar] Received error: ' + respData.message);
return callback(new Error('Api error: ' + (respData.message || 'Unknown error')));
}
exports.postItems = function(items, callback) {
return postApi('items/', buildPayload(items), callback);
};
/** Internal **/
function getApi(path, params, callback) {
var transport = SETTINGS.transport;
var opts = transportOpts(path, 'GET');
return makeApiRequest(transport, opts, null, callback);
if (SETTINGS.verbose) {
console.log('[Rollbar] Successful api response');
}
callback(null, respData.result);
}
function postApi(path, payload, callback) {
var transport = SETTINGS.transport;
var opts = transportOpts(path, 'POST');
function makeApiRequest(transport, opts, bodyObj, callback) {
var writeData, req;
return makeApiRequest(transport, opts, payload, callback);
}
if (!bodyObj) {
return callback(new Error('Cannot send empty request'));
}
function makeApiRequest(transport, opts, bodyObj, callback) {
var writeData = null;
if (bodyObj) {
try {
try {
try {
writeData = JSON.stringify(bodyObj);
} catch (e) {
console.error('[Rollbar] Could not serialize to JSON - falling back to safe-stringify');
writeData = stringify(bodyObj);
}
} catch(e) {
console.error(e);
return callback(e);
writeData = JSON.stringify(bodyObj);
} catch (e) {
console.error('[Rollbar] Could not serialize to JSON - falling back to safe-stringify');
writeData = stringify(bodyObj);
}
opts.headers = opts.headers || {};
opts.headers['Content-Type'] = 'application/json';
opts.headers['Content-Length'] = Buffer.byteLength(writeData, 'utf8');
} catch (e) {
console.error('[Rollbar] Could not safe-stringify data. Giving up');
return callback(e);
}
opts.headers = opts.headers || {};
opts.headers['Content-Type'] = 'application/json';
opts.headers['Content-Length'] = Buffer.byteLength(writeData, 'utf8');
opts.headers['X-Rollbar-Access-Token'] = exports.accessToken;
if (SETTINGS.proxy) {

@@ -96,18 +98,19 @@ opts.path = SETTINGS.protocol + '://' + opts.host + opts.path;

var req = transport.request(opts, function(resp) {
req = transport.request(opts, function (resp) {
var respData = [];
resp.setEncoding('utf8');
resp.on('data', function(chunk) {
resp.on('data', function (chunk) {
respData.push(chunk);
});
resp.on('end', function() {
resp.on('end', function () {
respData = respData.join('');
return parseApiResponse(respData, callback);
parseApiResponse(respData, callback);
});
});
req.on('error', function(err) {
req.on('error', function (err) {
console.error('[Rollbar] Could not make request to rollbar, ' + err);
return callback(err);
callback(err);
});

@@ -122,30 +125,9 @@

function parseApiResponse(respData, callback) {
try {
respData = JSON.parse(respData);
} catch (e) {
console.error('[Rollbar] Could not parse api response, err: ' + e);
return callback(e);
}
function postApi(path, payload, callback) {
var transport, opts;
if (respData.err) {
console.error('[Rollbar] Received error: ' + respData.message);
return callback(new Error('Api error: ' + (respData.message || 'Unknown error')));
} else {
if (SETTINGS.verbose) {
console.log('[Rollbar] Successful api response');
}
return callback(null, respData.result);
}
}
transport = SETTINGS.transport;
opts = transportOpts(path, 'POST');
function transportOpts(path, method) {
return {
host: SETTINGS.endpointOpts.host,
port: SETTINGS.port ||
(SETTINGS.protocol == 'http' ? 80 : (SETTINGS.protocol == 'https' ? 443 : undefined)),
path: SETTINGS.endpointOpts.path + path,
method: method
};
return makeApiRequest(transport, opts, payload, callback);
}

@@ -155,7 +137,46 @@

function buildPayload(data) {
var payload = {
var payload;
payload = {
access_token: exports.accessToken,
data: data
data: data
};
return payload;
}
/*
* Public API
*/
exports.init = function (accessToken, options) {
var opt, portCheck;
options = options || {};
exports.accessToken = accessToken;
exports.endpoint = options.endpoint || exports.endpoint;
for (opt in options) {
if (options.hasOwnProperty(opt)) {
SETTINGS[opt] = options[opt];
}
}
SETTINGS.endpointOpts = url.parse(exports.endpoint);
SETTINGS.protocol = SETTINGS.endpointOpts.protocol.split(':')[0];
SETTINGS.transport = {http: http, https: https}[SETTINGS.protocol];
SETTINGS.proxy = options.proxy;
portCheck = SETTINGS.endpointOpts.host.split(':');
if (portCheck.length > 1) {
SETTINGS.endpointOpts.host = portCheck[0];
SETTINGS.port = parseInt(portCheck[1], 10);
}
};
exports.postItem = function (item, callback) {
return postApi('item/', buildPayload(item), callback);
};

@@ -0,1 +1,6 @@

/*jslint devel: true, nomen: true, plusplus: true, regexp: true, indent: 2, maxlen: 100 */
"use strict";
var async = require('async');
var http = require('http');

@@ -10,4 +15,6 @@ var https = require('https');

exports.VERSION = packageJson.version;
var SETTINGS = {

@@ -21,5 +28,2 @@ accessToken: null,

branch: null, // git branch name
handler: 'inline', // 'nextTick' or 'setInterval' or 'inline'
handlerInterval: 3, // number of seconds to use with setInterval handler
batchSize: 10,
notifier: {

@@ -31,180 +35,164 @@ name: 'node_rollbar',

scrubFields: ['passwd', 'password', 'secret', 'confirm_password', 'password_confirmation'],
addRequestData: addRequestData
addRequestData: null // Can be set by the user or will default to addRequestData defined below
};
var apiClient;
var pendingItems = [];
var intervalHandler;
/*
* Public API
*/
exports.init = function(api, options) {
apiClient = api;
options = options || {};
SETTINGS.accessToken = api.accessToken;
for (var opt in options) {
SETTINGS[opt] = options[opt];
}
/** Internal **/
if (SETTINGS.handler == 'setInterval') {
intervalHandler = setInterval(postItems, SETTINGS.handlerInterval * 1000);
}
};
exports.shutdown = function(callback) {
exports.changeHandler('inline');
clearIntervalHandler();
postItems(callback);
};
function genUuid() {
var buf = new Buffer(16);
uuid.v4(null, buf);
return buf.toString('hex');
}
exports.changeHandler = function(newHandler) {
clearIntervalHandler();
SETTINGS.handler = newHandler;
if (newHandler == 'setInterval') {
intervalHandler = setInterval(postItems, SETTINGS.handlerInterval * 1000);
function buildBaseData(extra) {
var data, props;
extra = extra || {};
data = {
timestamp: Math.floor((new Date().getTime()) / 1000),
environment: extra.environment || SETTINGS.environment,
level: extra.level || 'error',
language: 'javascript',
framework: extra.framework || SETTINGS.framework,
uuid: genUuid(),
notifier: JSON.parse(JSON.stringify(SETTINGS.notifier))
};
if (SETTINGS.codeVersion) {
data.code_version = SETTINGS.codeVersion;
}
};
props = Object.getOwnPropertyNames(extra);
props.forEach(function (name) {
if (!data.hasOwnProperty(name)) {
data[name] = extra[name];
}
});
exports.handleError = function(err, req, callback) {
return exports.handleErrorWithPayloadData(err, {}, req, callback);
}
data.server = {};
data.server.host = SETTINGS.host;
exports.handleErrorWithPayloadData = function(err, payloadData, req, callback) {
// Allow the user to call with an optional request and callback
// e.g. handleErrorWithPayloadData(err, payloadData, req, callback)
// or handleErrorWithPayloadData(err, payloadData, callback)
// or handleErrorPayloadData(err, payloadData)
if (typeof req === 'function') {
callback = req;
req = null;
if (SETTINGS.branch) {
data.server.branch = SETTINGS.branch;
}
callback = callback || function(err) {};
if (!(err instanceof Error)) {
return callback(new Error('handleError was passed something other than an Error'));
if (SETTINGS.root) {
data.server.root = SETTINGS.root;
}
try {
return parser.parseException(err, function(e, errData) {
if (e) {
return callback(e);
} else {
var data = buildBaseData(payloadData);
data.body = {
trace: {
frames: errData.frames,
exception: {
class: errData['class'],
message: errData.message
}
}
};
if (req) {
SETTINGS.addRequestData(data, req);
}
data.server = buildServerData();
return addItem(data, callback);
return data;
}
function buildErrorData(baseData, err, callback) {
parser.parseException(err, function (e, errData) {
if (e) {
return callback(e);
}
baseData.body.trace = {
frames: errData.frames,
exception: {
class: errData['class'],
message: errData.message
}
});
} catch (exc) {
console.error('[Rollbar] error while parsing exception: ' + exc);
return callback(exc);
}
};
};
callback(null);
});
}
exports.reportMessage = function(message, level, req, callback) {
return exports.reportMessageWithPayloadData(message, {level: level}, req, callback);
};
function charFill(char, num) {
var a, x;
exports.reportMessageWithPayloadData = function(message, payloadData, req, callback) {
try {
var data = buildBaseData(payloadData);
data.body = {
message: {
body: message
}
};
if (req) {
SETTINGS.addRequestData(data, req);
}
data.server = buildServerData();
return addItem(data, callback);
} catch (exc) {
console.error('[Rollbar] error while reporting message: ' + exc);
return callback(exc);
a = [];
x = num;
while (x > 0) {
a[x] = '';
x -= 1;
}
};
return a.join(char);
}
/** Internal **/
function scrubRequestHeaders(headers, settings) {
var obj, k;
function postItems(callback) {
var items;
var numToRemove;
callback = callback || function(err, apiResp) {};
try {
var looper = function(err, apiResp) {
if (err) {
return callback(err);
} else if (pendingItems.length) {
numToRemove = Math.min(pendingItems.length, SETTINGS.batchSize);
items = pendingItems.splice(0, numToRemove);
return apiClient.postItems(items, looper);
obj = {};
settings = settings || SETTINGS;
for (k in headers) {
if (headers.hasOwnProperty(k)) {
if (settings.scrubHeaders.indexOf(k) === -1) {
obj[k] = headers[k];
} else {
return callback(null, apiResp);
obj[k] = charFill('*', headers[k].length);
}
};
return looper();
} catch (exc) {
console.error('[Rollbar] error while posting items: ' + exc);
return callback(exc);
}
}
};
return obj;
}
function addItem(item, callback) {
pendingItems.push(item);
if (SETTINGS.handler == 'nextTick') {
process.nextTick(function() {
postItems(callback);
});
} else if (SETTINGS.handler == 'inline') {
return postItems(callback);
} else {
if (callback && typeof callback === 'function') {
return callback(null);
function scrubRequestParams(params, settings) {
var k;
settings = settings || SETTINGS;
for (k in params) {
if (params.hasOwnProperty(k) && params[k] && settings.scrubFields.indexOf(k) >= 0) {
params[k] = charFill('*', params[k].length);
}
}
return params;
}
function buildBaseData(extra) {
extra = extra || {};
var data = {timestamp: Math.floor((new Date().getTime()) / 1000),
environment: extra.environment || SETTINGS.environment,
level: extra.level || 'error',
language: 'javascript',
framework: extra.framework || SETTINGS.framework,
uuid: genUuid(),
notifier: JSON.parse(JSON.stringify(SETTINGS.notifier))};
if (SETTINGS.codeVersion) {
data.code_version = SETTINGS.codeVersion;
function extractIp(req) {
var ip = req.ip;
if (!ip) {
if (req.headers) {
ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'];
}
if (!ip && req.connection && req.connection.remoteAddress) {
ip = req.connection.remoteAddress;
}
}
var props = Object.getOwnPropertyNames(extra);
props.forEach(function(name) {
if (!data.hasOwnProperty(name)) {
data[name] = extra[name];
return ip;
}
function buildRequestData(req) {
var headers, host, proto, reqUrl, parsedUrl, data, bodyParams, k;
headers = req.headers || {};
host = headers.host || '<no host>';
proto = req.protocol || (req.socket && req.socket.encrypted) ? 'https' : 'http';
reqUrl = proto + '://' + host + (req.url || '');
parsedUrl = url.parse(reqUrl, true);
data = {url: reqUrl,
GET: parsedUrl.query,
user_ip: extractIp(req),
headers: scrubRequestHeaders(headers),
method: req.method};
if (req.body) {
bodyParams = {};
if (typeof req.body === 'object') {
for (k in req.body) {
if (req.body.hasOwnProperty(k)) {
bodyParams[k] = req.body[k];
}
}
data[req.method] = scrubRequestParams(bodyParams);
} else {
data.body = req.body;
}
});
}
return data;

@@ -215,3 +203,5 @@ }

function addRequestData(data, req) {
var reqData = buildRequestData(req);
var reqData, userId;
reqData = buildRequestData(req);
if (reqData) {

@@ -226,4 +216,4 @@ data.request = reqData;

data.context = req.app._router.matchRequest(req).path;
} catch (e) {
//ignore
} catch (ignore) {
// ignore
}

@@ -243,3 +233,3 @@ }

} else if (req.user_id || req.userId) {
var userId = req.user_id || req.userId;
userId = req.user_id || req.userId;
if (typeof userId === 'function') {

@@ -253,107 +243,137 @@ userId = userId();

function buildServerData() {
var data = {
host: SETTINGS.host
};
function buildItemData(item, callback) {
var baseData, steps;
if (SETTINGS.branch) {
data.branch = SETTINGS.branch;
baseData = buildBaseData(item.payload);
// Add the message to baseData if there is one
function addMessageData(callback) {
baseData.body = {};
if (item.message !== undefined) {
baseData.body.message = {
body: item.message
};
}
callback(null);
}
if (SETTINGS.root) {
data.root = SETTINGS.root;
}
return data;
}
function buildRequestData(req) {
var headers = req.headers || {};
var host = headers.host || '<no host>';
var proto = req.protocol || (req.socket && req.socket.encrypted) ? 'https' : 'http';
var reqUrl = proto + '://' + host + (req.url || '');
var parsedUrl = url.parse(reqUrl, true);
var data = {url: reqUrl,
GET: parsedUrl.query,
user_ip: extractIp(req),
headers: scrubRequestHeaders(headers),
method: req.method};
if (req.body) {
var bodyParams = {};
if (typeof req.body === 'object') {
for (var k in req.body) {
bodyParams[k] = req.body[k];
}
data[req.method] = scrubRequestParams(bodyParams);
// Add the error trace information to baseData if there is one
function addTraceData(callback) {
if (item.error) {
buildErrorData(baseData, item.error, callback);
} else {
data.body = req.body;
callback(null);
}
}
return data;
}
function scrubRequestHeaders(headers, settings) {
var obj = {};
settings = settings || SETTINGS;
for (var k in headers) {
if (settings.scrubHeaders.indexOf(k) == -1) {
obj[k] = headers[k];
// Add the request information to baseData if there is one
function addRequestData(callback) {
var addReqDataFn = SETTINGS.addRequestData || addRequestData;
if (item.request) {
addReqDataFn(baseData, item.request);
}
else {
obj[k] = Array(headers[k].length + 1).join('*');
}
callback(null);
}
return obj;
}
function scrubRequestParams(params, settings) {
settings = settings || SETTINGS;
for (var k in params) {
if (params[k] && settings.scrubFields.indexOf(k) >= 0) {
params[k] = Array(params[k].length + 1).join('*');
steps = [
addMessageData,
addTraceData,
addRequestData
];
async.series(steps, function (err) {
if (err) {
callback(err);
}
}
callback(null, baseData);
});
return params;
}
function extractIp(req) {
var ip = req.ip;
if (!ip) {
if (req.headers) {
ip = req.headers['x-real-ip'] || req.headers['x-forwarded-for'];
function addItem(item, callback) {
buildItemData(item, function (err, data) {
if (err) {
return callback(err);
}
if (!ip && req.connection && req.connection.remoteAddress) {
ip = req.connection.remoteAddress;
}
}
return ip;
apiClient.postItem(data, function (err, resp) {
if (typeof callback === 'function') {
callback(err, data, resp);
}
});
});
}
function clearIntervalHandler() {
if (intervalHandler) {
clearInterval(intervalHandler);
}
}
/*
* Exports for testing
*/
function genUuid() {
var buf = new Buffer(16);
uuid.v4(null, buf);
return buf.toString('hex');
}
// Export for testing
exports._scrubRequestHeaders = function(headersToScrub, headers) {
exports._scrubRequestHeaders = function (headersToScrub, headers) {
return scrubRequestHeaders(headers, headersToScrub ? {scrubHeaders: headersToScrub} : undefined);
};
exports._scrubRequestParams = function(paramsToScrub, params) {
exports._scrubRequestParams = function (paramsToScrub, params) {
return scrubRequestParams(params, paramsToScrub ? {scrubFields: paramsToScrub} : undefined);
};
exports._extractIp = function(req) {
exports._extractIp = function (req) {
return extractIp(req);
};
/*
* Public API
*/
exports.init = function (api, options) {
var opt;
SETTINGS.accessToken = api.accessToken;
apiClient = api;
options = options || {};
for (opt in options) {
if (options.hasOwnProperty(opt)) {
SETTINGS[opt] = options[opt];
}
}
};
exports.handleError = function (err, req, callback) {
return exports.handleErrorWithPayloadData(err, {}, req, callback);
};
exports.handleErrorWithPayloadData = function (err, payloadData, req, callback) {
// Allow the user to call with an optional request and callback
// e.g. handleErrorWithPayloadData(err, payloadData, req, callback)
// or handleErrorWithPayloadData(err, payloadData, callback)
// or handleErrorPayloadData(err, payloadData)
if (typeof req === 'function') {
callback = req;
req = null;
}
if (!(err instanceof Error)) {
if (typeof callback === 'function') {
return callback(new Error('handleError was passed something other than an Error'));
}
}
addItem({error: err, payload: payloadData, request: req}, callback);
};
exports.reportMessage = function (message, level, req, callback) {
return exports.reportMessageWithPayloadData(message, {level: level}, req, callback);
};
exports.reportMessageWithPayloadData = function (message, payloadData, req, callback) {
addItem({message: message, payload: payloadData, request: req}, callback);
};

@@ -0,1 +1,6 @@

/*jslint devel: true, nomen: true, plusplus: true, regexp: true, indent: 2, maxlen: 100 */
"use strict";
var async = require('async');
var fs = require('fs');

@@ -6,3 +11,4 @@ var lru = require('lru-cache');

var linesOfContext = 3;
var tracePattern = /^\s*at (?:([^(]+(?: \[\w\s+\])?) )?\(?(.+?)(?::(\d+):(\d+)(?:, <js>:(\d+):(\d+))?)?\)?$/;
var tracePattern =
/^\s*at (?:([^(]+(?: \[\w\s+\])?) )?\(?(.+?)(?::(\d+):(\d+)(?:, <js>:(\d+):(\d+))?)?\)?$/;

@@ -12,37 +18,3 @@ var jadeTracePattern = /^\s*at .+ \(.+ (at[^)]+\))\)$/;

exports.parseException = function(exc, callback) {
var multipleErrs = getMultipleErrors(exc.errors);
return exports.parseStack(exc.stack, function(err, stack) {
if (err) {
console.error('could not parse exception, err: ' + err);
return callback(err);
} else {
var message = exc.message || '<no message>';
var ret = {
class: exc.name,
message: message,
frames: stack
};
if (multipleErrs && multipleErrs.length) {
var firstErr = multipleErrs[0];
ret = {
class: exc.name,
message: firstErr.message,
frames: stack
};
}
var jadeMatch = message.match(jadeFramePattern);
if (jadeMatch) {
jadeData = parseJadeDebugFrame(message);
ret.message = jadeData.message;
ret.frames.push(jadeData.frame);
}
return callback(null, ret);
}
});
};
var cache = lru({max: 100});

@@ -54,107 +26,12 @@ var pendingReads = {};

exports.parseStack = function(stack, callback) {
// grab all lines except the first
var lines = (stack || '').split('\n').slice(1);
var frames = [];
var curLine;
var looper = function(err) {
if (err) {
console.error('error while parsing the stack trace, err: ' + err);
return callback(err);
} else if (lines.length) {
curLine = lines.shift();
/*
* Internal
*/
var matched = curLine.match(jadeTracePattern);
if (matched) {
curLine = matched[1];
}
matched = curLine.match(tracePattern);
if (!matched) {
return looper(null);
}
var data = matched.slice(1);
var frame = {
method: data[0] || '<unknown>',
filename: data[1],
lineno: ~~data[2],
colno: ~~data[3]
};
// For coffeescript, lineno and colno refer to the .coffee positions
// The .js lineno and colno will be stored in compiled_*
if (data[4]) {
frame.compiled_lineno = ~~data[4];
}
if (data[5]) {
frame.compiled_colno = ~~data[5];
}
function getMultipleErrors(errors) {
var errArray, key;
var pendingCallback = function(err) {
// internal Node files are not full path names. Ignore them.
if (frame.filename[0] === '/' || frame.filename[0] === '.') {
// There was an error reading the file, just push the frame and
// move on.
if (err) {
frames.push(frame);
} else {
// check if it has been read in first
var cachedFileLines = cache.get(frame.filename);
if (cachedFileLines) {
extractContextLines(frame, cachedFileLines);
frames.push(frame);
} else {
// If it has not been read yet, check to see if the file is pending
// any reads from other callbacks. If so, queue up pendingCallback to
// be called once the read is complete. If not, read the file async.
if (pendingReads[frame.filename]) {
pendingReads[frame.filename].push(pendingCallback);
return;
}
pendingReads[frame.filename] = pendingReads[frame.filename] || [];
return fs.readFile(frame.filename, 'utf8', function(err2, fileData) {
try {
if (err2) {
console.error('could not read in file ' + frame.filename + ' for context');
return pendingCallback(err2);
} else {
var fileLines = fileData.split('\n');
cache.set(frame.filename, fileLines);
extractContextLines(frame, fileLines);
frames.push(frame);
return looper(null);
}
} finally {
var pendingReadCallbacks = pendingReads[frame.filename];
// Check for any pending reads and call their callbacks
if (pendingReadCallbacks) {
var cb;
while (cb = pendingReadCallbacks.shift()) {
cb(err2);
}
delete pendingReads[frame.filename];
}
}
});
}
}
} else {
frames.push(frame);
}
return looper(null);
};
return pendingCallback();
} else {
frames.reverse();
return callback(null, frames);
}
};
return looper(null);
};
var getMultipleErrors = function(errors) {
if (errors == null || errors == undefined) {
if (errors === null || errors === undefined) {
return null;

@@ -171,5 +48,5 @@ }

var errArray = [];
errArray = [];
for (var key in errors) {
for (key in errors) {
if (errors.hasOwnProperty(key)) {

@@ -182,18 +59,19 @@ errArray.push(errors[key]);

var parseJadeDebugFrame = function(body) {
function parseJadeDebugFrame(body) {
var lines, lineNumSep, filename, lineno, numLines, msg, i,
contextLine, preContext, postContext, line, jadeMatch;
// Given a Jade exception body, return a frame object
var lines = body.split('\n');
var lineNumSep = lines[0].indexOf(':');
var filename = lines[0].slice(0, lineNumSep);
var lineno = parseInt(lines[0].slice(lineNumSep + 1));
var numLines = lines.length;
var msg = lines[numLines - 1];
lines = body.split('\n');
lineNumSep = lines[0].indexOf(':');
filename = lines[0].slice(0, lineNumSep);
lineno = parseInt(lines[0].slice(lineNumSep + 1), 10);
numLines = lines.length;
msg = lines[numLines - 1];
lines = lines.slice(1, numLines - 1);
var i;
var contextLine;
var preContext = [];
var postContext = [];
var line;
preContext = [];
postContext = [];
for (i = 0; i < numLines - 2; ++i) {

@@ -222,15 +100,19 @@ line = lines[i];

return {frame: {method: '<jade>',
filename: filename,
lineno: lineno,
code: contextLine,
context: {
pre: preContext,
post: postContext
}},
message: msg};
return {
frame: {
method: '<jade>',
filename: filename,
lineno: lineno,
code: contextLine,
context: {
pre: preContext,
post: postContext
}
},
message: msg
};
}
};
var extractContextLines = function(frame, fileLines) {
function extractContextLines(frame, fileLines) {
frame.code = fileLines[frame.lineno - 1];

@@ -241,2 +123,194 @@ frame.context = {

};
}
function parseFrameLine(line, callback) {
var matched, curLine, data, frame;
curLine = line;
matched = curLine.match(jadeTracePattern);
if (matched) {
curLine = matched[1];
}
matched = curLine.match(tracePattern);
if (!matched) {
return callback(null, null);
}
data = matched.slice(1);
frame = {
method: data[0] || '<unknown>',
filename: data[1],
lineno: Math.floor(data[2]),
colno: Math.floor(data[3])
};
// For coffeescript, lineno and colno refer to the .coffee positions
// The .js lineno and colno will be stored in compiled_*
if (data[4]) {
frame.compiled_lineno = Math.floor(data[4]);
}
if (data[5]) {
frame.compiled_colno = Math.floor(data[5]);
}
callback(null, frame);
}
function shouldReadFrameFile(frameFilename, callback) {
var isValidFilename, isCached, isPending;
isValidFilename = frameFilename[0] === '/' || frameFilename[0] === '.';
isCached = !!cache.get(frameFilename);
isPending = !!pendingReads[frameFilename];
callback(isValidFilename && !isCached && !isPending);
}
function readFileLines(filename, callback) {
try {
fs.readFile(filename, function (err, fileData) {
var fileLines;
if (err) {
return callback(err);
}
fileLines = fileData.toString('utf8').split('\n');
return callback(null, fileLines);
});
} catch (e) {
console.log(e);
}
}
/* Older versions of node do not have fs.exists so we implement our own */
function checkFileExists(filename, callback) {
if (fs.exists !== undefined) {
fs.exists(filename, callback);
} else {
fs.stat(filename, function (err) {
callback(!err);
});
}
}
function gatherContexts(frames, callback) {
var frameFilenames = [];
frames.forEach(function (frame) {
if (frameFilenames.indexOf(frame.filename) === -1) {
frameFilenames.push(frame.filename);
}
});
async.filter(frameFilenames, shouldReadFrameFile, function (results) {
var tempFileCache;
tempFileCache = {};
function gatherFileData(filename, callback) {
readFileLines(filename, function (err, lines) {
if (err) {
return callback(err);
}
// Cache this in a temp cache as well as the LRU cache so that
// we know we will have all of the necessary file contents for
// each filename in tempFileCache.
tempFileCache[filename] = lines;
cache.set(filename, lines);
return callback(null);
});
}
function gatherContextLines(frame, callback) {
var lines = tempFileCache[frame.filename];
if (lines) {
extractContextLines(frame, lines);
}
callback(null);
}
async.filter(results, checkFileExists, function (filenames) {
async.each(filenames, gatherFileData, function (err) {
if (err) {
return callback(err);
}
async.eachSeries(frames, gatherContextLines, function (err) {
if (err) {
return callback(err);
}
callback(null, frames);
});
});
});
});
}
/*
* Public API
*/
exports.parseException = function (exc, callback) {
var multipleErrs = getMultipleErrors(exc.errors);
return exports.parseStack(exc.stack, function (err, stack) {
var message, ret, firstErr, jadeMatch, jadeData;
if (err) {
console.error('could not parse exception, err: ' + err);
return callback(err);
}
message = exc.message || '<no message>';
ret = {
class: exc.name,
message: message,
frames: stack
};
if (multipleErrs && multipleErrs.length) {
firstErr = multipleErrs[0];
ret = {
class: exc.name,
message: firstErr.message,
frames: stack
};
}
jadeMatch = message.match(jadeFramePattern);
if (jadeMatch) {
jadeData = parseJadeDebugFrame(message);
ret.message = jadeData.message;
ret.frames.push(jadeData.frame);
}
return callback(null, ret);
});
};
exports.parseStack = function (stack, callback) {
var lines;
// grab all lines except the first
lines = (stack || '').split('\n').slice(1);
// Parse out all of the frame and filename info
async.map(lines, parseFrameLine, function (err, frames) {
if (err) {
return callback(err);
}
frames.reverse();
async.filter(frames, function (frame, callback) { callback(!!frame); }, function (results) {
gatherContexts(results, callback);
});
});
};

@@ -13,3 +13,3 @@ {

],
"version": "0.4.5",
"version": "0.5.0",
"repository": "git://github.com/rollbar/node_rollbar.git",

@@ -28,3 +28,4 @@ "author": "Rollbar, Inc. <support@rollbar.com>",

"lru-cache": "~2.2.1",
"json-stringify-safe": "~5.0.0"
"json-stringify-safe": "~5.0.0",
"async": "~0.9.0"
},

@@ -31,0 +32,0 @@ "devDependencies": {

@@ -66,12 +66,9 @@ # Rollbar notifier for Node.js [![Build Status](https://secure.travis-ci.org/rollbar/node_rollbar.png?branch=master)](https://travis-ci.org/rollbar/node_rollbar)

// Queue up and report messages/exceptions to rollbar every 5 seconds
rollbar.init("POST_SERVER_ITEM_ACCESS_TOKEN", {handler: "setInterval", handlerInterval: 5});
rollbar.init("POST_SERVER_ITEM_ACCESS_TOKEN", {
environment: "staging",
endpoint: "https://api.rollbar.com/api/1/"
});
```
When you are finished using rollbar, clean up any remaining items in the queue using the shutdown function:
```js
rollbar.shutdown();
```
## Usage

@@ -145,3 +142,3 @@

// reports a string message at the level "info", along with a request and callback
// reports a string message at the level "warning", along with a request and callback
// only the first param is required

@@ -197,7 +194,2 @@ // valid severity levels: "critical", "error", "warning", "info", "debug"

<dl>
<dt>batchSize</dt>
<dd>The max number of items sent to rollbar at a time.
Default: `10`
</dd>

@@ -228,20 +220,2 @@ <dt>branch</dt>

<dt>handler</dt>
<dd>The method that the notifier will use to report exceptions.
Supported values:
- setInterval -- all items that are queued up are sent to rollbar in batches in a setInterval callback
- NOTE: using this mode will mean that items are queued internally before being sent. For applications that send a very large amount of items, it is possible to use up too much memory and crash the node process. If this starts to happen, try lowering the handlerInterval setting or switch to a different handler, e.g. 'nextTick'.
- nextTick -- all items that are queued up are sent to rollbar in a process.nextTick callback
- inline -- items are sent to rollbar as they are queued up, one at-a-time
Default: `inline`
</dd>
<dt>handlerInterval</dt>
<dd>If the handler is `setInterval`, this is the number of seconds between batch posts of items to rollbar.
Default: `3`
</dd>
<dt>host</dt>

@@ -283,8 +257,3 @@ <dd>The hostname of the server the node.js process is running on.

## Performance
The default configuration uses the `inline` handler which will cause errors to be reported to Rollbar at the time they occur. This works well for small applications but can quickly become a problem for high-throughput apps. For better performance, the `setInterval` handler is recommended since it queues up errors before sending them.
When using a handler besides `inline`, you should make sure to call `rollbar.shutdown()` in order to flush all errors before exiting.
## Help / Support

@@ -291,0 +260,0 @@

@@ -0,1 +1,5 @@

/*jslint devel: true, nomen: true, indent: 2, maxlen: 100 */
"use strict";
var api = require('./lib/api');

@@ -24,3 +28,3 @@ var notifier = require('./lib/notifier');

*
* app.get('/', function(req, res) {
* app.get('/', function (req, res) {
* ...

@@ -39,3 +43,2 @@ * });

* rollbar.reportMessage('Hello world', 'debug');
* rollbar.shutdown(); // Processes any remaining items and stops any running timers
*

@@ -49,3 +52,3 @@ * Uncaught exceptions -

*
* app.get('/', function(req, res) {
* app.get('/', function (req, res) {
* try {

@@ -60,3 +63,3 @@ * ...

*
* app.get('/', function(req, res) {
* app.get('/', function (req, res) {
* req.userId = 12345; // or req.user_id

@@ -67,3 +70,3 @@ * rollbar.reportMessage('Interesting event', req);

exports.init = function(accessToken, options) {
exports.init = function (accessToken, options) {
/*

@@ -78,11 +81,5 @@ * Initialize the rollbar library.

* environment - Default: 'unspecified' - the environment the code is running in. e.g. 'staging'
* handler - Default: 'inline' - the method that the notifier will use to report exceptions,
* choices:
* setInterval: all items that are queued up are sent to rollbar in batches in a setInterval callback
* nextTick: all items that are queued up are sent to rollbar in a process.nextTick callback
* inline: items are sent to rollbar as they are queued up, one at-a-time
* handlerInterval - Default: 3 - if handler=setInterval, this is the number of seconds between batch posts of items to rollbar
* batchSize - Default: 10 - the max number of items sent to rollbar at a time
* endpoint - Default: 'https://api.rollbar.com/api/1/' - the url to send items to
* root - the path to your code, (not including any trailing slash) which will be used to link source files on rollbar
* root - the path to your code, (not including any trailing slash) which will be used to link
* source files on rollbar
* branch - the branch in your version control system for this code

@@ -119,4 +116,4 @@ * codeVersion - the version or revision of your code

* request - optional request object to send along with the message
* callback - optional callback that will be invoked depending on the handler method used.
* Should take a single parameter to denote if there was an error.
* callback - optional callback that will be invoked once the message was reported.
* callback should take 3 parameters: callback(err, payloadData, response)
*

@@ -127,4 +124,9 @@ * Examples:

*
* rollbar.reportMessage("Something suspicious...", "debug", null, function(err) {
* // message was queued/sent to rollbar
* rollbar.reportMessage("Something suspicious...", "debug", null, function (err, payloadData) {
* if (err) {
* console.error('Error sending to Rollbar:', err);
* } else {
* console.log('Reported message to rollbar:');
* console.log(payloadData);
* }
* });

@@ -146,4 +148,4 @@ *

* request - optional request object to send along with the message
* callback - optional callback that will be invoked depending on the handler method used.
* Should take a single parameter to denote if there was an error.
* callback - optional callback that will be invoked once the message has been sent to Rollbar.
* callback should take 3 parameters: callback(err, payloadData, response)
*

@@ -153,3 +155,3 @@ * Examples:

* rollbar.reportMessageWithPayloadData("Memcache miss",
* {level: "debug", fingerprint: "Memcache-miss"}, null, function(err) {
* {level: "debug", fingerprint: "Memcache-miss"}, null, function (err) {
* // message was queued/sent to rollbar

@@ -169,4 +171,4 @@ * });

* request - an optional request object to send along with the error
* callback - optional callback that will be invoked depending on the handler method used.
* Should take a single parameter to denote if there was an error.
* callback - optional callback that will be invoked after the error was sent to Rollbar.
* callback should take 3 parameters: callback(err, payloadData, response)
*

@@ -177,3 +179,3 @@ * Examples:

*
* rollbar.handleError(new Error("it's just foobar..."), function(err) {
* rollbar.handleError(new Error("it's just foobar..."), function (err) {
* // error was queued/sent to rollbar

@@ -198,4 +200,4 @@ * });

* request - optional request object to send along with the message
* callback - optional callback that will be invoked depending on the handler method used.
* Should take a single parameter to denote if there was an error.
* callback - optional callback that will be invoked after the error was sent to Rollbar.
* callback should take 3 parameters: callback(err, payloadData, response)
*

@@ -207,3 +209,3 @@ * Examples:

* {custom: {someKey: "its value, otherKey: ["other", "value"]}});
* rollbar.handleError(new Error("error message"), {}, req, function(err) {
* rollbar.handleError(new Error("error message"), {}, req, function (err) {
* // error was queued/sent to rollbar

@@ -215,8 +217,3 @@ * });

exports.shutdown = function(callback) {
notifier.shutdown(callback);
};
exports.errorHandler = function(accessToken, options) {
exports.errorHandler = function (accessToken, options) {
/*

@@ -230,4 +227,4 @@ * A middleware handler for connect and express.js apps. For a list

exports.init(accessToken, options);
return function(err, req, res, next) {
var cb = function(rollbarErr) {
return function (err, req, res, next) {
var cb = function (rollbarErr) {
if (rollbarErr) {

@@ -238,9 +235,12 @@ console.error('[Rollbar] Error reporting to rollbar, ignoring: ' + rollbarErr);

};
if (!err) {
return next(err, req, res);
} else if (err instanceof Error) {
}
if (err instanceof Error) {
return notifier.handleError(err, req, cb);
} else {
return notifier.reportMessage('Error: ' + err, 'error', req, cb);
}
return notifier.reportMessage('Error: ' + err, 'error', req, cb);
};

@@ -250,3 +250,3 @@ };

exports.handleUncaughtExceptions = function(accessToken, options) {
exports.handleUncaughtExceptions = function (accessToken, options) {
/*

@@ -266,3 +266,4 @@ * Registers a handler for the process.uncaughtException event.

options = options || {};
var exitOnUncaught = options.exitOnUncaughtException === undefined ? false : !!options.exitOnUncaughtException;
var exitOnUncaught = options.exitOnUncaughtException === undefined ?
false : !!options.exitOnUncaughtException;
delete options.exitOnUncaughtException;

@@ -273,3 +274,3 @@

if (initialized) {
process.on('uncaughtException', function(err) {
process.on('uncaughtException', function (err) {
console.error('[Rollbar] Handling uncaught exception.');

@@ -282,3 +283,3 @@ console.error(err);

notifier.handleError(err, function(err) {
notifier.handleError(err, function (err) {
if (err) {

@@ -290,5 +291,3 @@ console.error('[Rollbar] Encountered an error while handling an uncaught exception.');

if (exitOnUncaught) {
exports.shutdown(function(e) {
process.exit(1);
});
process.exit(1);
}

@@ -295,0 +294,0 @@ });

@@ -0,1 +1,5 @@

/*jslint devel: true, nomen: true, plusplus: true, regexp: true, indent: 2, maxlen: 100 */
"use strict";
var assert = require('assert');

@@ -9,9 +13,11 @@ var vows = require('vows');

rollbar.init(ACCESS_TOKEN, {environment: 'playground', handler: 'inline'});
rollbar.init(ACCESS_TOKEN, {environment: 'playground'});
var suite = vows.describe('json').addBatch({
'should handle circular object references': {
topic: function() {
var c = {};
var circularObject = {c: c};
topic: function () {
var c, circularObject;
c = {};
circularObject = {c: c};
c.d = circularObject;

@@ -21,34 +27,32 @@

},
'verify there were no errors reporting the circular object': function(err, resp) {
'verify there were no errors reporting the circular object': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
}
},
'should handle sibling keys that refer to the same object': {
topic: function() {
var obj = {foo: 'bar'};
var testObj = {a: obj, b: obj};
topic: function () {
var obj, testObj;
obj = {foo: 'bar'};
testObj = {a: obj, b: obj};
notifier.reportMessageWithPayloadData('test', testObj, null, this.callback);
},
'verify there were no errors reporting the object': function(err, resp) {
'verify there were no errors reporting the object': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
}
},
'should be able to send unicode characters properly': {
topic: function() {
var obj = {foo: '☀ ☁ ☂ ☃ ☄ ★ ☆ ☇ ☈ ☉ ☊ ☋ ☌ ☍ ☎ ☏ ☐ ☑ ☒ ☓ ☚ ☛ ☜'};
var testObj = {a: obj, b: obj};
topic: function () {
var obj, testObj;
obj = {foo: '☀ ☁ ☂ ☃ ☄ ★ ☆ ☇ ☈ ☉ ☊ ☋ ☌ ☍ ☎ ☏ ☐ ☑ ☒ ☓ ☚ ☛ ☜'};
testObj = {a: obj, b: obj};
notifier.reportMessageWithPayloadData('test', testObj, null, this.callback);
},
'verify there were no errors reporting the object': function(err, resp) {
'verify there were no errors reporting the object': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
}
}
}).export(module, {error: false});

@@ -0,1 +1,5 @@

/*jslint devel: true, node: true, nomen: true, plusplus: true, indent: 2, maxlen: 100 */
"use strict";
var assert = require('assert');

@@ -9,8 +13,48 @@ var vows = require('vows');

rollbar.init(ACCESS_TOKEN, {environment: 'playground', handler: 'inline'});
rollbar.init(ACCESS_TOKEN, {environment: 'playground'});
var b = {
'handleError with a normal error': {
topic: function () {
var test = function () {
var x = thisVariableIsNotDefined;
};
try {
test();
} catch (e) {
notifier.handleError(e, this.callback);
}
},
'verify no error is returned': function (err) {
assert.isNull(err);
}
}
};
var b2 = {
'handleError with a normal error 2': {
topic: function () {
var test = function () {
var x = thisVariableIsNotDefined;
};
try {
test();
} catch (e) {
notifier.handleError(e, this.callback);
}
},
'verify no error is returned': function (err) {
assert.isNull(err);
}
}
};
var suite = vows.describe('notifier').addBatch(b).addBatch(b2).export(module, {error: false});
/*
var suite = vows.describe('notifier').addBatch({
'handleError with a normal error': {
topic: function() {
var test = function() {
topic: function () {
var test = function () {
var x = thisVariableIsNotDefined;

@@ -24,12 +68,9 @@ };

},
'verify no error is returned': function(err, resp) {
'verify no error is returned': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
// TODO - verify contents of payload
}
},
'handleErrorWithPayloadData with a normal error': {
topic: function() {
var test = function() {
topic: function () {
var test = function () {
var x = thisVariableIsNotDefined;

@@ -43,11 +84,8 @@ };

},
'verify no error is returned': function(err, resp) {
'verify no error is returned': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
// TODO - verify contents of payload
}
},
'handleError with an Error that has a missing stack': {
topic: function() {
topic: function () {
var e = new Error('test error');

@@ -57,11 +95,9 @@ e.stack = undefined;

},
'verify no error is returned': function(err, resp) {
'verify no error is returned': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
}
},
'handleError with an infinite recursion stack limit reached Error': {
topic: function() {
var genError = function(counter) {
topic: function () {
var genError = function (counter) {
return genError(++counter);

@@ -75,13 +111,11 @@ };

},
'verify the error was sent': function(err, resp) {
'verify the error was sent': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
}
},
'reportMessage with invalid request object': {
topic: function() {
topic: function () {
notifier.reportMessage('test', 'debug', 1, this.callback);
},
'verify no error is returned': function(err) {
'verify no error is returned': function (err) {
assert.isNull(err);

@@ -91,17 +125,22 @@ }

'reportMessageWithPayloadData with valid level and fingerprint': {
topic: function() {
notifier.reportMessageWithPayloadData('test', {level: 'debug', fingerprint: 'custom-fingerprint'}, null, this.callback);
topic: function () {
notifier.reportMessageWithPayloadData('test', {
level: 'debug',
fingerprint: 'custom-fingerprint'
}, null, this.callback);
},
'verify no error is returned': function(err, resp) {
'verify no error is returned': function (err) {
assert.isNull(err);
assert.isObject(resp);
assert.include(resp, 'ids');
}
},
'scrubRequestHeaders scrubs "cookie" header': {
topic: function() {
topic: function () {
var callback = this.callback;
return callback(null, notifier._scrubRequestHeaders(['cookie'], {cookie: 'remove=me', otherHeader: 'test'}));
return callback(null,
notifier._scrubRequestHeaders(['cookie'], {
cookie: 'remove=me',
otherHeader: 'test'
}));
},
'verify cookie is scrubbed': function(err, headers) {
'verify cookie is scrubbed': function (err, headers) {
assert.equal(headers.cookie, '*********');

@@ -112,11 +151,12 @@ assert.equal(headers.otherHeader, 'test');

'scrubRequestHeaders scrubs multiple headers': {
topic: function() {
topic: function () {
var callback = this.callback;
return callback(null,
notifier._scrubRequestHeaders(['cookie', 'password'],
{cookie: 'remove=me',
password: 'secret',
otherHeader: 'test'}));
notifier._scrubRequestHeaders(['cookie', 'password'], {
cookie: 'remove=me',
password: 'secret',
otherHeader: 'test'
}));
},
'verify all scrub fields are scrubbed': function(err, headers) {
'verify all scrub fields are scrubbed': function (err, headers) {
assert.equal(headers.cookie, '*********');

@@ -128,11 +168,12 @@ assert.equal(headers.password, '******');

'scrubRequestParams scrubs "password" and "confirm_password" fields by default': {
topic: function() {
topic: function () {
var callback = this.callback;
return callback(null,
notifier._scrubRequestParams(undefined,
{password: 'secret',
confirm_password: 'secret',
otherParam: 'test'}));
notifier._scrubRequestParams(undefined, {
password: 'secret',
confirm_password: 'secret',
otherParam: 'test'
}));
},
'verify fields are scrubbed': function(err, params) {
'verify fields are scrubbed': function (err, params) {
assert.equal(params.password, '******');

@@ -144,12 +185,13 @@ assert.equal(params.confirm_password, '******');

'scrubRequestParams ignores null or undefined values': {
topic: function() {
topic: function () {
var callback = this.callback;
return callback(null,
notifier._scrubRequestParams(['nullValue', 'undefinedValue', 'emptyValue'],
{nullValue: null,
undefinedValue: undefined,
emptyValue: '',
goodValue: 'goodValue'}));
notifier._scrubRequestParams(['nullValue', 'undefinedValue', 'emptyValue'], {
nullValue: null,
undefinedValue: undefined,
emptyValue: '',
goodValue: 'goodValue'
}));
},
'verify fields are scrubbed': function(err, params) {
'verify fields are scrubbed': function (err, params) {
assert.equal(params.nullValue, null);

@@ -162,3 +204,3 @@ assert.equal(params.undefinedValue, undefined);

'extractIp returns req.ip first': {
topic: function() {
topic: function () {
var dummyReq = {

@@ -176,3 +218,3 @@ ip: 'req.ip IP address',

},
'verify the IP': function(ip) {
'verify the IP': function (ip) {
assert.equal(ip, 'req.ip IP address');

@@ -182,3 +224,3 @@ }

'extractIp returns req.header["x-real-ip"] if req.ip doesn\'t exist': {
topic: function() {
topic: function () {
var dummyReq = {

@@ -195,3 +237,3 @@ headers: {

},
'verify the IP': function(ip) {
'verify the IP': function (ip) {
assert.equal(ip, 'X-Real-Ip IP address');

@@ -201,3 +243,3 @@ }

'extractIp returns req.header["x-forwarded-for"] if x-real-ip doesn\'t exist': {
topic: function() {
topic: function () {
var dummyReq = {

@@ -213,3 +255,3 @@ headers: {

},
'verify the IP': function(ip) {
'verify the IP': function (ip) {
assert.equal(ip, 'X-Forwarded-For IP address');

@@ -219,3 +261,3 @@ }

'extractIp returns req.connection.remoteAddress x-forwarded-for doesn\'t exist': {
topic: function() {
topic: function () {
var dummyReq = {

@@ -230,3 +272,3 @@ headers: {

},
'verify the IP': function(ip) {
'verify the IP': function (ip) {
assert.equal(ip, 'Connection IP address');

@@ -236,7 +278,7 @@ }

'extractIp doesn\'t crash if req.connection/req.headers doesn\'t exist': {
topic: function() {
topic: function () {
var dummyReq = {};
return this.callback(notifier._extractIp(dummyReq));
},
'verify the IP': function(ip) {
'verify the IP': function (ip) {
assert.equal(ip, undefined);

@@ -246,1 +288,2 @@ }

}).export(module, {error: false});
*/

@@ -0,1 +1,5 @@

/*jslint devel: true, nomen: true, plusplus: true, regexp: true, unparam: true, indent: 2, maxlen: 100 */
"use strict";
var assert = require('assert');

@@ -8,12 +12,101 @@ var fs = require('fs');

/*
* Internal
*/
function readJadeFixture(filename, includeJadeFilename, jadeLocals, callback) {
fs.readFile(filename, function (err, data) {
var opts, jadeFn;
opts = {};
if (err) {
return callback(err);
}
if (includeJadeFilename) {
opts.filename = __dirname + '/../fixture/jadeerr.jade';
}
jadeFn = jade.compile(data, opts);
try {
jadeFn(jadeLocals);
return callback(new Error('expected this to break'));
} catch (e) {
return callback(null, e);
}
});
}
function parseAndVerifyException(jadeDebug) {
return {
topic: function (err, exc) {
parser.parseException(exc, this.callback);
},
'verify stack frames': function (err, parsedObj) {
var frames, i, numFrames, cur, jadeDebugFound;
assert.isNull(err);
assert.isObject(parsedObj);
frames = parsedObj.frames;
assert.isArray(frames);
assert.equal(parsedObj.class, 'ReferenceError');
assert.equal(parsedObj.message, 'foo is not defined');
numFrames = frames.length;
assert.isTrue(numFrames >= 1);
jadeDebugFound = false;
for (i = 0; i < numFrames; ++i) {
cur = frames[i];
assert.include(cur, 'method');
assert.include(cur, 'filename');
assert.include(cur, 'lineno');
if (cur.method !== '<jade>') {
assert.include(cur, 'colno');
assert.isNumber(cur.colno);
} else {
jadeDebugFound = true;
}
assert.isNumber(cur.lineno);
assert.isString(cur.method);
assert.isString(cur.filename);
}
if (jadeDebug) {
if (!jadeDebugFound) {
assert.isFalse('jade debug not found');
}
} else {
if (jadeDebugFound) {
assert.isFalse('jade debug found but should not be');
}
}
}
};
}
/*
* Tests
*/
var suite = vows.describe('parser').addBatch({
'Read in a jade template with an error, compile with no filename': {
topic: function() {
topic: function () {
readJadeFixture(__dirname + '/../fixture/jadeerr.jade',
false, {breakme: true}, this.callback);
},
'parse the exception with parseException': parseAndVerifyException(false),
'parse the exception with parseException': parseAndVerifyException(false)
},
'Read in a jade template with an error, compile with a filename': {
topic: function() {
topic: function () {
readJadeFixture(__dirname + '/../fixture/jadeerr.jade',

@@ -25,3 +118,3 @@ true, {breakme: true}, this.callback);

'Create a new Error in this file': {
topic: function() {
topic: function () {
// NOTE: Don't change this next line of code since we verify the context line

@@ -32,6 +125,6 @@ // in the parsed exception below.

'parse it with parseException': {
topic: function(err, exc) {
topic: function (err, exc) {
return parser.parseException(exc, this.callback);
},
'verify the filename': function(err, parsedObj) {
'verify the filename': function (err, parsedObj) {
assert.isNull(err);

@@ -45,3 +138,3 @@ assert.isObject(parsedObj);

},
'verify the context line': function(err, parsedObj) {
'verify the context line': function (err, parsedObj) {
var lastFrame = parsedObj.frames[parsedObj.frames.length - 1];

@@ -54,3 +147,3 @@ assert.isString(lastFrame.code);

'An error reading a file': {
topic: function(err) {
topic: function (err) {
var exc = new Error();

@@ -61,3 +154,3 @@ exc.stack = "Error\n at REPLServer.self.eval (/tmp/file-does-not-exist.js:1:2)" +

},
'it returns frames without context': function(err, parsedObj) {
'it returns frames without context': function (err, parsedObj) {
assert.equal(parsedObj.frames.length, 2);

@@ -73,8 +166,9 @@ assert.equal(parsedObj.frames[0].filename, "/tmp/other-file-does-not-exist.js");

'A coffee script stacktrace': {
topic: function(err) {
topic: function (err) {
var exc = new Error();
exc.stack = "TypeError: Cannot read property 'foo' of undefined\n at example (/tmp/example.coffee:2:3, <js>:5:20)";
exc.stack = "TypeError: Cannot read property 'foo' of undefined\n" +
" at example (/tmp/example.coffee:2:3, <js>:5:20)";
return parser.parseException(exc, this.callback);
},
'it parses correctly': function(err, parsedObj) {
'it parses correctly': function (err, parsedObj) {
assert.equal(parsedObj.frames[0].filename, "/tmp/example.coffee");

@@ -89,10 +183,12 @@ assert.equal(parsedObj.frames[0].lineno, 2);

'Simple': {
topic: function(err) {
var exc1 = new Error('First error');
var exc2 = new Error('Second error');
var containerExc = new Error();
topic: function (err) {
var exc1, exc2, containerExc;
exc1 = new Error('First error');
exc2 = new Error('Second error');
containerExc = new Error();
containerExc.errors = [exc1, exc2];
return parser.parseException(containerExc, this.callback);
},
'it uses the first error': function(err, parsedObj) {
'it uses the first error': function (err, parsedObj) {
assert.equal(parsedObj.message, 'First error');

@@ -102,10 +198,12 @@ }

'exception.errors is an object': {
topic: function(err) {
var exc1 = new Error('First error');
var exc2 = new Error('Second error');
var containerExc = new Error();
topic: function (err) {
var exc1, exc2, containerExc;
exc1 = new Error('First error');
exc2 = new Error('Second error');
containerExc = new Error();
containerExc.errors = {first: exc1, second: exc2};
return parser.parseException(containerExc, this.callback);
},
'one of the errors was used': function(err, parsedObj) {
'one of the errors was used': function (err, parsedObj) {
assert.isTrue(parsedObj.message === 'First error' || parsedObj.message === 'Second error');

@@ -115,3 +213,3 @@ }

'exception.errors is null': {
topic: function(err) {
topic: function (err) {
var containerExc = new Error('Container error');

@@ -121,3 +219,3 @@ containerExc.errors = null;

},
'the container error was used': function(err, parsedObj) {
'the container error was used': function (err, parsedObj) {
assert.equal(parsedObj.message, 'Container error');

@@ -127,3 +225,3 @@ }

'exception.errors is an empty array': {
topic: function(err) {
topic: function (err) {
var containerExc = new Error('Container error');

@@ -133,3 +231,3 @@ containerExc.errors = [];

},
'the container error was used': function(err, parsedObj) {
'the container error was used': function (err, parsedObj) {
assert.equal(parsedObj.message, 'Container error');

@@ -140,73 +238,1 @@ }

}).export(module, {error: false});
function readJadeFixture(filename, includeJadeFilename, jadeLocals, callback) {
fs.readFile(filename, function(err, data) {
if (err) {
return callback(err);
} else {
var opts = {};
if (includeJadeFilename) {
opts.filename = __dirname + '/../fixture/jadeerr.jade';
}
var jadeFn = jade.compile(data, opts);
try {
jadeFn(jadeLocals);
return callback(new Error('expected this to break'));
} catch (e) {
return callback(null, e);
}
}
});
}
function parseAndVerifyException(jadeDebug) {
return {
topic: function(err, exc) {
parser.parseException(exc, this.callback);
},
'verify stack frames': function(err, parsedObj) {
assert.isNull(err);
assert.isObject(parsedObj);
var frames = parsedObj.frames;
assert.isArray(frames);
assert.equal(parsedObj.class, 'ReferenceError');
assert.equal(parsedObj.message, 'foo is not defined');
var i;
var numFrames = frames.length;
var cur;
assert.isTrue(numFrames >= 1);
var jadeDebugFound = false;
for (i = 0; i < numFrames; ++i) {
cur = frames[i];
assert.include(cur, 'method');
assert.include(cur, 'filename');
assert.include(cur, 'lineno');
if (cur.method !== '<jade>') {
assert.include(cur, 'colno');
assert.isNumber(cur.colno);
} else {
jadeDebugFound = true;
}
assert.isNumber(cur.lineno);
assert.isString(cur.method);
assert.isString(cur.filename);
}
if (jadeDebug) {
if (!jadeDebugFound) {
assert.isFalse('jade debug not found');
}
} else {
if (jadeDebugFound) {
assert.isFalse('jade debug found but should not be');
}
}
}
};
};

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc