Skip to content

Instantly share code, notes, and snippets.

@satyampatro
Last active October 3, 2018 09:25
Show Gist options
  • Save satyampatro/d2925e542cfea60b39b8011be3fca4ef to your computer and use it in GitHub Desktop.
Save satyampatro/d2925e542cfea60b39b8011be3fca4ef to your computer and use it in GitHub Desktop.
"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