var async = require('async'); var parser = require('./parser'); var requestIp = require('request-ip'); var url = require('url'); var _ = require('../utility'); var scrub = require('../scrub'); function baseData(item, options, callback) { var environment = (options.payload && options.payload.environment) || options.environment; var data = { timestamp: Math.round(item.timestamp / 1000), environment: item.environment || environment, level: item.level || 'error', language: 'javascript', framework: item.framework || options.framework, uuid: item.uuid, notifier: JSON.parse(JSON.stringify(options.notifier)), custom: item.custom }; if (options.codeVersion) { data.code_version = options.codeVersion; } else if (options.code_version) { data.code_version = options.code_version; } var props = Object.getOwnPropertyNames(item.custom || {}); props.forEach(function (name) { if (!data.hasOwnProperty(name)) { data[name] = item.custom[name]; } }); data.server = { host: options.host, argv: process.argv.concat(), pid: process.pid }; if (options.branch) { data.server.branch = options.branch; } if (options.root) { data.server.root = options.root; } item.data = data; callback(null, item); } function addMessageData(item, options, callback) { item.data = item.data || {}; item.data.body = item.data.body || {}; var message = item.message || 'Item sent with null or missing arguments.'; item.data.body.message = { body: message }; callback(null, item); } function addErrorData(item, options, callback) { if (item.stackInfo) { item.data = item.data || {}; item.data.body = item.data.body || {}; item.data.body.trace_chain = item.stackInfo; } callback(null, item); } function addBody(item, options, callback) { if (item.stackInfo) { addErrorData(item, options, callback); } else { addMessageData(item, options, callback); } } function handleItemWithError(item, options, callback) { if (!item.err) { return callback(null, item); } var err = item.err; var errors = []; var chain = []; do { errors.push(err); err = err.nested || err.cause; } while (err); item.stackInfo = chain; if (options.addErrorContext) { _.addErrorContext(item, errors); } var cb = function(e) { if (e) { item.message = item.err.message || item.err.description || item.message || String(item.err); item.diagnostic.buildTraceData = e.message; delete item.stackInfo; } callback(null, item); }; async.eachSeries(errors, _buildTraceData(chain, options, item), cb); } function addRequestData(item, options, callback) { item.data = item.data || {}; var req = item.request; if (!req) { callback(null, item); return; } var baseUrl = req.baseUrl || ''; if (options.addRequestData && _.isFunction(options.addRequestData)) { options.addRequestData(item.data, req); callback(null, item); return; } var requestData = _buildRequestData(req); _.filterIp(requestData, options.captureIp); item.data.request = requestData; var routePath; if (req.route) { routePath = req.route.path; item.data.context = baseUrl && baseUrl.length ? baseUrl + routePath : routePath; } else { try { routePath = req.app._router.matchRequest(req).path; item.data.context = baseUrl && baseUrl.length ? baseUrl + routePath : routePath; } catch (ignore) { // Ignored } } var captureEmail = options.captureEmail; var captureUsername = options.captureUsername; if (req.rollbar_person) { var person = req.rollbar_person; if (!captureEmail && person.email) { person.email = null; } if (!captureUsername && person.username) { person.username = null; } item.data.person = person; } else if (req.user) { item.data.person = {id: req.user.id}; if (req.user.username && captureUsername) { item.data.person.username = req.user.username; } if (req.user.email && captureEmail) { item.data.person.email = req.user.email; } } else if (req.user_id || req.userId) { var userId = req.user_id || req.userId; if (_.isFunction(userId)) { userId = userId(); } item.data.person = {id: userId}; } callback(null, item); } function addLambdaData(item, options, callback) { var c = item.lambdaContext; if (!c) { callback(null, item); return; } var data = { remainingTimeInMillis: c.getRemainingTimeInMillis(), callbackWaitsForEmptyEventLoop: c.callbackWaitsForEmptyEventLoop, functionName: c.functionName, functionVersion: c.functionVersion, arn: c.invokedFunctionArn, requestId: c.awsRequestId }; item.data = item.data || {}; item.data.custom = item.data.custom || {}; item.data.custom.lambda = data; callback(null, item); } function scrubPayload(item, options, callback) { var scrubHeaders = options.scrubHeaders || []; var scrubFields = options.scrubFields || []; var scrubPaths = options.scrubPaths || []; scrubFields = scrubHeaders.concat(scrubFields); parseRequestBody(item.data.request, options); item.data = scrub(item.data, scrubFields, scrubPaths); serializeRequestBody(item.data.request, options); callback(null, item); } function parseRequestBody(req, options) { if (!req || !options.scrubRequestBody) { return } try { if (_.isString(req.body) && _isJsonContentType(req)) { req.body = JSON.parse(req.body); } } catch (e) { req.body = null; req.error = 'request.body parse failed: ' + e.message; } } function serializeRequestBody(req, options) { if (!req || !options.scrubRequestBody) { return } try { if (_.isObject(req.body) && _isJsonContentType(req)) { req.body = JSON.stringify(req.body); } } catch (e) { req.body = null; req.error = 'request.body serialization failed: ' + e.message; } } /** Helpers **/ function _isJsonContentType(req) { return req.headers && req.headers['content-type'] && req.headers['content-type'].includes('json'); } function _buildTraceData(chain, options, item) { return function(ex, cb) { parser.parseException(ex, options, item, function (err, errData) { if (err) { return cb(err); } chain.push({ frames: errData.frames, exception: { class: errData['class'], message: errData.message } }); return cb(null); }); }; } function _extractIp(req) { var ip = req.ip; if (!ip) { ip = requestIp.getClientIp(req); } return ip; } function _buildRequestData(req) { var headers = req.headers || {}; var host = headers.host || ''; var proto = req.protocol || ((req.socket && req.socket.encrypted) ? 'https' : 'http' ); var parsedUrl; var baseUrl = req.baseUrl || ''; if (_.isType(req.url, 'string')) { var fullUrl = baseUrl && baseUrl.length ? baseUrl + req.url : req.url parsedUrl = url.parse(fullUrl, true); } else { parsedUrl = req.url || {}; } parsedUrl.protocol = parsedUrl.protocol || proto; parsedUrl.host = parsedUrl.host || host; var reqUrl = url.format(parsedUrl); var data = { url: reqUrl, user_ip: _extractIp(req), headers: headers, method: req.method }; if (parsedUrl.search && parsedUrl.search.length > 0) { data.GET = parsedUrl.query; } var body = req.body || req.payload; if (body) { var bodyParams = {}; if (_.isIterable(body)) { for (var k in body) { if (Object.prototype.hasOwnProperty.call(body, k)) { bodyParams[k] = body[k]; } } data[req.method] = bodyParams; } else { data.body = body; } } return data; } module.exports = { baseData: baseData, handleItemWithError: handleItemWithError, addBody: addBody, addMessageData: addMessageData, addErrorData: addErrorData, addRequestData: addRequestData, addLambdaData: addLambdaData, scrubPayload: scrubPayload };