opbeat-ionic
Version:
The unofficial Opbeat client for Ionic
128 lines (114 loc) • 4.19 kB
JavaScript
;
var url = require('url');
var util = require('util');
var stackman = require('stackman')();
var stringify = require('json-stringify-safe');
exports.parseMessage = function (message, options) {
if (typeof message === 'object') {
// if `captureError` is parsed an object instead of a string we except
// it to be in the format of `{ message: '...', params: [] }` and it will
// be used as `param_message`.
if (message.message) {
options.param_message = message.message;
message = util.format.apply(this, [message.message].concat(message.params));
} else {
message = util.inspect(message);
}
}
options.message = message;
};
exports.parseError = function (err, options, callback) {
stackman(err, function (stack) {
setMessage(options, err);
setException(options, err);
setStacktrace(options, stack.frames);
setCulprint(options, stack.frames);
setModule(options);
setExtraProperties(options, stack.properties);
callback(options);
});
};
exports.parseRequest = function (req, options) {
var protocol = req.socket.encrypted ? 'https' : 'http',
host = req.headers.host || '<no host>',
userAgent = req.headers['user-agent'];
options = options || {};
options.http = {
method : req.method,
url : protocol + '://' + host + req.url,
query_string : url.parse(req.url).query,
headers : req.headers,
secure : req.socket.encrypted,
remote_host : req.socket.remoteAddress,
data : req.json || req.body || '<unavailable: use bodyParser middleware>'
};
if (typeof options.http.data !== 'string') options.http.data = stringify(options.http.data);
if (req.cookies) options.http.cookies = req.cookies;
if (userAgent) options.http.user_agent = userAgent;
return options;
};
function setMessage(options, err) {
// If the message have already been set, for instance when calling
// `captureError` with a string or an object-literal, just return without
// overwriting it
if (options.message) return;
// TODO: Normally err.name is just "Error", maybe we should omit it in that case?
options.message = err.name + ': ' + (err.message || '<no message>');
}
function setException(options, err) {
options.exception = {
type : err.name,
value : err.message
};
}
function setStacktrace(options, frames) {
options.stacktrace = {
frames: frames.map(function (callsite) {
var filename = callsite.getFileName();
var frame = {
filename : callsite.getRelativeFileName() || '',
lineno : callsite.getLineNumber(),
'function' : callsite.getFunctionNameSanitized(),
in_app : callsite.isApp()
};
if (!Number.isFinite(frame.lineno)) frame.lineno = 0; // this should be an int, but sometimes it's not?!
if (filename) frame.abs_path = filename;
if ('context' in callsite) {
frame.pre_context = callsite.context.pre;
frame.context_line = callsite.context.line;
frame.post_context = callsite.context.post;
}
return frame;
})
};
}
// Default `culprit` to the top of the stack or the highest `in_app` frame if such exists
function setCulprint(options, frames) {
if (!frames.length) return;
var filename = frames[0].getRelativeFileName();
var fnName = frames[0].getFunctionNameSanitized();
for (var n = 0, l = frames.length; n < l; n++) {
if (frames[n].in_app) {
filename = frames[n].getRelativeFileName();
fnName = frames[n].getFunctionNameSanitized();
break;
}
}
options.culprit = filename ? fnName + ' (' + filename + ')' : fnName;
};
function setModule(options) {
var frame = options.stacktrace.frames[0];
if (!frame || frame.in_app) return;
var match = frame.filename.match(/node_modules\/([^\/]*)/);
if (!match) return;
options.exception.module = match[1];
}
function setExtraProperties(options, properties) {
var keys = Object.keys(properties || {});
if (!keys.length) return;
options.extra = options.extra || {};
keys.forEach(function (key) {
if (key in options.extra) return;
options.extra[key] = properties[key];
});
}