Last active
October 3, 2018 09:25
-
-
Save satyampatro/d2925e542cfea60b39b8011be3fca4ef to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
var moment = require("moment"); | |
var stackTrace = require("stack-trace"); | |
var cuid = require("cuid"); | |
var jsonStringify = require("json-stringify-safe"); | |
var fs = require("fs"); | |
var microtime = require('microtime'); | |
const axios = require("axios"); | |
const httpClient = axios.create(); | |
// down vote | |
class Growlytics { | |
constructor(options) { | |
//Other configuration options | |
this.baseUrl = "https://growlytics.flexiloans.com:5051/"; | |
// this.baseUrl = "http://api.growlytics.com:5051"; | |
//Read logger | |
if (options.debug && options.debug == true) { | |
this.logger = new Logger('debug'); | |
} else { | |
this.logger = new Logger('info'); | |
} | |
//Read host | |
if (options.host && options.host != null && options.host.trim() != null) | |
this.baseUrl = options.host; | |
// Read proejct_id and api_key and | |
this.projectId = options.projectId; | |
this.apiKey = options.apiKey; | |
// Read list of fields to be skipped | |
this.skipApiFields = {}; | |
if (options.options && options.options.skipApiFields) | |
this.skipApiFields = options.options.skipApiFields; | |
// enable/disble option. | |
this.isEnabled = true; | |
if (options.enabled == false) { | |
this.logger.info('Growlytics initialized but in disabled mode.'); | |
this.isEnabled = false; | |
} else { | |
//Add appenders | |
this.appenderList = []; | |
if (options.hasOwnProperty('appenders')) { | |
this.appenderList = options.appenders; | |
} | |
this.logger.info('Growlytics initialized.'); | |
} | |
} | |
/******************** Start: Request Handling Part *********************/ | |
requestHandler() { | |
return function (req, res, next) { | |
try { | |
var requestId = cuid(); | |
var timestamp = moment().valueOf(); | |
var requestPid = null, requestSessionId = null; | |
if (req.headers['growlytics-info']) { | |
requestPid = req.headers['growlytics-info'].split(',')[0]; | |
if (requestPid == this.projectId) { | |
requestSessionId = req.headers['growlytics-info'].split(',')[1]; | |
} | |
} | |
try { | |
this.setRequestInfo(requestSessionId, requestId, timestamp, req, res); | |
this.sendRequestReport(requestSessionId, requestId, timestamp, req); | |
//console.log('Growlytics request info set.'); | |
} catch (err) { | |
this.logger.debug(err); | |
this.logger.error("Failed to set request information"); | |
} | |
return next(); | |
} catch (error) { | |
this.logger.debug(error); | |
this.logger.error("Growlytics- Failed to send request report: " + JSON.stringify(error)); | |
} | |
}.bind(this); | |
} | |
setRequestInfo(requestSessionId, requestId, timestamp, req, res) { | |
//Set rquest, session, customer info for given reqeust | |
req.growlyticsInfo = { | |
metadata: this, | |
reqId: requestId, | |
reqTimestamp: timestamp, | |
path: req.path || req.url, | |
httpMethod: req.method, | |
sessionId: requestSessionId | |
}; | |
//Set logging methods | |
var item = this; | |
req.getGrowlyticsLogger = function () { | |
return { | |
debug: item.logDebug.bind({ growlyticsInfo: this.growlyticsInfo }), | |
info: item.logInfo.bind({ growlyticsInfo: this.growlyticsInfo }), | |
warning: item.logWarning.bind({ growlyticsInfo: this.growlyticsInfo }), | |
error: item.logError.bind({ growlyticsInfo: this.growlyticsInfo }) | |
}; | |
}.bind({ growlyticsInfo: req.growlyticsInfo }); | |
//Set respone data capturing logic | |
let oldWrite = res.write; | |
let oldEnd = res.end; | |
const chunks = []; | |
res.write = (...restArgs) => { | |
chunks.push(new Buffer(restArgs[0])); | |
oldWrite.apply(res, restArgs); | |
}; | |
res.end = (...restArgs) => { | |
if (restArgs[0]) { | |
chunks.push(new Buffer(restArgs[0])); | |
} | |
// console.log(body); | |
oldEnd.apply(res, restArgs); | |
}; | |
//Set response handler | |
res.on('finish', function () { | |
setImmediate(function () { | |
const body = Buffer.concat(chunks).toString('utf8'); | |
req.growlyticsInfo.metadata.sendRequestFinishReport(req, res, body); | |
}); | |
}.bind({ 'request': req, 'response': res, 'chunk': chunks })); | |
//Handle client abort | |
res.on('close', function () { | |
const body = Buffer.concat(chunks).toString('utf8'); | |
console.log(' Client aborted request.'); | |
req.growlyticsInfo.metadata.sendRequestFinishReport(req, res, body); | |
}.bind({ 'request': req, 'response': res, 'chunk': chunks })); | |
//Handle request error | |
res.on('error', function (error) { | |
const body = Buffer.concat(chunks).toString('utf8'); | |
item.logger.info(' Reporting unhandled exception to Growlytics.'); | |
req.growlyticsInfo.metadata.sendErrorReport(req.growlyticsInfo, error); | |
req.growlyticsInfo.metadata.sendRequestFinishReport(req, res, body); | |
}.bind({ 'request': req, 'response': res, 'chunk': chunks })); | |
} | |
sendRequestReport(sessionId, requestId, timeStamp, req) { | |
try { | |
//If plugin is not enabled, dont process request. | |
if (!this.isEnabled) { | |
return; | |
} | |
//Skip fields if specified | |
var skipFields = []; | |
if (this.skipApiFields[req.url]) { | |
skipFields = this.skipApiFields[req.url]; | |
} | |
//Log request with input parameters | |
var connection = req.connection; | |
var address = connection && connection.address && connection.address(); | |
var portNumber = address && address.port; | |
var port = | |
!portNumber || portNumber === 80 || portNumber === 443 | |
? "" | |
: ":" + portNumber; | |
var full_url = | |
req.protocol + "://" + (req.hostname || req.host) + port + req.url; | |
var request = { | |
reqId: requestId, | |
url: full_url, | |
path: req.path || req.url, | |
httpMethod: req.method, | |
headers: req.headers, | |
httpVersion: req.httpVersion, | |
timestamp: timeStamp, | |
cookies: req.cookies || null | |
}; | |
if (req.params && typeof req.params === "object" && Object.keys(req.params).length > 0) { | |
request.params = Object.assign({}, req.params); | |
for (var i = 0; i < skipFields.length; i++) { | |
delete request.params[skipFields[i]]; | |
} | |
} | |
if (req.query && typeof req.query === "object" && Object.keys(req.query).length > 0) { | |
request.query = Object.assign({}, req.query); | |
for (var i = 0; i < skipFields.length; i++) { | |
delete request.query[skipFields[i]]; | |
} | |
} | |
if (req.body && typeof req.body === "object" && Object.keys(req.body).length > 0) { | |
request.body = Object.assign({}, req.body); | |
for (var i = 0; i < skipFields.length; i++) { | |
delete request.body[skipFields[i]]; | |
} | |
} | |
if (connection) { | |
request.connection = { | |
remoteAddress: connection.remoteAddress || req.ip, | |
remotePort: connection.remotePort, | |
bytesRead: connection.bytesRead, | |
bytesWritten: connection.bytesWritten, | |
localPort: portNumber, | |
localAddress: address ? address.address : void 0, | |
IPVersion: address ? address.family : void 0 | |
}; | |
} | |
//Save request data | |
var params = { | |
request: request | |
}; | |
if (sessionId != null) { | |
params.sessionId = sessionId; | |
} | |
var item = this; | |
//Save Request using api. | |
httpClient | |
.post(this.baseUrl + "/saveRequest", params, { | |
headers: { | |
glytics_pid: this.projectId, | |
glytics_api_key: this.apiKey, | |
} | |
}) | |
.then(function (response) { | |
}) | |
.catch(function (error) { | |
item.logger.debug(error); | |
item.logger.error("Failed to send request report. "); | |
}); | |
} catch (error) { | |
this.logger.debug(error); | |
this.logger.error(" Failed to send request report. "+ JSON.stringify(error)); | |
} | |
} | |
sendRequestFinishReport(req, res, responseData) { | |
let params = { | |
status: res.statusCode, | |
statusMessage: res.statusMessage, | |
dataLength: res.get('Content-Length') || 0, | |
data: responseData, | |
headers: res.getHeaders(), | |
timestamp: moment().valueOf(), | |
reqId: req.growlyticsInfo.reqId, | |
sessionId: req.growlyticsInfo.sessionId | |
}; | |
// console.log('params for saving resposne status', params); | |
//Call save api | |
httpClient | |
.post(req.growlyticsInfo.metadata.baseUrl + "/saveRequestFinishStatus", params, { | |
headers: { | |
glytics_pid: req.growlyticsInfo.metadata.projectId, | |
glytics_api_key: req.growlyticsInfo.metadata.apiKey, | |
} | |
}) | |
.then(function (response) { | |
//console.error("Idealize logger failed. ", response); | |
}) | |
.catch(function (error) { | |
req.growlyticsInfo.metadata.logger.debug(error); | |
req.growlyticsInfo.metadata.logger.error("Failed to request finish details. "+ JSON.stringify(error)); | |
}); | |
} | |
/******************** End: Request Handling Part *********************/ | |
/******************** Start: Log Handling Part *********************/ | |
logDebug() { | |
//Print logs to console | |
console.log.apply(console, arguments); | |
if (arguments.length == 0) { | |
return; | |
} | |
try { | |
var item = this; | |
let data = arguments; | |
setImmediate(function () { | |
item.growlyticsInfo.metadata.saveLog( | |
"debug", | |
data, | |
item.growlyticsInfo | |
); | |
//Save info | |
}); | |
} catch (error) { | |
console.error(" Failed to send log info."+ JSON.stringify(error)); | |
} | |
} | |
logInfo() { | |
//Print logs to console | |
console.log.apply(console, arguments); | |
if (arguments.length == 0) { | |
return; | |
} | |
try { | |
let data = arguments; | |
let item = this; | |
setImmediate(function () { | |
item.growlyticsInfo.metadata.saveLog( | |
"info", | |
data, | |
item.growlyticsInfo | |
); | |
//Save info | |
}); | |
} catch (error) { | |
console.error(" Failed to send log info."); | |
} | |
} | |
logWarning() { | |
//Print logs to console | |
console.log.apply(console, arguments); | |
if (arguments.length == 0) { | |
return; | |
} | |
try { | |
var item = this; | |
let data = arguments; | |
setImmediate(function () { | |
item.growlyticsInfo.metadata.saveLog( | |
"warning", | |
data, | |
item.growlyticsInfo | |
); | |
//Save info | |
}); | |
} catch (error) { | |
console.error(" Failed to send log info."); | |
} | |
} | |
logError() { | |
//Print logs to console | |
console.log.apply(console, arguments); | |
if (arguments.length == 0) { | |
return; | |
} | |
try { | |
// var trace = this.growlyticsInfo.metadata.getTrace(1); | |
var item = this; | |
let data = arguments; | |
setImmediate(function () { | |
item.growlyticsInfo.metadata.saveLog( | |
"error", | |
data, | |
item.growlyticsInfo | |
); | |
//Save info | |
}); | |
} catch (error) { | |
console.error(" Failed to send log info."); | |
} | |
} | |
saveLog(level, data, growlyticsInfo) { | |
//If data is error, serialize error. | |
for (var i = 0; i < data.length; i++) { | |
if (data[i] instanceof Error) { | |
data[i] = data[i].stack; | |
} | |
} | |
try { | |
var timestamp = microtime.now(); | |
var params = { | |
logLevel: level, | |
tags: growlyticsInfo.metadata.tags, | |
data: data, | |
requestId: growlyticsInfo.reqId, | |
timestamp: timestamp, | |
reqTimestamp: growlyticsInfo.reqTimestamp, | |
reqPath: growlyticsInfo.path, | |
httpMethod: growlyticsInfo.httpMethod, | |
sessionId: growlyticsInfo.sessionId | |
}; | |
//If plugin is not enabled, don't save log. | |
if (!growlyticsInfo.metadata.isEnabled) { | |
return; | |
} | |
//Call save api | |
httpClient | |
.post(growlyticsInfo.metadata.baseUrl + "/saveLogDetails", params, { | |
headers: { | |
glytics_pid: growlyticsInfo.metadata.projectId, | |
glytics_api_key: growlyticsInfo.metadata.apiKey, | |
} | |
}) | |
.then(function (response) { | |
//console.error("Idealize logger failed. ", response); | |
}) | |
.catch(function (error) { | |
console.log(" Failed to save log details. "); | |
}); | |
} catch (error) { | |
console.error(" Failed to send log info."); | |
} | |
//Call appenders for logging if they have method. | |
for (var i = 0; i < this.appenderList.length; i++) { | |
if (this.appenderList[i].hasOwnProperty(level)) { | |
try { | |
this.appenderList[i][level].apply(null, data); | |
} catch (err) { | |
console.error(' Appender not working', err); | |
throw err; | |
} | |
} | |
} | |
} | |
/******************** End: Log Handling Part *********************/ | |
/******************** Start: Error Handling Part *********************/ | |
errorHandler() { | |
//Register unhandledRejection handler | |
if (this.isEnabled) { | |
let item = this; | |
process.on('unhandledRejection', function (reason, p) { | |
item.logger.error("Captured Unhandled Rejection ", reason); | |
//Send error report | |
try { | |
this.sendErrorReport({ 'metadata': this }, reason); | |
} catch (error) { | |
item.logger.debug(error); | |
console.error(" Failed to send error report to Growlytics."); | |
} | |
}.bind(this)); | |
} | |
return function (err, req, res, next) { | |
var item = this; | |
try { | |
// console.log('at error with request id', req.growlyticsInfo); | |
this.sendErrorReport(req.growlyticsInfo, err); | |
return next(err); | |
} catch (error) { | |
item.logger.debug(error); | |
item.logger.error("Failed to send error report to server."); | |
return next(err); | |
} | |
}.bind(this); | |
} | |
async sendErrorReport(growlyticsInfo, error) { | |
try { | |
//If plugin is not enabled, save error details. | |
if (!growlyticsInfo.metadata.isEnabled) { | |
return; | |
} | |
var trace = stackTrace.parse(error); | |
var requestId = growlyticsInfo.reqId || null; | |
var timestamp = growlyticsInfo.reqTimestamp || moment().valueOf(); | |
var fileCode = ''; | |
if (trace.length > 0) { | |
fileCode = await this.readCodeFromFile(trace); | |
} | |
//Read error messge | |
var errorMsg = error.message; | |
if (typeof error === 'string' || error instanceof String) { | |
errorMsg = error.toString(); | |
} | |
// console.log('Message is .....', errorMsg); | |
var errorDetails = { | |
code: fileCode, | |
stackTrace: trace | |
}; | |
var params = { | |
timestamp: timestamp, | |
requestId: requestId, | |
reqTimestamp: growlyticsInfo.reqTimestamp, | |
reqPath: growlyticsInfo.path, | |
httpMethod: growlyticsInfo.httpMethod, | |
sessionId: growlyticsInfo.sessionId, | |
errorMsg: errorMsg, | |
errorDetails: errorDetails | |
}; | |
let errorSaveUrl = '/saveErrorRequest'; | |
if (requestId == null) { | |
errorSaveUrl = '/saveError'; | |
} | |
//Save Request using api. | |
let item = this; | |
httpClient | |
.post(this.baseUrl + errorSaveUrl, params, { | |
headers: { | |
glytics_pid: growlyticsInfo.metadata.projectId, | |
glytics_api_key: growlyticsInfo.metadata.apiKey, | |
} | |
}) | |
.then(function (response) { | |
////console.log("Request saved. "); | |
}) | |
.catch(function (error) { | |
item.logger.debug(error); | |
item.logger.error(" Failed to send error report."); | |
}); | |
} catch (error) { | |
this.logger.debug(error); | |
this.logger.error("Failed to send error report to server."); | |
} | |
} | |
//Read files for given stack trace file name | |
async readCodeFromFile(trace) { | |
let item = this; | |
return new Promise((resolve, reject) => { | |
try { | |
var fileName = trace[0].fileName; | |
var lineNo = trace[0].lineNumber; | |
var padding = 6; | |
fs.readFile(fileName, function (err, data) { | |
var resultLines = null; | |
if (!err) { | |
var fileLines = data.toString().split("\n"); | |
resultLines = {}; | |
var startLine = lineNo - padding, | |
endLine = lineNo - 1 + padding; | |
for (var i = startLine; i < endLine + padding; i++) { | |
if (fileLines[i] !== undefined && fileLines[i] != null) { | |
resultLines[i + 1] = fileLines[i].toString(); | |
} | |
} | |
} | |
resolve(resultLines); | |
}); | |
} catch (err) { | |
// console.log("-------->Failed to read code", err); | |
item.logger.debug(err); | |
item.logger.error(" Failed to send error report to server."); | |
resolve(null); | |
} | |
}); | |
} | |
getTrace(stacksToSkip) { | |
stacksToSkip++; | |
var stackTrace = require('stack-trace'); | |
var trace = stackTrace.get(); | |
var traceToReturn = []; | |
for (var i = stacksToSkip; i < trace.length; i++) { | |
var cr = trace[i]; | |
// console.log(cr.getFileName() + ':' + cr.getLineNumber()); | |
traceToReturn.push({ | |
'fileName': cr.getFileName(), | |
'lineNumber': cr.getLineNumber(), | |
'functionName': cr.getFunctionName(), | |
'typeName': cr.getTypeName(), | |
'methodName': cr.getMethodName(), | |
'columnNumber': cr.getColumnNumber(), | |
'native': cr.isNative() | |
}); | |
} | |
return traceToReturn; | |
} | |
/******************** End: Error Handling Part *********************/ | |
} | |
module.exports = Growlytics; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment