appc-logger
Version:
Appcelerator Logger
853 lines (798 loc) • 23.7 kB
JavaScript
// jscs:disable jsDoc
// jshint -W079
/* eslint no-unused-expressions: "off" */
/* eslint quote-props: "off" */
;
const should = require('should'),
util = require('util'),
ConsoleClass = require('./_console'),
_console = new ConsoleClass(),
index = require('../'),
debug = require('debug')('appc:logger'),
chalk = require('chalk'),
ConsoleLogger = index.ConsoleLogger,
defaultTravis = process.env.TRAVIS,
defaultArgs = process.argv;
/**
* Adds a regular expression that will match the timestamp added when the log level is trace or debug.
* @returns {RegExp}
*/
String.prototype.withTimestampPrefix = function () {
const str = this;
/**
* Expects a string to have a time prefix followed by a particular value.
* @param {String} val value
* @returns {Boolean}
*/
return function withTimestampPrefix(val) {
const match = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z \| /);
if (!match) {
throw new Error('Needs timestamp prefix. Got: ' + val);
}
if (val.indexOf(str) === -1) {
throw new Error('Does not match str. Got: ' + val);
}
return true;
};
};
/**
* Adds a regular expression that will match and fail the timestamp added when the log level is trace or debug.
* @returns {RegExp}
*/
String.prototype.withoutTimestampPrefix = function () {
const str = this;
/**
* Expects a string to have a time prefix followed by a particular value.
* @param {String} val value
* @returns {Boolean}
*/
return function withoutTimestampPrefix(val) {
const match = val.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z \| /);
if (match) {
throw new Error('Should not have timestamp. Got: ' + val);
}
if (val.indexOf(str) === -1) {
throw new Error('Does not match str. Got: ' + val);
}
return true;
};
};
describe('console', function () {
before(function () {
ConsoleLogger.resetColorize();
});
after(function () {
_console.stop();
ConsoleLogger.resetColorize();
process.env.TRAVIS = defaultTravis;
process.argv = defaultArgs;
});
it('should be able to log at info', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('INFO | hello');
callback();
});
const logger = index.createDefaultLogger();
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.setLevel('info');
logger.info('hello');
} finally {
_console.stop();
}
});
it('should be able to set level', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('INFO | hello');
callback();
});
const logger = index.createDefaultLogger();
should(logger).be.an.object;
should(logger.info).be.a.function;
should(logger.level).be.a.function;
should(logger.setLevel).be.a.function;
logger.setLevel('info');
logger.debug('goodbye');
logger.info('hello');
} finally {
_console.stop();
}
});
it('should log timestamps when the level is set to trace', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
const data = [];
_console.on('data', function (buf) {
data.push(buf);
debug(buf);
});
const logger = index.createDefaultLogger();
logger.setLevel('trace');
logger.trace('i am trace');
logger.debug('i am debug');
logger.info('i am info');
logger.warn('i am warn');
logger.error('i am error');
logger.fatal('i am fatal');
should(data[0]).match('TRACE | i am trace'.withTimestampPrefix());
should(data[1]).match('DEBUG | i am debug'.withTimestampPrefix());
should(data[2]).match('INFO | i am info'.withTimestampPrefix());
should(data[3]).match('WARN | i am warn'.withTimestampPrefix());
should(data[4]).match('ERROR | i am error'.withTimestampPrefix());
should(data[5]).match('FATAL | i am fatal'.withTimestampPrefix());
} finally {
_console.stop();
}
callback();
});
it('should not log timestamps when the level is set to info', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
const data = [];
_console.on('data', function (buf) {
data.push(String(buf));
debug(buf);
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.trace('i am trace');
logger.debug('i am debug');
should(data).have.property('length', 0);
logger.info('i am info');
logger.warn('i am warn');
logger.error('i am error');
logger.fatal('i am fatal');
logger.setLevel('debug');
logger.info('i am info');
should(data[0]).match('INFO | i am info'.withoutTimestampPrefix());
should(data[1]).match('WARN | i am warn'.withoutTimestampPrefix());
should(data[2]).match('ERROR | i am error'.withoutTimestampPrefix());
should(data[3]).match('FATAL | i am fatal'.withoutTimestampPrefix());
should(data[4]).match('INFO | i am info'.withTimestampPrefix());
} finally {
_console.stop();
}
callback();
});
it('should log timestamps when the level is set to debug', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
const data = [];
_console.on('data', function (buf) {
debug(buf);
data.push(buf);
});
const logger = index.createDefaultLogger();
logger.setLevel('debug');
logger.trace('i am trace');
should(data).have.property('length', 0);
logger.debug('i am debug');
should(data[0]).match('DEBUG | i am debug'.withTimestampPrefix());
logger.info('i am info');
should(data[1]).match('INFO | i am info'.withTimestampPrefix());
logger.warn('i am warn');
should(data[2]).match('WARN | i am warn'.withTimestampPrefix());
logger.error('i am error');
should(data[3]).match('ERROR | i am error'.withTimestampPrefix());
logger.fatal('i am fatal');
should(data[4]).match('FATAL | i am fatal'.withTimestampPrefix());
} finally {
_console.stop();
}
callback();
});
it('should be able to log at debug', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).match('DEBUG | hello'.withTimestampPrefix());
callback();
});
const logger = index.createDefaultLogger();
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.debug('hello');
} finally {
_console.stop();
}
});
it('should be able to log at trace', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).match('TRACE | hello'.withTimestampPrefix());
callback();
});
const logger = index.createDefaultLogger();
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.trace('hello');
} finally {
_console.stop();
}
});
it('should be able to log at warn', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('WARN | hello');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.warn('hello');
} finally {
_console.stop();
}
});
it('should be able to log at error', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('ERROR | hello');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.error('hello');
} finally {
_console.stop();
}
});
it('should be able to log at fatal', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('FATAL | hello');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.fatal('hello');
} finally {
_console.stop();
}
});
it('should be able to log with object but have it ignored', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('INFO | hello');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.info({ a: 1 }, 'hello');
} finally {
_console.stop();
}
});
it('should be able to log with object', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('INFO | { a: 1 }');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.info({ a: 1, prefix: 'ignored' });
} finally {
_console.stop();
}
});
it('should ignore null objects', function (callback) {
const logger = index.createDefaultLogger(),
consoleLogger = logger.streams[0].stream;
consoleLogger.write = function (record) {
should(record.obj).be.an.object;
should(record.obj).be.not.ok;
callback();
return true;
};
logger.info(null, 'hello');
});
it('should ignore functions', function (callback) {
const logger = index.createDefaultLogger(),
consoleLogger = logger.streams[0].stream;
consoleLogger.write = function (record) {
should(record.obj).be.an.object;
should(record.obj).be.not.ok;
callback();
return true;
};
logger.info(function () {
throw new Error('this should not be called');
}, 'hello');
});
it('should be able to log with empty object', function (callback) {
should(ConsoleLogger).be.an.object;
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('INFO | ');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.info({ prefix: 'ignored' });
} finally {
_console.stop();
}
});
it('should be able to use format symbols', function (callback) {
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('INFO | hello world 1');
callback();
});
const logger = index.createDefaultLogger();
logger.setLevel('info');
logger.info('hello %s %d', 'world', 1);
} finally {
_console.stop();
}
});
it('should remove color coding', function (callback) {
var console_ = new ConsoleClass(false);
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).equal('INFO | hello world 1');
callback();
});
process.env.TRAVIS = 1; // force log coloring off
ConsoleLogger.resetColorize();
const logger = index.createDefaultLogger();
logger.setLevel('info');
const chalk = require('chalk');
logger.info('hello %s %d', chalk.red('world'), 1);
} finally {
console_.stop();
}
});
it('should remove log level', function (callback) {
var console_ = new ConsoleClass(false);
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).equal('hello world');
callback();
});
const logger = index.createLogger({ prefix: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('hello world');
} finally {
console_.stop();
}
});
it('should remove carriage return marker', function (callback) {
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('hello\nworld');
callback();
});
const logger = index.createLogger({ prefix: false, showcr: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('hello\nworld');
} finally {
_console.stop();
}
});
it('should show carriage return marker', function (callback) {
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('hello↩\nworld↩');
callback();
});
const logger = index.createLogger({ prefix: false, showcr: true });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('hello\nworld');
} finally {
_console.stop();
}
});
it('should show tab marker', function (callback) {
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('hello\t↠world');
callback();
});
const logger = index.createLogger({ prefix: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('hello\tworld');
} finally {
_console.stop();
}
});
it('should remove tab marker', function (callback) {
try {
_console.start();
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('hello\tworld');
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('hello\tworld');
} finally {
_console.stop();
}
});
it('should log the record if there is no message', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal(util.format({ 'hello': 'world' }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info({ 'hello': 'world' });
} finally {
_console.stop();
}
});
it('should mask log record if only argument', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).not.equal(util.format({ 'password': '1234' }));
should(buf).equal(util.format({ 'password': '[HIDDEN]' }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info({ 'password': '1234' });
} finally {
_console.stop();
}
});
it('should mask password confirmation with dash', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).not.equal(util.format({ 'password-confirmation': '1234' }));
should(buf).equal(util.format({ 'password-confirmation': '[HIDDEN]' }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info({ 'password-confirmation': '1234' });
} finally {
_console.stop();
}
});
it('should mask password confirmation with underscore', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).not.equal(util.format({ 'password_confirmation': '1234' }));
should(buf).equal(util.format({ 'password_confirmation': '[HIDDEN]' }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info({ 'password_confirmation': '1234' });
} finally {
_console.stop();
}
});
it('should mask log arguments', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('your password is ' + util.format({ 'password': '[HIDDEN]' }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('your password is', { 'password': '1234' });
} finally {
_console.stop();
}
});
it('should mask log arguments that are nested', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('your password is ' + util.format({ foo: { 'password': '[HIDDEN]' } }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('your password is', { foo: { 'password': '1234' } });
} finally {
_console.stop();
}
});
it('should mask log arguments that are nested as 3rd arg', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('your password is ' + util.format({}) + ' ' + util.format({ foo: { 'password': '[HIDDEN]' } }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('your password is', {}, { foo: { 'password': '1234' } });
} finally {
_console.stop();
}
});
it('should mask log arguments using format', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal(util.format('your password is %j', { 'password': '[HIDDEN]' }));
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('your password is %j', { 'password': '1234' });
} finally {
_console.stop();
}
});
it('should handle circular references', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('nested object is { key: { key: \'[Circular]\' } }');
callback();
});
const obj = {};
obj.key = obj;
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('nested object is', obj);
} finally {
_console.stop();
}
});
it('should handle buffer references', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('buffer is [Buffer]');
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('buffer is', new Buffer('hello'));
} finally {
_console.stop();
}
});
it('should handle RegExp references', function (callback) {
try {
this.timeout(1000);
_console.start(1000);
_console.on('data', function (buf) {
_console.stop();
should(buf).equal('buffer is /^foo$/');
callback();
});
const logger = index.createLogger({ prefix: false, showtab: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('buffer is', /^foo$/);
} finally {
_console.stop();
}
});
it.skip('should color code if colorize is specified', function (callback) { // eslint-disable-line mocha/no-skipped-tests
var console_ = new ConsoleClass(false);
try {
chalk.enabled = true;
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).equal('\u001b[32mINFO \u001b[39m \u001b[1m\u001b[90m|\u001b[39m\u001b[22m hello \u001b[31mworld\u001b[39m 1');
callback();
});
const logger = index.createDefaultLogger({ colorize: true, problemLogger: false });
should(logger).be.an.object;
should(logger.info).be.a.function;
logger.info('hello %s %d', chalk.red('world'), 1);
} finally {
console_.stop();
}
});
it('should not color code if colorize is specified as false', function (callback) {
var console_ = new ConsoleClass(false);
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).equal('INFO | hello world 1');
callback();
});
const logger = index.createDefaultLogger({ colorize: false });
logger.setLevel('info');
const chalk = require('chalk');
logger.info('hello %s %d', chalk.red('world'), 1);
} finally {
console_.stop();
}
});
it('should not color code if --no-colors is specified', function (callback) {
var console_ = new ConsoleClass(false);
var args = process.argv;
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).equal('INFO | hello world 1');
callback();
});
process.argv = [ 'node', '--no-colors' ];
ConsoleLogger.resetColorize();
const logger = index.createDefaultLogger();
logger.setLevel('info');
const chalk = require('chalk');
logger.info('hello %s %d', chalk.red('world'), 1);
} finally {
console_.stop();
process.argv = args;
}
});
it('should not color code if --no-color is specified', function (callback) {
var console_ = new ConsoleClass(false);
var args = process.argv;
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).equal('INFO | hello world 1');
callback();
});
process.argv = [ 'node', '--no-color' ];
ConsoleLogger.resetColorize();
const logger = index.createDefaultLogger();
logger.setLevel('info');
const chalk = require('chalk');
logger.info('hello %s %d', chalk.red('world'), 1);
} finally {
console_.stop();
process.argv = args;
}
});
[ '--colorize', '--color', '--colors' ].forEach(function (flag) {
it('should color code if ' + flag + ' is specified', function (callback) {
var console_ = new ConsoleClass(false);
var args = process.argv;
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).containEql('hello');
should(buf).containEql('\u001b');
callback();
});
process.argv = [ 'node', flag ];
ConsoleLogger.resetColorize();
const logger = index.createDefaultLogger();
logger.setLevel('info');
const chalk = require('chalk');
logger.info('hello\t%s\n%d', chalk.red('world'), 1);
} finally {
console_.stop();
process.argv = args;
}
});
});
it('should logPrepend', function (callback) {
var console_ = new ConsoleClass(false);
try {
console_.start();
console_.on('data', function (buf) {
console_.stop();
should(buf).containEql('Find This!');
callback();
});
const logger = index.createDefaultLogger({
colorize: false,
logPrepend: 'Find This!'
});
ConsoleLogger.resetColorize();
logger.setLevel('info');
const chalk = require('chalk');
logger.info('hello %s %d', chalk.red('world'), 1);
} finally {
console_.stop();
}
});
it('should prepend pid for cluster worker logs', function (callback) {
const spawn = require('child_process').spawn; // eslint-disable-line security/detect-child-process
const path = require('path');
const child = spawn(process.execPath, [ path.join(__dirname, '_cluster.js') ], { cwd: __dirname });
let output;
child.stdout.on('data', function (buf) {
output = String(buf);
});
child.stderr.on('data', function (buf) {
console.log(String(buf));
});
child.on('exit', function (err) {
if (err !== 0) {
callback(new Error('exit code ' + err));
} else {
should(output).match(/INFO\s|\s(\d+)\s|\shello/);
callback();
}
});
});
});