UNPKG

debug-logger

Version:

A wrapper for visionmedia/debug logger, adding levels and colored output

365 lines (319 loc) 9.06 kB
'use strict'; var util = require('util'), vmDebug = require('debug'), streamSpy = require('./stream-spy'); exports = module.exports = debugLogger; exports.debug = vmDebug; exports.config = function config(options){ options = options || {}; if(options.ensureNewline){ ensureNewline(); } if(options.inspectOptions){ exports.inspectOptions = options.inspectOptions; } if(options.levels){ merge(exports.levels, options.levels); } return debugLogger; }; exports.inspectOptions = {}; exports.colors = { black : 0, red : 1, green : 2, yellow : 3, blue : 4, magenta : 5, cyan : 6, white : 7 }; exports.colorReset = '\x1b[0m'; exports.levels = { trace : { color : exports.colors.cyan, prefix : '', namespaceSuffix : ':trace', level : 0, fd : 1 // currently only 1 (stdout) is supported. stderr is debug's standard }, debug : { color : exports.colors.blue, prefix : '', namespaceSuffix : ':debug', level : 1, fd : 1 }, log : { color : '', prefix : ' ', namespaceSuffix : ':log', level : 2, fd : 1 }, info : { color : exports.colors.green, prefix : ' ', namespaceSuffix : ':info', level : 3, fd : 1 }, warn : { color : exports.colors.yellow, prefix : ' ', namespaceSuffix : ':warn', level : 4 }, error : { color : exports.colors.red, prefix : '', namespaceSuffix : ':error', level : 5 } }; exports.styles = { underline : '\x1b[4m' }; function time(label){ this.timeLabels[label] = process.hrtime(); } function timeEnd(label, level){ level = level || 'log'; var diff = process.hrtime(this.timeLabels[label]); var diffMs = (diff[0] * 1e9 + diff[1]) / 1e6; this[level](label + ':', diffMs + 'ms'); return diffMs; } function dir(obj, options, level){ if(!level && this[options]){ level = options; options = undefined; } options = options || exports.inspectOptions; level = level || 'log'; this[level](util.inspect(obj, options)); } function assert(expression){ if (!expression) { var level = 'error'; var arr = Array.prototype.slice.call(arguments, 1); if(this[arr[arr.length-1]]){ level = arr[arr.length-1]; arr = arr.slice(0, -1); } var assrt = require('assert'); var err = new assrt.AssertionError({ message: util.format.apply(this, arr), actual: false, expected: true, operator: '==', stackStartFunction: assert }); this[level](err); throw err; } } var ensureNewlineEnabled = false; var fd = parseInt(process.env.DEBUG_FD, 10) || 2; function ensureNewline(){ if(fd !== 1 && fd !== 2){ return; } streamSpy.enable(); ensureNewlineEnabled = true; return debugLogger; } function merge(object, source){ Object.keys(source).forEach(function(key){ var val = source[key]; if(!object[key] || !isObject(val)){ object[key] = val; return; } Object.keys(val).forEach(function(idx){ object[key][idx] = val[idx]; }); }); } function getLogLevel(namespace) { if(!process.env.DEBUG_LEVEL) { return 0; } var debugLevel = process.env.DEBUG_LEVEL.toLowerCase(); if(debugLevel.indexOf('*:') === 0){ return hasLogLevel(debugLevel.slice(2)) || 0; } var hasLevel = hasLogLevel(debugLevel); if(hasLevel !== null){ return hasLevel; } if(!namespace) { return 0; } //currently we will only process the first part of namespace var appNamespace = namespace.split(':')[0].toLowerCase(); var debugLevelParts = debugLevel.split(','); var i; for(i = 0; i < debugLevelParts.length; i++){ var parts = debugLevelParts[i].split(':'); if(appNamespace === parts[0]){ return hasLogLevel(parts[parts.length-1]) || 0; } } return 0; } function hasLogLevel(level) { if(!level) { return null; } if (!isNaN(level)){ return level; } else if(isString(level) && exports.levels[level]){ return exports.levels[level].level || 0; } return null; } function isString(str){ return typeof str === 'string' || str instanceof String; } function isObject(obj){ return typeof obj === 'object' || obj instanceof Object; } function hasFormattingElements(str){ if(!str) { return false; } var res = false; ['%s', '%d', '%j', '%o'].forEach(function(elem){ if(str.indexOf(elem) >= 0) { res = true; } }); return res; } function getErrorMessage(e) { var errorStrings = ['' + e]; if (typeof e === 'undefined') { return errorStrings; } if (e === null) { return errorStrings; } if (e instanceof Date) { return errorStrings; } if (e instanceof Error) { errorStrings[0] = e.toString(); if (e.stack) { errorStrings[1] = 'Stack trace'; errorStrings[2] = e.stack; } return errorStrings; } if (isObject(e)) { var inspection = util.inspect(e, exports.inspectOptions); if(inspection.length < 55){ errorStrings[0] = inspection; return errorStrings; } if (typeof e.toString !== 'undefined') { errorStrings[0] = e.toString(); } errorStrings[1] = 'Inspected object'; errorStrings[2] = inspection; } return errorStrings; } function getForeColor(color){ if(!isNaN(color)){ return '\x1b[3' + color + 'm'; } else if(exports.colors[color]){ return '\x1b[3' + exports.colors[color] + 'm'; } return color; } function disableColors(loggerLevel, disable){ if(disable){ loggerLevel.color = ''; loggerLevel.reset = ''; loggerLevel.inspectionHighlight = ''; } } var debugInstances = {}; function getDebugInstance(namespace, color, fd){ if(!debugInstances[namespace]){ debugInstances[namespace] = vmDebug(namespace); if(fd === 1 && isNaN(parseInt(process.env.DEBUG_FD))){ debugInstances[namespace].log = console.log.bind(console); } if(!isNaN(color)){ debugInstances[namespace].color = color; } } return debugInstances[namespace]; } function debugLogger(namespace) { var levels = exports.levels; var debugLoggers = { 'default': getDebugInstance.bind(this, namespace, '') }; var logger = function(){ debugLoggers['default']().apply(this, arguments); }; logger.logLevel = getLogLevel(namespace); logger.timeLabels = {}; logger.time = time; logger.timeEnd = timeEnd; logger.dir = dir; logger.assert = assert; Object.keys(levels).forEach(function(levelName) { var loggerNamespaceSuffix = levels[levelName].namespaceSuffix ? levels[levelName].namespaceSuffix : 'default'; if(!debugLoggers[loggerNamespaceSuffix]){ debugLoggers[loggerNamespaceSuffix] = getDebugInstance.bind(this, namespace + loggerNamespaceSuffix, levels[levelName].color, levels[levelName].fd); } var levelLogger = debugLoggers[loggerNamespaceSuffix]; var initialized = false; function logFn() { if (logger.logLevel > logger[levelName].level) { return; } var levelLog = levelLogger(); if(!levelLog.enabled) { return; } if(!initialized){ initialized = true; disableColors(logger[levelName], !levelLog.useColors); } if (isString(arguments[0]) && hasFormattingElements(arguments[0])){ arguments[0] = logger[levelName].color + levels[levelName].prefix + logger[levelName].reset + arguments[0]; return levelLog.apply(this, arguments); } var selfArguments = arguments; var errorStrings = Object.keys(selfArguments).map(function(key){ return getErrorMessage(selfArguments[key]); }); var message = ""; var inspections = ""; var i, param; var n = 1; for(i=0; i<errorStrings.length; i++){ param = errorStrings[i]; message += i === 0 ? param[0] : ' ' + param[0]; if (param.length > 1) { var highlightStack = param[1].indexOf('Stack') >= 0 ? logger[levelName].color : ''; inspections += '\n' + logger[levelName].inspectionHighlight + '___' + param[1] + ' #' + n++ + '___' + logger[levelName].reset +'\n' + highlightStack + param[2] + logger[levelName].reset; } }; levelLog(logger[levelName].color + levels[levelName].prefix + logger[levelName].reset + message + inspections); }; function logNewlineFn() { if (streamSpy.lastCharacter !== '\n') { vmDebug.log(''); } logFn.apply(logFn, arguments); }; logger[levelName] = ensureNewlineEnabled ? logNewlineFn : logFn; logger[levelName].level = levels[levelName].level; logger[levelName].logger = function(){ return levelLogger(); }; logger[levelName].enabled = function(){ return logger.logLevel <= logger[levelName].level && levelLogger().enabled; }; logger[levelName].color = getForeColor(levels[levelName].color); logger[levelName].reset = exports.colorReset; logger[levelName].inspectionHighlight = exports.styles.underline; }); return logger; }