logops
Version:
Simple and performant nodejs JSON logger
212 lines (186 loc) • 6.32 kB
JavaScript
/**
* @license
* Copyright 2015 Telefonica Investigación y Desarrollo, S.A.U
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*jshint -W072*/
;
var util = require('util'),
chalk = require('chalk'),
safeStringify = require('safe-json-stringify'),
serializeErr = require('serr'),
_ = require('lodash');
var DEFAULT_NOT_AVAILABLE = 'n/a';
var notAvailable = DEFAULT_NOT_AVAILABLE,
templateTrace = 'time=%s | lvl=%s | corr=%s | trans=%s | op=%s | msg=';
/**
* The exported formatters.
*
* @type {Object}
*/
var API = module.exports = {
dev: formatDevTrace,
json: formatJsonTrace,
stacktracesWith: ['ERROR', 'FATAL'],
pipe: formatTrace, // Deprecated
setNotAvailable: setNotAvailable // Deprecated
};
/**
* Sets a value for those fields that are not available in the context. This field
* will only be used in the 'pipes' formatter.
*
* @param {String} na New value for not available fields.
* @deprecated
*/
function setNotAvailable(na) {
notAvailable = na;
}
/**
* Formats a trace message with some nice TTY colors
*
* @param {String} level One of the following values
* ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
* @param {Object} context Additional information to add to the trace
* @param {String} message The main message to be added to the trace
* @param {Array} args More arguments provided to the log function
* @param {Error|undefined} err A cause error used to log extra information
*
* @return {String} The trace formatted
*/
function formatDevTrace(level, context, message, args, err) {
var str,
mainMessage = util.format.apply(global, [message].concat(args)),
printStack = API.stacktracesWith.indexOf(level) > -1,
errCommomMessage = err && (err.name + ': ' + err.message),
isErrorLoggingWithoutMessage = mainMessage === errCommomMessage;
switch (level) {
case 'DEBUG':
str = chalk.grey(level);
break;
case 'INFO':
str = chalk.blue(level) + ' '; // Pad to 5 chars
break;
case 'WARN':
str = chalk.yellow(level) + ' '; // Pad to 5 chars
break;
case 'ERROR':
str = chalk.red(level);
break;
case 'FATAL':
str = chalk.red.bold(level);
break;
}
str += ' ' + mainMessage;
if (isErrorLoggingWithoutMessage) {
str += colorize(chalk.gray, serializeErr(err).toString(printStack).substr(mainMessage.length));
} else if (err) {
str += '\n' + colorize(chalk.gray, serializeErr(err).toString(printStack));
}
var localContext = _.omit(context, formatDevTrace.omit);
str += Object.keys(localContext).length ?
' ' + colorize(chalk.gray, util.inspect(localContext)) :
'';
// pad all subsequent lines with as much spaces as "DEBUG " or "INFO " have
return str.replace(new RegExp('\r?\n','g'), '\n ');
}
/**
* Context properties that should be skipped from printing in dev format
* @type {Array.<String>}
*/
formatDevTrace.omit = [];
// Damm! colors are not applied to multilines! split, apply and join!
function colorize(color, str) {
return str
.split('\n')
.map(part => color(part))
.join('\n');
}
/**
* Formats a trace message with fields separated by pipes.
*
* DEPRECATED!
*
* @param {String} level One of the following values
* ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
* @param {Object} context Additional information to add to the trace
* @param {String} message The main message to be added to the trace
* @param {Array} args More arguments provided to the log function
* @param {Error|undefined} err A cause error used to log extra information
*
* @return {String} The trace formatted
* @deprecated
*/
function formatTrace(level, context, message, args, err) {
var recontext = {
time: (new Date()).toISOString(),
lvl: level,
corr: context.corr || notAvailable,
trans: context.trans || notAvailable,
op: context.op || notAvailable
};
Object.keys(context)
.filter((key) => {
return !(context[key] && Object.prototype.toString.call(context[key]) === '[object Function]');
})
.forEach((key) => {
recontext[key] = context[key] || notAvailable;
});
if (message instanceof Date || message instanceof Error) {
// Node6 related hack. See https://github.com/telefonicaid/logops/issues/36
recontext.msg = util.format(message);
} else {
recontext.msg = message;
}
var str = Object.keys(recontext)
.map((key) => key + '=' + recontext[key])
.join(' | ');
args.unshift(str);
if (err && message !== '' + err) {
args.push(err);
}
return util.format.apply(global, args);
}
/**
* Formats a trace message in JSON format
*
* @param {String} level One of the following values
* ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
* @param {Object} context Additional information to add to the trace
* @param {String} message The main message to be added to the trace
* @param {Array} args More arguments provided to the log function
* @param {Error|null} err A cause error used to log extra information
*
* @return {String} The trace formatted
*/
function formatJsonTrace(level, context, message, args, err) {
return formatJsonTrace.stringify(formatJsonTrace.toObject(level, context, message, args, err));
}
formatJsonTrace.stringify = (obj) => {
try {
return JSON.stringify(obj);
} catch (err) {
return safeStringify(obj);
}
};
formatJsonTrace.toObject = (level, context, message, args, err) => {
let log = {};
for (let attrname in context) {
log[attrname] = context[attrname];
}
log.time = new Date();
log.lvl = level;
log.err = err && serializeErr(err).toObject(API.stacktracesWith.indexOf(level) > -1);
log.msg = util.format.apply(global, [message].concat(args));
return log;
};