Skip to content

Instantly share code, notes, and snippets.

@ronalddddd
Created January 11, 2017 10:00
Show Gist options
  • Save ronalddddd/425d9972c318383c0076360db94bef8d to your computer and use it in GitHub Desktop.
Save ronalddddd/425d9972c318383c0076360db94bef8d to your computer and use it in GitHub Desktop.
Reusable logger (with cloudwatch transport and request logging middleware)
// Generic logger with CloudWatch transport
const winston = require('winston');
const WinstonCloudWatch = require('winston-cloudwatch');
const AWS_REGION = process.env.npm_config_logger_cloudwatch_log_region;
const AWS_KEY_ID = process.env.npm_config_logger_cloudwatch_key_id;
const AWS_SECRET = process.env.npm_config_logger_cloudwatch_key_secret;
const LOG_GROUP_NAME = process.env.npm_config_logger_cloudwatch_log_group || 'unknown-group';
const LOG_STREAM_NAME = process.env.npm_config_logger_cloudwatch_log_stream || 'unknown-stream';
const LOG_LEVEL = process.env.npm_config_logger_log_level || 'info';
const SENSITIVE_KEYS = (process.env.npm_config_log_sensitive_keys) ?
JSON.parse(process.env.npm_config_log_sensitive_keys) : ['password'];
const loggers = {};
// Setup the default logger
loggers.default = winston;
loggers.default.level = LOG_LEVEL;
if (AWS_REGION && AWS_KEY_ID && AWS_SECRET) {
winston.add(WinstonCloudWatch, {
level: LOG_LEVEL,
logGroupName: LOG_GROUP_NAME,
logStreamName: LOG_STREAM_NAME,
awsAccessKeyId: AWS_KEY_ID,
awsSecretKey: AWS_SECRET,
awsRegion: AWS_REGION,
jsonMessage: true,
});
}
class Logger {
// Removes functions, circular-references and mask sensitive properties
static makeSerializable(obj, existingObjects = [], sensitiveKeys = SENSITIVE_KEYS) {
if (typeof obj !== 'object' || obj === null) return obj;
const outputObj = {};
existingObjects.push(obj);
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object') {
if (obj[key] instanceof Array) {
outputObj[key] = obj[key].map(arrayElement => Logger.makeSerializable(arrayElement, existingObjects));
} else {
outputObj[key] = (existingObjects.indexOf(obj[key]) > -1) ?
'***CIRCULAR-REF***' : Logger.makeSerializable(obj[key], existingObjects);
}
} else if (typeof obj[key] === 'function') {
outputObj[key] = '***FUNCTION***'
} else {
outputObj[key] = (sensitiveKeys.indexOf(key) > -1) ? '***MASKED***' : obj[key];
}
});
return outputObj;
}
static get logger() {
return Logger.getLogger();
}
static getLogger(loggerName) {
if (!loggerName || !(AWS_REGION && AWS_KEY_ID && AWS_SECRET)) {
return loggers.default;
} else if (!loggers[loggerName]) { // TODO: do something with individual stream instances
const transportName = `CloudWatch_${loggerName}`;
winston.transports[transportName] = WinstonCloudWatch;
loggers[loggerName] = winston.loggers.add(loggerName, {
[transportName]: {
level: LOG_LEVEL,
logGroupName: LOG_GROUP_NAME,
logStreamName: LOG_STREAM_NAME,
awsAccessKeyId: AWS_KEY_ID,
awsSecretKey: AWS_SECRET,
awsRegion: AWS_REGION,
jsonMessage: true,
}
});
}
return loggers[loggerName];
}
static get middleware() {
const logger = Logger.logger;
// request logging middleware
function requestLogger(req, res, next) {
// Wrap the response.end method
const _end = res.end;
res.end = function() {
const logData = Logger.makeSerializable({
statusCode: res.statusCode,
statusMessage: res.statusMessage,
method: req.method,
originalUrl: req.originalUrl,
path: req.path,
params: req.params,
query: req.query,
headers: req.headers,
body: req.body,
});
if (res.statusCode >= 400) {
try {
// TODO: this is too hacky
logData.responseBody = JSON.parse(new Buffer(arguments[0]).toString());
} catch (e) {
// not JSON response or something wrong with buffer
}
}
logger.info(`HTTP ${req.method} request`, logData);
_end.apply(res, arguments);
};
next();
}
return requestLogger;
}
}
module.exports = Logger;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment