Merge pull request #305 from hybridgroup/refactor/logger

Simplify logger
This commit is contained in:
Ron Evans 2015-08-31 21:19:22 -07:00
commit bea67b2e5e
11 changed files with 116 additions and 275 deletions

View File

@ -38,7 +38,7 @@ api.create = function create(Server, opts) {
[
"Cannot find the " + req + " API module.",
"You may be able to install it: `npm install " + req + "`"
].forEach(_.arity(Logger.fatal, 1));
].forEach(Logger.log);
throw new Error("Missing API plugin - cannot proceed");
}

View File

@ -6,9 +6,11 @@ var config = module.exports = {},
callbacks = [];
// default data
config.logging = {};
config.haltTimeout = 3000;
config.testMode = false;
config.logger = null;
config.silent = false;
config.debug = false;
/**
* Updates the Config, and triggers handler callbacks

View File

@ -1,44 +1,82 @@
"use strict";
var levels = ["debug", "info", "warn", "error", "fatal"];
/* eslint no-use-before-define: 0 */
var BasicLogger = require("./logger/basic_logger"),
NullLogger = require("./logger/null_logger"),
Config = require("./config"),
var Config = require("./config"),
_ = require("./utils/helpers");
var BasicLogger = function basiclogger(str) {
var prefix = new Date().toISOString() + " : ";
console.log(prefix + str);
};
var NullLogger = function nulllogger() {};
var Logger = module.exports = {
setup: function(opts) {
if (_.isObject(opts)) { _.extend(Config.logging, opts); }
setup: setup,
var logger = Config.logging.logger,
level = Config.logging.level || "info";
// --debug CLI flag overrides any other option
if (_.includes(process.argv, "--debug")) {
level = "debug";
}
logger = (logger == null) ? BasicLogger : logger;
Logger.logger = logger || NullLogger;
Logger.level = level;
return Logger;
should: {
log: true,
debug: false
},
toString: function() {
return Logger.logger.toString();
log: function log(str) {
if (Logger.should.log) {
Logger.logger.call(Logger.logger, str);
}
},
debug: function debug(str) {
if (Logger.should.log && Logger.should.debug) {
Logger.logger.call(Logger.logger, str);
}
}
};
Logger.setup();
Config.subscribe(Logger.setup);
function setup(opts) {
if (_.isObject(opts)) { _.extend(Config, opts); }
levels.forEach(function(level) {
Logger[level] = function() {
if (levels.indexOf(level) >= levels.indexOf(Logger.level)) {
return Logger.logger[level].apply(Logger.logger, arguments);
var logger = Config.logger;
// if no logger supplied, use basic logger
if (logger == null) { logger = BasicLogger; }
// if logger is still falsy, use NullLogger
Logger.logger = logger || NullLogger;
Logger.should.log = !Config.silent;
Logger.should.debug = Config.debug;
// --silent CLI flag overrides
if (_.includes(process.argv, "--silent")) {
Logger.should.log = false;
}
// --debug CLI flag overrides
if (_.includes(process.argv, "--debug")) {
Logger.should.debug = false;
}
return Logger;
}
setup();
Config.subscribe(setup);
// deprecated holdovers
["info", "warn", "error", "fatal"].forEach(function(method) {
var called = false;
function showDeprecationNotice() {
console.log("The method Logger#" + method + " has been deprecated.");
console.log("It will be removed in Cylon 2.0.0.");
console.log("Please switch to using the #log or #debug Logger methods");
called = true;
}
Logger[method] = function() {
if (!called) { showDeprecationNotice(); }
Logger.log.apply(null, arguments);
};
});

View File

@ -1,25 +0,0 @@
"use strict";
var getArgs = function(args) {
return args.length >= 1 ? [].slice.call(args, 0) : [];
};
var logString = function(type) {
var time = new Date().toISOString(),
upcase = String(type).toUpperCase(),
padded = String(" " + upcase).slice(-5);
return upcase[0] + ", [" + time + "] " + padded + " -- :";
};
// The BasicLogger logs to console.log
var BasicLogger = module.exports = {
toString: function() { return "BasicLogger"; },
};
["debug", "info", "warn", "error", "fatal"].forEach(function(type) {
BasicLogger[type] = function() {
var args = getArgs(arguments);
return console.log.apply(console, [].concat(logString(type), args));
};
});

View File

@ -1,11 +0,0 @@
"use strict";
// The NullLogger is designed for cases where you want absolutely nothing to
// print to anywhere. Every proxied method from the Logger returns a noop.
var NullLogger = module.exports = {
toString: function() { return "NullLogger"; }
};
["debug", "info", "warn", "error", "fatal"].forEach(function(type) {
NullLogger[type] = function() {};
});

View File

@ -31,7 +31,7 @@ mcp.create = function create(opts) {
var str = "Robot names must be unique. Renaming '";
str += original + "' to '" + opts.name + "'";
Logger.warn(str);
Logger.log(str);
}
var bot = new Robot(opts);

View File

@ -118,7 +118,7 @@ Robot.prototype.connection = function(name, conn) {
str = "Connection names must be unique.";
str += "Renaming '" + original + "' to '" + conn.name + "'";
this.log("warn", str);
this.log(str);
}
this.connections[conn.name] = initializer("adaptor", conn);
@ -144,7 +144,7 @@ Robot.prototype.initRobot = function(opts) {
this.commands = {};
if (!this.work) {
this.work = function() { this.log("debug", "No work yet."); };
this.work = function() { this.log("No work yet."); };
}
_.each(opts.connections, function(conn, key) {
@ -189,13 +189,13 @@ Robot.prototype.device = function(name, device) {
str = "Device names must be unique.";
str += "Renaming '" + original + "' to '" + device.name + "'";
this.log("warn", str);
this.log(str);
}
if (_.isString(device.connection)) {
if (this.connections[device.connection] == null) {
str = "No connection found with the name " + device.connection + ".\n";
this.log("fatal", str);
this.log(str);
process.emit("SIGINT");
}
@ -238,8 +238,8 @@ Robot.prototype.start = function(callback) {
start
], function(err, results) {
if (err) {
this.log("fatal", "An error occured while trying to start the robot:");
this.log("fatal", err);
this.log("An error occured while trying to start the robot:");
this.log(err);
this.halt(function() {
if (_.isFunction(this.error)) {
@ -266,7 +266,7 @@ Robot.prototype.start = function(callback) {
* @return {void}
*/
Robot.prototype.startWork = function() {
this.log("info", "Working.");
this.log("Working.");
this.emit("ready", this);
this.work.call(this, this);
@ -281,7 +281,7 @@ Robot.prototype.startWork = function() {
* @return {void}
*/
Robot.prototype.startConnections = function(callback) {
this.log("info", "Starting connections.");
this.log("Starting connections.");
var starters = _.map(this.connections, function(conn, name) {
this[name] = conn;
@ -295,7 +295,7 @@ Robot.prototype.startConnections = function(callback) {
str += " on port " + conn.port;
}
this.log("debug", str + ".");
this.log(str + ".");
return conn.connect.call(conn, cb);
}.bind(this);
}, this);
@ -313,7 +313,7 @@ Robot.prototype.startConnections = function(callback) {
Robot.prototype.startDevices = function(callback) {
var log = this.log;
log("info", "Starting devices.");
log("Starting devices.");
var starters = _.map(this.devices, function(device, name) {
this[name] = device;
@ -325,7 +325,7 @@ Robot.prototype.startDevices = function(callback) {
str += " on pin " + device.pin;
}
log("debug", str + ".");
log(str + ".");
return device.start.call(device, cb);
};
}, this);
@ -360,8 +360,8 @@ Robot.prototype.halt = function(callback) {
});
} catch (e) {
var msg = "An error occured while attempting to safely halt the robot";
this.log("error", msg);
this.log("error", e.message);
this.log(msg);
this.log(e.message);
}
this.running = false;
@ -376,8 +376,6 @@ Robot.prototype.toString = function() {
return "[Robot name='" + this.name + "']";
};
Robot.prototype.log = function(level) {
var args = Array.prototype.slice.call(arguments, 1);
args.unshift("[" + this.name + "] -");
Logger[level].apply(null, args);
Robot.prototype.log = function(str) {
Logger.log("[" + this.name + "] - " + str);
};

View File

@ -17,12 +17,12 @@ function die() {
function warn(messages) {
messages = [].concat(messages);
messages.map(function(msg) { Logger.warn(msg); });
messages.map(function(msg) { Logger.log(msg); });
}
function fatal(messages) {
messages = [].concat(messages);
messages.map(function(msg) { Logger.fatal(msg); });
messages.map(function(msg) { Logger.log(msg); });
die();
}

View File

@ -1,48 +1,38 @@
"use strict";
var Logger = lib("logger"),
Config = lib("config"),
NullLogger = lib("logger/null_logger");
Config = lib("config");
describe("Logger", function() {
afterEach(function() {
// to be friendly to other specs
Config.logging = { logger: false, level: "debug" };
Config.logger = false;
Logger.setup();
});
describe("#setup", function() {
context("with no arguments", function() {
it("sets up a BasicLogger", function() {
Config.logging = {};
Config.logger = null;
Logger.setup();
expect(Logger.toString()).to.be.eql("BasicLogger");
expect(Logger.logger.name).to.be.eql("basiclogger");
});
});
context("with false", function() {
it("sets up a NullLogger", function() {
Config.logging = { logger: false };
Config.logger = false;
Logger.setup();
expect(Logger.toString()).to.be.eql("NullLogger");
expect(Logger.logger.name).to.be.eql("nulllogger");
});
});
context("with a custom logger", function() {
it("uses the custom logger", function() {
var logger = { toString: function() { return "custom"; } };
Config.logging = { logger: logger };
function customlogger() {}
Config.logger = customlogger;
Logger.setup();
expect(Logger.toString()).to.be.eql("custom");
});
});
context("with a custom logger, provided directly", function() {
it("uses the custom logger", function() {
var logger = { toString: function() { return "custom"; } };
Logger.setup({ logger: logger });
expect(Logger.toString()).to.be.eql("custom");
expect(Config.logging.logger).to.be.eql(logger);
expect(Logger.logger.name).to.be.eql("customlogger");
});
});
});
@ -51,107 +41,31 @@ describe("Logger", function() {
var logger;
beforeEach(function() {
logger = Config.logging.logger = {
debug: spy(),
info: spy(),
warn: spy(),
error: spy(),
fatal: spy()
};
Logger.setup();
logger = spy();
Logger.logger = logger;
});
describe("#debug", function() {
it("proxies to the Logger's #debug method", function() {
Logger.debug("Hello", "World");
expect(logger.debug).to.be.calledWith("Hello", "World");
});
});
describe("#info", function() {
it("proxies to the Logger's #info method", function() {
Logger.info("Hello", "World");
expect(logger.info).to.be.calledWith("Hello", "World");
});
});
describe("#warn", function() {
it("proxies to the Logger's #warn method", function() {
Logger.warn("Hello", "World");
expect(logger.warn).to.be.calledWith("Hello", "World");
});
});
describe("#error", function() {
it("proxies to the Logger's #error method", function() {
Logger.error("Hello", "World");
expect(logger.error).to.be.calledWith("Hello", "World");
});
});
describe("#fatal", function() {
it("proxies to the Logger's #fatal method", function() {
Logger.fatal("Hello", "World");
expect(logger.fatal).to.be.calledWith("Hello", "World");
});
});
});
describe("log levels", function() {
var logger;
beforeEach(function() {
logger = {
debug: spy(),
info: spy(),
warn: spy(),
error: spy(),
fatal: spy()
};
Config.logging = {
logger: logger,
level: "warn"
};
Logger.setup();
});
it("prevents logging below the specified level", function() {
it("proxies to the logger method", function() {
Logger.should.debug = true;
Logger.debug("debug message");
Logger.info("info message");
expect(logger.debug).to.not.be.called;
expect(logger.info).to.not.be.called;
Logger.should.debug = false;
expect(logger).to.be.calledWith("debug message");
});
});
it("still logs levels equal/greater than the specified level", function() {
Logger.warn("warn message");
Logger.error("error message");
Logger.fatal("fatal message");
expect(logger.warn).to.be.calledWith("warn message");
expect(logger.error).to.be.calledWith("error message");
expect(logger.fatal).to.be.calledWith("fatal message");
describe("#log", function() {
it("proxies to the logger method", function() {
Logger.log("log message");
expect(logger).to.be.calledWith("log message");
});
it("defaults to 'info' level", function() {
delete Config.logging.level;
Logger.setup();
Logger.debug("debug message");
Logger.info("info message");
expect(logger.debug).to.not.be.called;
expect(logger.info).to.be.calledWith("info message");
});
});
it("automatically updates if configuration changed", function() {
var custom = spy();
expect(Logger.logger).to.be.eql(NullLogger);
Config.update({ logging: { logger: custom } });
expect(Logger.logger.name).to.be.eql("basiclogger");
Config.update({ logger: custom });
expect(Logger.logger).to.be.eql(custom);
});
});

View File

@ -1,70 +0,0 @@
"use strict";
var logger = lib("logger/basic_logger");
var date = new Date(0).toISOString();
describe("BasicLogger", function() {
var clock;
beforeEach(function() {
stub(console, "log");
clock = sinon.useFakeTimers(0);
});
afterEach(function() {
console.log.restore();
clock.restore();
});
describe("#toString", function() {
it("returns 'BasicLogger'", function() {
expect(logger.toString()).to.be.eql("BasicLogger");
});
});
describe("#debug", function() {
it("logs to the console with a debug string", function() {
var logstring = "D, [" + date + "] DEBUG -- :";
logger.debug("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#info", function() {
it("logs to the console with a info string", function() {
var logstring = "I, [" + date + "] INFO -- :";
logger.info("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#warn", function() {
it("logs to the console with a warn string", function() {
var logstring = "W, [" + date + "] WARN -- :";
logger.warn("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#error", function() {
it("logs to the console with a error string", function() {
var logstring = "E, [" + date + "] ERROR -- :";
logger.error("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
describe("#fatal", function() {
it("logs to the console with a fatal string", function() {
var logstring = "F, [" + date + "] FATAL -- :";
logger.fatal("Hello, World");
expect(console.log).to.be.calledWith(logstring, "Hello, World");
});
});
});

View File

@ -515,22 +515,17 @@ describe("Robot", function() {
describe("#log", function() {
beforeEach(function() {
stub(Logger, "info");
stub(Logger, "fatal");
robot.log("info", "an informative message");
robot.log("fatal", "a fatal error");
stub(Logger, "log");
robot.log("an informative message");
});
afterEach(function() {
Logger.info.restore();
Logger.fatal.restore();
Logger.log.restore();
});
it("it passes messages onto Logger, with the Robot's name", function() {
var nameStr = "[" + robot.name + "] - ";
expect(Logger.info).to.be.calledWith(nameStr, "an informative message");
expect(Logger.fatal).to.be.calledWith(nameStr, "a fatal error");
expect(Logger.log).to.be.calledWith(nameStr + "an informative message");
});
});
});