Scribe.js/lib/console2.js

641 lines
19 KiB
JavaScript
Raw Normal View History

2014-10-24 15:33:39 +02:00
(function () {
'use strict';
var stack = require('callsite'),
util = require('util'),
EventEmitter = require('events').EventEmitter,
2014-10-25 19:43:54 +02:00
path = require('path'),
2014-10-26 09:09:26 +01:00
colorsjs = require('colors/safe');
2014-10-24 15:33:39 +02:00
/**
* consoleOriginal
*
* NodeJS console object
* @type {Object}
*/
var consoleOriginal = console;
/**
* getLocation
*
* Get location of log.
* ie. filename and line number
2014-12-15 12:39:47 +01:00
*
* @return {Obsject}
* @return {String} filename
* @return {Number} line
*/
var getLocation = function () {
2014-12-20 10:09:14 +01:00
var st = stack()[2],
result = {};
result.filename = path.basename(st.getFileName());
result.line = st.getLineNumber();
return result;
};
2014-10-24 15:33:39 +02:00
/**
* buildTime
*
* @param {timestamp} timestamp
*
2014-12-20 10:09:14 +01:00
* @return {Object}
* @return {String} msg ISO time
2014-12-20 10:09:14 +01:00
* @return {int} msgLength
2014-10-24 15:33:39 +02:00
*/
var buildTime = function (timestamp) {
var time = (new Date(timestamp)).toISOString();
return {
msg : time,
msgLength : time.length
};
2014-10-24 15:33:39 +02:00
};
/**
2014-10-25 17:13:44 +02:00
* buildDate
2014-10-24 15:33:39 +02:00
*
* @param {timestamp} timestamp
*
2014-12-20 10:09:14 +01:00
* @return {Object}
* @return {String} msg Date to string
2014-12-20 10:09:14 +01:00
* @return {int} msgLength
2014-10-24 15:33:39 +02:00
*/
2014-10-25 17:13:44 +02:00
var buildDate = function (timestamp) {
var date = (new Date(timestamp)).toDateString();
return {
msg : date,
msgLength : date.length
};
2014-10-24 15:33:39 +02:00
};
/**
* buildTags
*
* @param {Array} tags Tags are string or object {msg :'', colors : []}
*
2014-12-20 10:09:14 +01:00
* @return {Object}
* @return {String} msg Tags string with colors if given. Ex : "[tag1][tag2]"
* @return {int} msgLength Tags string length (without colors elements)
2014-10-24 15:33:39 +02:00
*/
var buildTags = function (tags) {
2014-12-20 10:09:14 +01:00
var result = "",
length = 0;
tags.forEach(function (tag) {
2014-12-22 18:56:52 +01:00
if(typeof tag === "undefined" || tag === null) {
return;
}
2014-12-21 21:13:27 -06:00
if (typeof tag === 'object') {
result += applyColors('[' + tag.msg + ']', tag.colors);
2014-12-23 13:02:38 -06:00
length += ("" + tag.msg).length + 2;
} else {
result += '[' + tag + ']';
2014-12-23 13:02:38 -06:00
length += ("" + tag).length + 2;
}
});
return {
msg : result,
msgLength : length
};
2014-10-24 15:33:39 +02:00
};
2014-10-25 17:25:05 +02:00
/**
* buildFileInfos
*
* @param {Object} infos
2014-10-25 17:25:05 +02:00
* @param {String} infos.filename
* @param {String} infos.line
2014-12-20 10:09:14 +01:00
*
* @param {String|Array} fileColors Optional colors.
* @param {String|Array} lineColors Optional colors.
*
2014-12-20 10:09:14 +01:00
* @return {Object}
* @return {String} msg File infos with colors if given
2014-12-20 10:09:14 +01:00
* @return {int} msgLength
2014-10-25 17:25:05 +02:00
*/
var buildFileInfos = function (infos, fileColors, lineColors) {
return {
2014-12-21 21:13:27 -06:00
msg : '[' + applyColors(infos.filename, fileColors) + ':' +
applyColors(infos.line, lineColors) + ']',
2014-12-23 13:02:38 -06:00
msgLength : (infos.filename + "" +
infos.line).length + 3
};
2014-10-24 15:33:39 +02:00
};
2014-10-26 14:34:59 +01:00
/**
* areAllStringOrNumber
*
* Check in an array contains only string and number
*
* @param {Array} arr
* @retrun {Boolean}
*/
var areAllStringOrNumber = function (arr) {
2014-10-26 16:07:32 +01:00
var result = true;
2014-10-26 14:34:59 +01:00
arr.forEach(function (elem) {
2014-10-26 16:07:32 +01:00
if (!(typeof elem === 'string' || typeof elem === 'number')) {
result = false;
2014-10-26 14:34:59 +01:00
}
});
2014-10-26 16:07:32 +01:00
return result;
2014-10-26 14:34:59 +01:00
};
2014-10-26 09:09:26 +01:00
/**
* applyColors
*
* Apply style with colors.js on the console output
* @param {String} msg The msg to stylize
* @param {Array|String} colors The colors
*
* @return {String}
*/
var applyColors = function (msg, colors) {
if (!colors) {
return msg;
2014-10-26 09:09:26 +01:00
}
if (typeof colors === 'string') {
colors = [colors];
}
colors.forEach(function (color) {
if (typeof colorsjs[color] === 'function') {
msg = colorsjs[color](msg);
}
});
return msg;
};
2014-10-24 15:33:39 +02:00
/**
* spaceLUT
*
* Save offset space strings
*/
var spaceLUT = {};
/**
* getSpaces
*
* @param {int} offset
* @return {String} A string of [offset] space
*/
var getSpaces = function(offset){
if(typeof spaceLUT[offset] === "undefined"){
spaceLUT[offset] = new Array(offset).join(' ');
}
return spaceLUT[offset];
};
2014-10-24 15:33:39 +02:00
/*
* Console2
*
* @constructor
*
2014-10-26 09:09:26 +01:00
* @param {Object} opt Optional default options for all loggers.
* @param {Boolean} opt.logInConsole Should all loggers print to console by default ? Default true.
*
2014-10-26 09:09:26 +01:00
* @param {int} opt.contextMediumSize Medium size of the context part of a log message.
* Used when calculating indent. Default to 45.
* @param {int} opt.spaceSize Space between context part and log part. Default to 4.
2014-12-20 10:09:14 +01:00
*
2014-10-26 09:09:26 +01:00
* @param {Array|String} opt.colors Default colors output for all loggers. Default ['cyan'].
* @param {Array|String} opt.tagsColors Default colors output for tags. Default undefined.
* @param {Array|String} opt.timeColors Default colors output for time. Default undefined.
* @param {Array|String} opt.dateColors Default colors output for date. Default undefined.
* @param {Array|String} opt.fileColors Default colors output for filename. Default undefined.
* @param {Array|String} opt.lineColors Default colors output for line number. Default undefined.
2014-12-20 10:09:14 +01:00
*
2014-10-26 09:09:26 +01:00
* @param {Boolean} opt.alwaysTags Always print tags (even without tag() ). Default false.
* @param {Boolean} opt.alwaysLocation Always print location (even without file() ). Default false.
* @param {Boolean} opt.alwaysTime Always print time (even without time() ). Default false.
* @param {Boolean} opt.alwaysDate Always print date (even without date() ). Default false.
2014-10-24 15:33:39 +02:00
*/
var Console2 = function (opt) {
if (!opt) {
opt = {};
}
/**
* opt
*
* Constructor opt.
* Setting default.
*/
this.opt = {
2014-10-26 12:11:01 +01:00
logInConsole : opt.logInConsole !== false,
contextMediumSize : opt.contextMediumSize || 45,
spaceSize : opt.spaceSize || 4,
colors : opt.colors || "cyan",
tagsColors : opt.tagsColors,
timeColors : opt.timeColors,
dateColors : opt.dateColors,
lineColors : opt.lineColors,
fileColors : opt.fileColors,
2014-10-26 12:11:01 +01:00
alwaysTags : opt.alwaysTags === true,
alwaysLocation : opt.alwaysLocation === true,
alwaysTime : opt.alwaysTime === true,
alwaysDate : opt.alwaysDate === true
};
2014-10-24 15:33:39 +02:00
/**
* _tags
*
* Store all tags for current log
2014-12-20 10:09:14 +01:00
*
2014-10-24 15:33:39 +02:00
* @type {Array}
*/
this._tags = [];
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
/**
*._time
*
* Log time (full date) ?
2014-12-20 10:09:14 +01:00
*
2014-10-24 15:33:39 +02:00
* @type {Boolean}
*/
this._time = false;
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
/**
2014-10-25 17:13:44 +02:00
* _date
2014-10-24 15:33:39 +02:00
*
2014-10-25 17:13:44 +02:00
* Log date ?
2014-12-20 10:09:14 +01:00
*
2014-10-24 15:33:39 +02:00
* @type {Boolean}
*/
2014-10-25 17:13:44 +02:00
this._date = false;
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
/**
* _location
*
* Should we log filename and line number ?
2014-10-24 15:33:39 +02:00
*
* @type {Boolean}
2014-10-24 15:33:39 +02:00
*/
2014-12-20 10:09:14 +01:00
this._location = false;
2014-10-24 15:33:39 +02:00
/**
* _reset
*
* Reset properties after log
*/
this._reset = function () {
this._tags = [];
2014-10-25 17:13:44 +02:00
this._time = false;
this._date = false;
2014-10-26 14:34:59 +01:00
this._location = false;
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
return this;
};
2014-12-20 10:09:14 +01:00
/**
* loggers
*
* Stores loggers infos
* @type {Object}
*/
this.loggers = {};
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
};
2014-12-21 21:13:27 -06:00
// inherits form EventEmitter.prototype
2014-10-24 15:33:39 +02:00
util.inherits(Console2, EventEmitter);
/**
* Console2.prototype.time
*
* Log the time
*/
Console2.prototype.time = function () {
this._time = true;
return this;
};
/**
2014-10-25 17:13:44 +02:00
* Console2.prototype.date
2014-10-24 15:33:39 +02:00
*
2014-10-25 17:13:44 +02:00
* Log the date
2014-10-24 15:33:39 +02:00
*/
2014-10-25 17:13:44 +02:00
Console2.prototype.date = function () {
this._date = true;
2014-10-24 15:33:39 +02:00
return this;
};
/**
* Console2.prototype.tag
*
* Add tags
2014-10-26 14:34:59 +01:00
* @param {String|Object} tag
2014-12-19 17:56:57 +01:00
* @param {String} tag.msg The tag
* @paral {String|Array} tag.colors colors.js colors
2014-10-24 15:33:39 +02:00
*/
Console2.prototype.tag = Console2.prototype.t = function () {
2014-10-25 17:13:44 +02:00
var tags = Array.prototype.slice.call(arguments, 0);
this._tags = this._tags.concat(tags);
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
return this;
};
/**
* Console2.prototype.file
*
* Log the file name + line
*/
Console2.prototype.file = Console2.prototype.f = function () {
this._location = true;
2014-10-24 15:33:39 +02:00
return this;
};
/**
* Console2.prototype.buildArgs
*
* Build the args string
* ie. the string composed with the arguments send to the logger
*
* @param {Object} log
* @return {String} the string
*/
2014-12-21 21:13:27 -06:00
Console2.prototype.buildArgs = function (log, offset, context) {
2014-12-21 21:13:27 -06:00
var args = Array.prototype.slice.call(log.args, 0), // transform args in an array
msg = "", // the log message; with newline
raw = ""; // no-newlines
2014-12-21 21:13:27 -06:00
// if all args are string or number, format args as usual
if (areAllStringOrNumber(args)) {
2014-12-21 21:13:27 -06:00
raw = util.format.apply(util, args);
msg = getSpaces(offset) + raw;
2014-12-21 21:13:27 -06:00
// if objects or array present
2014-12-20 10:09:14 +01:00
} else {
2014-12-21 21:13:27 -06:00
var delimiter = '',
multiLines = false; // if the log need multiples lines (ie. object, array)
2014-12-21 21:13:27 -06:00
// Process arg one by one
args.forEach(function (arg, index) {
2014-12-20 10:09:14 +01:00
2014-12-21 21:13:27 -06:00
// if arg is an object / array
// use multiples lines
if (arg !== null && typeof arg === 'object') {
2014-12-20 10:09:14 +01:00
2014-12-21 21:13:27 -06:00
msg += delimiter + JSON.stringify(arg, null, 2);
multiLines = true;
2014-12-20 10:09:14 +01:00
2014-12-21 21:13:27 -06:00
// for "normal" args
} else {
2014-12-21 21:13:27 -06:00
if (multiLines) {
msg += delimiter;
}
2014-12-21 21:13:27 -06:00
2014-12-23 13:02:38 -06:00
msg += (arg + "");
2014-12-21 21:13:27 -06:00
}
if(index === 0){
delimiter = '\n';
}
});
2014-12-20 10:09:14 +01:00
2014-12-21 21:16:46 -06:00
raw += msg;
2014-12-21 21:13:27 -06:00
msg = getSpaces(offset) + msg;
if (multiLines) {
2014-12-21 21:13:27 -06:00
msg = msg.replace(/\n/gm, '\n' + context + getSpaces(offset));
}
}
2014-12-21 21:13:27 -06:00
return {
msg : msg,
raw : raw
};
};
2014-10-24 15:33:39 +02:00
/**
* Console2.prototype.buildContext
*
* Build the context string
* ie. the string composed with arguments passed to Console2 context functions
2014-10-24 15:33:39 +02:00
*
* @param {Object} log The log object
*
* @param {Object} opt Optional options telling what to include in the message
* @param {Boolean} opt.tags Print Tags ? Default false.
* @param {Boolean} opt.location Print location ? Default false.
* @param {Boolean} opt.time Print time ? Default false.
* @param {Boolean} opt.date Print date ? Default false.
*
2014-12-20 10:09:14 +01:00
* @return {Object}
* @return {String} result
* @return {Int} length the "human readable" length of the result
2014-10-24 15:33:39 +02:00
*/
Console2.prototype.buildContext = function (log, opt) {
2014-10-24 15:33:39 +02:00
if (!opt) {
opt = {};
}
2014-10-26 12:11:01 +01:00
opt.tags = opt.tags === true;
opt.location = opt.location === true;
opt.time = opt.time === true;
opt.date = opt.date === true;
2014-10-24 15:33:39 +02:00
2014-12-21 21:13:27 -06:00
var result = "", // final output
space = " ", // space between context parts
length = 0; // length of the context part (human readable string)
// ie. without escapted or colors caracters
2014-10-24 15:33:39 +02:00
if (opt.tags && log.context.tags) {
var tags = buildTags(log.context.tags);
result += applyColors(tags.msg, log.opt.tagsColors) + space;
length += tags.msgLength + space.length;
2014-10-24 15:33:39 +02:00
}
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
if (opt.location && log.context.location.filename && log.context.location.line) {
var infos = buildFileInfos(log.context.location, log.opt.fileColors, log.opt.lineColors);
result += infos.msg + space;
length += infos.msgLength + space.length;
2014-10-26 12:18:41 +01:00
}
2014-12-20 10:09:14 +01:00
2014-10-26 12:18:41 +01:00
if (opt.time && log.context.time) {
var time = buildTime(log.context.time);
result += applyColors(time.msg, log.opt.timeColors) + space;
length += time.msgLength + space.length;
2014-10-26 12:18:41 +01:00
}
if (opt.date && log.context.time) {
var date = buildDate(log.context.time);
result += applyColors(date.msg, log.opt.dateColors) + space;
length += date.msgLength + space.length;
2014-10-24 15:33:39 +02:00
}
return {
result : result,
length : length
};
2014-10-24 15:33:39 +02:00
};
/**
* Console2.prototype.addLogger
*
* Create a new logger
* You can then use it with console.myNewLogger
*
* @param {String} name The name of the logger.
* @param {Array|String} colors Optional colorsjs colors of the console output.
* Override constructor default.
2014-12-21 21:13:27 -06:00
* See text colors from https:// github.com/Marak/colors.js
*
* @param {Object} opt Optional options object. @see Console2 opt for default values.
*
* @param {Array|String} opt.tagsColors Default colors output for tags. Default undefined.
* @param {Array|String} opt.timeColors Default colors output for time. Default undefined.
* @param {Array|String} opt.dateColors Default colors output for date. Default undefined.
* @param {Array|String} opt.fileColors Default colors output for filename. Default undefined.
* @param {Array|String} opt.lineColors Default colors output for line number. Default undefined.
*
2014-12-20 10:09:14 +01:00
* @param {Boolean} opt.logInConsole Should the logger print to the console ?
* @param {Boolean} opt.alwaysTags Always print tags (even without tag() )
* @param {Boolean} opt.alwaysLocation Always print location (even without file() )
* @param {Boolean} opt.alwaysTime Always print time (even without time() )
* @param {Boolean} opt.alwaysDate Always print date (even without date() )
2014-10-24 15:33:39 +02:00
*/
2014-10-26 09:09:26 +01:00
Console2.prototype.addLogger = function (name, colors, opt) {
2014-10-24 15:33:39 +02:00
if (!opt) {
opt = {};
}
2014-12-20 10:09:14 +01:00
if (!name) {
throw new Error("No name given at addLogger");
}
2014-10-24 15:33:39 +02:00
opt.name = name;
opt.type = opt.type || opt.name;
2014-12-20 10:09:14 +01:00
opt.colors = colors || this.opt.colors;
opt.tagsColors = opt.tagsColors || this.opt.tagsColors;
opt.timeColors = opt.timeColors || this.opt.timeColors;
opt.dateColors = opt.dateColors || this.opt.dateColors;
opt.fileColors = opt.fileColors || this.opt.fileColors;
opt.lineColors = opt.lineColors || this.opt.lineColors;
opt.logInConsole = opt.logInConsole || this.opt.logInConsole;
opt.alwaysTags = opt.alwaysTags || this.opt.alwaysTags;
opt.alwaysLocation = opt.alwaysLocation || this.opt.alwaysLocation;
opt.alwaysTime = opt.alwaysTime || this.opt.alwaysTime;
opt.alwaysDate = opt.alwaysDate || this.opt.alwaysDate;
2014-10-24 15:33:39 +02:00
2014-12-21 09:21:40 +01:00
/**
* this.[name]
*
* @params anything you want, printf formar, etc.
* @return {Console2} this
*/
2014-10-24 15:33:39 +02:00
this[name] = function () {
var location = getLocation();
var time = Date.now();
2014-12-21 21:13:27 -06:00
// Let's build the log object
2014-10-24 15:33:39 +02:00
var log = {
type : opt.type || name,
2014-11-03 21:49:10 +01:00
show : {
tags : this._tags.length > 0 || this.opt.alwaysTags,
location : this._location || this.opt.alwaysLocation,
time : this._time || this.opt.alwaysTime,
date : this._date || this.opt.alwaysDate
},
2014-10-24 15:33:39 +02:00
context : {
tags : this._tags,
file : this._location,
time : time,
location : location
2014-10-24 15:33:39 +02:00
},
args : arguments,
opt : opt
};
2014-12-21 21:13:27 -06:00
// Build the context string
2014-11-03 21:49:10 +01:00
var context = this.buildContext(log, log.show);
2014-10-24 15:33:39 +02:00
2014-12-21 21:13:27 -06:00
// Generate the according number of space between context and args strings
// add space according to the contextMediumSize
var offset = Math.max(0, this.opt.contextMediumSize - context.length);
2014-12-20 10:09:14 +01:00
2014-12-21 21:13:27 -06:00
// save context
log.contextString = context.result;
2014-12-21 21:13:27 -06:00
// Build the args string
var built = this.buildArgs(log, offset, log.contextString);
2014-12-20 10:09:14 +01:00
2014-12-21 21:13:27 -06:00
// newline enabled
2014-12-21 21:16:46 -06:00
log.argsString = built.raw;
2014-12-21 21:13:27 -06:00
// Finally, the message
log.message = log.contextString + log.argsString;
2014-12-21 21:13:27 -06:00
// Emit events
this.emit('new', log, log.type); // 'new' event
2014-12-18 22:10:14 +01:00
2014-12-20 10:09:14 +01:00
var msg = log.type;
2014-12-18 22:10:14 +01:00
2014-12-20 10:09:14 +01:00
/*
2014-12-18 22:10:14 +01:00
* EventEmitter.emit() will raise an Error
2014-12-20 19:35:26 +01:00
* if we emit the event 'error' and there is no listeners
2014-12-18 22:10:14 +01:00
* For now, transform 'error' in 'errorEvent'
*/
if (msg === 'error') {
2014-12-20 10:09:14 +01:00
msg += 'Event';
2014-12-18 22:10:14 +01:00
}
2014-12-21 21:13:27 -06:00
this.emit(msg, log); // `log.type` event
2014-12-20 10:09:14 +01:00
2014-12-21 21:13:27 -06:00
// If the logger should print the message
// Print it
2014-10-24 15:33:39 +02:00
if (opt.logInConsole) {
2014-12-21 21:16:46 -06:00
global.console.log(applyColors(log.contextString + built.msg, opt.colors));
2014-10-24 15:33:39 +02:00
}
this._reset();
2014-12-21 09:15:28 +01:00
return this;
2014-10-24 15:33:39 +02:00
};
this.loggers[name] = opt;
2014-10-26 20:58:00 +01:00
this.emit('newLogger', name, opt);
2014-10-24 15:33:39 +02:00
};
2014-12-21 21:13:27 -06:00
// Keep the old console
// use also `global.console`
2014-10-24 15:33:39 +02:00
Console2.prototype.Original = consoleOriginal;
2014-12-20 10:09:14 +01:00
2014-10-24 15:33:39 +02:00
module.exports = Console2;
}());