From 07df799104b3be7422869e338adf1ddde7a55f12 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:38:53 -0400 Subject: [PATCH 01/10] create logger with log level threshold --- logger.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 logger.js diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..d9dfd82 --- /dev/null +++ b/logger.js @@ -0,0 +1,55 @@ +var colors = require('colors'); +var config = require('./config'); + +var NONE = 'NONE'; +var CRITICAL = 'CRITICAL'; +var ERROR = 'ERROR'; +var WARN = 'WARN'; +var LOG = 'LOG'; +var INFO = 'INFO'; +var DEBUG = 'DEBUG'; +var ALL = 'ALL'; + +var levels = [NONE, CRITICAL, ERROR, WARN, INFO, LOG, DEBUG, ALL]; + +function canLog(level) { + var threshold = config.logLevel; + if (threshold === undefined) threshold = LOG; + var thresholdIndex = levels.indexOf(threshold); + var levelIndex = levels.indexOf(level); + return levelIndex <= thresholdIndex; +} + +function log(method, level, color, message) { + if (canLog(level)) { + console[method](color(message)); + } +} + +function checkLevel(level) { + if (!levels.includes(level)) { + throw new Error(`Invalid log level '${level}'. Acceptable values are ${levels.join(', ')}`); + } +} + +function checkLogger(customLogger) { + var keys = ['critical', 'error', 'warn', 'info', 'log', 'debug']; + keys.forEach(function (key) { + if (typeof customLogger[key] !== 'function') { + throw new Error(`Custom Logger '${key}' is not a function. Expected functions: ${keys.join(', ')}`); + } + }); +} + +var logger = { + checkLevel, + checkLogger, + critical: log.bind(null, 'error', CRITICAL, colors.red), + error: log.bind(null, 'error', ERROR, colors.red), + warn: log.bind(null, 'warn', WARN, colors.yellow), + info: log.bind(null, 'info', INFO, colors.blue), + log: log.bind(null, 'log', LOG, colors.white), + debug: log.bind(null, 'debug', DEBUG, colors.grey) +} + +module.exports = logger; \ No newline at end of file From aa9f6a3eafa633f7255a5c076783d6d16df91b81 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:39:30 -0400 Subject: [PATCH 02/10] config stores logger and log level --- config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 3dea0db..0ccbc2c 100644 --- a/config.js +++ b/config.js @@ -1,4 +1,6 @@ module.exports = { 'table' : 'mysql_migrations_347ertt3e', - 'migrations_types' : ['up', 'down'] + 'migrations_types': ['up', 'down'], + logger: console, + logLevel: 'ALL' }; From 9aa6fb73f5a28dcfc9d04cfe4a62a348b388d653 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:40:12 -0400 Subject: [PATCH 03/10] assign logger and log level based on init options --- index.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/index.js b/index.js index 71e1195..90c66e4 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var queryFunctions = require('./query'); var config = require('./config'); var table = config['table']; var migrations_types = config['migrations_types']; +var logger = require('./logger'); var updateSchema = false; var migrate_all = false; @@ -32,6 +33,23 @@ function migration(conn, path, cb, options) { if (options.indexOf("--update-schema") > -1) { updateSchema = true; } + + var loggerIndex = options.findIndex(option => option === '--logger'); + if (loggerIndex !== -1) { + var customLogger = options[loggerIndex + 1]; + logger.checkLogger(customLogger); + config.logger = customLogger; + } else { + config.logger = logger; + } + + options + .filter(option => typeof option === "string" && option.startsWith('--log-level ')) + .forEach(option => { + var level = option.replace('--log-level ', '').toUpperCase(); + logger.checkLevel(level); + config.logLevel = level; + }); } queryFunctions.run_query(conn, "CREATE TABLE IF NOT EXISTS `" + table + "` (`timestamp` varchar(254) NOT NULL UNIQUE)", function (res) { From 72eb960005f7db11fba3fc694f86b0fdf97e69ff Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:40:28 -0400 Subject: [PATCH 04/10] don't hang if no parameters specified --- index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.js b/index.js index 90c66e4..3d7337f 100644 --- a/index.js +++ b/index.js @@ -112,6 +112,8 @@ function handle(argv, conn, path, cb) { else { throw new Error('command not found : ' + argv.join(" ")); } + } else { + cb(); } } From 1402473a933d05a0921e3bb076b27ac6717f1b6d Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:40:38 -0400 Subject: [PATCH 05/10] test logger --- test/logger.js | 341 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 test/logger.js diff --git a/test/logger.js b/test/logger.js new file mode 100644 index 0000000..cae155b --- /dev/null +++ b/test/logger.js @@ -0,0 +1,341 @@ +var chai = require('chai'); + +var testCommons = require('./test_commons'); +var assert = require('assert'); +var logger = require('../logger'); +var config = require('../config'); + +var should = chai.should(); + +var originalLog = console.log; +var originalError = console.error; +var originalInfo = console.info; +var originalDebug = console.debug; +var originalWarn = console.warn; +var logs = { + log: [], + error: [], + info: [], + debug: [], + warn: [] +}; + +describe('logger.js', function () { + before(function (done) { + // mock console log methods + console.log = function (message) { logs.log.push(message); }; + console.error = function (message) { logs.error.push(message); } + console.info = function (message) { logs.info.push(message); } + console.debug = function (message) { logs.debug.push(message); } + console.warn = function (message) { logs.warn.push(message); } + done(); + }); + beforeEach(function (done) { + config.logger = logger; + config.logLevel = 'all'; + logs = { + log: [], + error: [], + info: [], + debug: [], + warn: [] + }; + done(); + }); + after(function (done) { + console.log = originalLog.bind(console); + console.error = originalError.bind(console); + console.info = originalInfo.bind(console); + console.debug = originalDebug.bind(console); + console.warn = originalWarn.bind(console); + done(); + }) + + context('threshold=ALL', function () { + beforeEach(function () { + config.logLevel = 'ALL'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends error to console.error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends warn to console.warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.ok(logs.warn[0].includes(message)); + }); + it('sends info to console.info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.ok(logs.info[0].includes(message)); + }); + it('sends log to console.log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.ok(logs.log[0].includes(message)); + }); + it('sends debug to console.debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.ok(logs.debug[0].includes(message)); + }); + }); + + context('threshold=DEBUG', function () { + beforeEach(function () { + config.logLevel = 'DEBUG'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends error to console.error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends warn to console.warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.ok(logs.warn[0].includes(message)); + }); + it('sends info to console.info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.ok(logs.info[0].includes(message)); + }); + it('sends log to console.log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.ok(logs.log[0].includes(message)); + }); + it('sends debug to console.debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.ok(logs.debug[0].includes(message)); + }); + }); + + context('threshold=LOG', function () { + beforeEach(function () { + config.logLevel = 'LOG'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends error to console.error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends warn to console.warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.ok(logs.warn[0].includes(message)); + }); + it('sends info to console.info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.ok(logs.info[0].includes(message)); + }); + it('sends log to console.log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.ok(logs.log[0].includes(message)); + }); + it('ignores debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.equal(logs.debug.length, 0); + }); + }); + + context('threshold=INFO', function () { + beforeEach(function () { + config.logLevel = 'INFO'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends error to console.error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends warn to console.warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.ok(logs.warn[0].includes(message)); + }); + it('sends info to console.info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.ok(logs.info[0].includes(message)); + }); + it('ignores log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.equal(logs.log.length, 0); + }); + it('ignores debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.equal(logs.debug.length, 0); + }); + }); + + context('threshold=WARN', function () { + beforeEach(function () { + config.logLevel = 'WARN'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends error to console.error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends warn to console.warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.ok(logs.warn[0].includes(message)); + }); + it('ignores info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.equal(logs.info.length, 0); + }); + it('ignores log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.equal(logs.log.length, 0); + }); + it('ignores debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.equal(logs.debug.length, 0); + }); + }); + + context('threshold=ERROR', function () { + beforeEach(function () { + config.logLevel = 'ERROR'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('sends error to console.error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.ok(logs.error[0].includes(message)); + }); + it('ignores warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.equal(logs.warn.length, 0); + }); + it('ignores info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.equal(logs.info.length, 0); + }); + it('ignores log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.equal(logs.log.length, 0); + }); + it('ignores debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.equal(logs.debug.length, 0); + }); + }); + + context('threshold=CRITICAL', function () { + beforeEach(function () { + config.logLevel = 'CRITICAL'; + }); + it('sends critical to console.error', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.ok(logs.error[0].includes(message)); + }); + it('ignores error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.equal(logs.error.length, 0); + }); + it('ignores warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.equal(logs.warn.length, 0); + }); + it('ignores info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.equal(logs.info.length, 0); + }); + it('ignores log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.equal(logs.log.length, 0); + }); + it('ignores debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.equal(logs.debug.length, 0); + }); + }); + + context('threshold=NONE', function () { + beforeEach(function () { + config.logLevel = 'NONE'; + }); + it('ignores critical', function () { + var message = 'this is a critical message'; + logger.critical(message); + assert.equal(logs.error.length, 0); + }); + it('ignores error', function () { + var message = 'this is an error message'; + logger.error(message); + assert.equal(logs.error.length, 0); + }); + it('ignores warn', function () { + var message = 'this is a warn message'; + logger.warn(message); + assert.equal(logs.warn.length, 0); + }); + it('ignores info', function () { + var message = 'this is an info message'; + logger.info(message); + assert.equal(logs.info.length, 0); + }); + it('ignores log', function () { + var message = 'this is a log message'; + logger.log(message); + assert.equal(logs.log.length, 0); + }); + it('ignores debug', function () { + var message = 'this is a debug message'; + logger.debug(message); + assert.equal(logs.debug.length, 0); + }); + }); +}); From 73bb48669e47891864c074c5a9b0f7efc256c775 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:40:51 -0400 Subject: [PATCH 06/10] test assignment of logger & log level --- test/index.js | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/index.js diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..aa03a9d --- /dev/null +++ b/test/index.js @@ -0,0 +1,65 @@ +var chai = require('chai'); + +var testCommons = require('./test_commons'); +var assert = require('assert'); +var logger = require('../logger'); +var config = require('../config'); +var index = require('../index'); +var mysql = require('./mysql'); + +var should = chai.should(); +var path = __dirname + '/migrations/'; + +describe('index.js', function () { + + beforeEach(function (done) { + config.logger = logger; + config.logLevel = 'ALL'; + done(); + }); + + describe('.init', function () { + it('assigns --logger to config.logger', function (done) { + function noop() { }; + + var newLogger = { + critical: noop, + error: noop, + warn: noop, + info: noop, + log: noop, + debug: noop + }; + assert.notEqual(config.logger, newLogger); + + index.init( + mysql, + path, + function () { + assert.equal(config.logger, newLogger); + done(); + }, + [ + '--logger', + newLogger + ] + ); + }); + it('assigns --log-level to config.logLevel', function (done) { + var newLevel = 'NONE'; + assert.notEqual(config.logLevel, newLevel); + + index.init( + mysql, + path, + function () { + assert.equal(config.logLevel, newLevel); + done(); + }, + [ + `--log-level ${newLevel}` + ] + ); + }); + }); +}); From 02ed2533d2b7642a43a094692fd0c6c27b7106b5 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:42:03 -0400 Subject: [PATCH 07/10] reset logger / log level for each test --- test/test_commons.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test_commons.js b/test/test_commons.js index 6be62c4..3248bc1 100644 --- a/test/test_commons.js +++ b/test/test_commons.js @@ -1,5 +1,7 @@ var mysql = require('./mysql'); var fs = require('fs'); +var config = require('../config'); +var logger = require('../logger'); function deleteFolderRecursive(path) { if (fs.existsSync(path)) { @@ -15,7 +17,9 @@ function deleteFolderRecursive(path) { } module.exports = function(cb) { - mysql.getConnection(function(err, connection) { + config.logLevel = 'ALL'; + config.logger = logger; + mysql.getConnection(function (err, connection) { if (err) { throw err; } From b4eeb8bb6f12c3a0b95581e4ff8211d6e31cc7be Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:43:18 -0400 Subject: [PATCH 08/10] use config logger --- query.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/query.js b/query.js index 9ea29d1..fd3c2c5 100644 --- a/query.js +++ b/query.js @@ -1,6 +1,5 @@ -var table = require('./config')['table']; -var fileFunctions = require('./file'); -var colors = require('colors'); +var config = require('./config'); +var table = config.table; function run_query(conn, query, cb, run) { if (run == null) { @@ -36,7 +35,8 @@ function execute_query(conn, path, final_file_paths, type, cb, run) { var current_file_path = path + "/" + file_name; var queries = require(current_file_path); - console.info(colors.green("Run: " + run + " Type: " + type.toUpperCase() + ": " +queries[type])); + config.logger.info(`Run: ${run} Type: ${type.toUpperCase()}`); + config.logger.debug(queries[type]); var timestamp_val = file_name.split("_", 1)[0]; if (typeof(queries[type]) == 'string') { @@ -46,7 +46,7 @@ function execute_query(conn, path, final_file_paths, type, cb, run) { }); }, run); } else if (typeof(queries[type]) == 'function') { - console.info(`${type.toUpperCase()} Function: "${ queries[type].toString() }"`); + config.logger.info(`${type.toUpperCase()} Function: "${queries[type].toString()}"`); queries[type](conn, function() { updateRecords(conn, type, table, timestamp_val, function () { @@ -56,7 +56,7 @@ function execute_query(conn, path, final_file_paths, type, cb, run) { } } else { - console.info(colors.blue("No more " + type.toUpperCase() + " migrations to run")); + config.logger.info(`No more ${type.toUpperCase()} migrations to run`); cb(); } } From 27769ec6c879ee6d7e3c12b8908a863df72b6043 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:44:47 -0400 Subject: [PATCH 09/10] use config logger --- core_functions.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core_functions.js b/core_functions.js index b724cdf..08da3df 100644 --- a/core_functions.js +++ b/core_functions.js @@ -2,9 +2,9 @@ var fs = require("fs"); var fileFunctions = require('./file'); var queryFunctions = require('./query'); -var colors = require('colors'); var exec = require('child_process').exec; -var table = require('./config')['table']; +var config = require('./config'); +var table = config.table; function add_migration(argv, path, cb) { fileFunctions.validate_file_name(argv[4]); @@ -27,7 +27,7 @@ function add_migration(argv, path, cb) { throw err; } - console.log("Added file " + file_name); + config.logger.info("Added file " + file_name); cb(); }); }); @@ -139,7 +139,7 @@ function update_schema(conn, path, cb) { exec(cmd, function(error, stdout, stderr) { fs.writeFile(filePath, stdout, function(err) { if (err) { - console.log(colors.red("Could not save schema file")); + config.logger.error("Could not save schema file"); } cb(); }); @@ -172,7 +172,7 @@ function createFromSchema(conn, path, cb) { cmd = cmd + " < " + filePath; exec(cmd, function(error, stdout, stderr) { if (error) { - console.log(colors.red("Could not load from Schema: " + error)); + config.logger.error("Could not load from Schema: " + error); cb(); } else { var file_paths = []; @@ -193,7 +193,7 @@ function createFromSchema(conn, path, cb) { } }); } else { - console.log(colors.red("Schema Missing: " + filePath)); + config.logger.error("Schema Missing: " + filePath); cb(); } } From 0c610b96a9e73284fcea576373109f9ba0a5cee8 Mon Sep 17 00:00:00 2001 From: Lewis Moten Date: Mon, 3 Jun 2024 17:45:43 -0400 Subject: [PATCH 10/10] document logging options --- README.MD | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.MD b/README.MD index e1582fa..7b6cf99 100644 --- a/README.MD +++ b/README.MD @@ -6,6 +6,7 @@ - [Setup](#setup) - [Adding Migrations](#adding-migrations) - [Executing Migrations](#executing-migrations) + - [Logging](#logging) ## Prerequisites A node project with [mysql](https://github.com/mysqljs/mysql) used for database. @@ -216,6 +217,73 @@ node migration.js load-from-schema The above command will create all the tables and make entry in the logs table. It is helpful when setting up projects for newbies or environments without running entire migration process. +## Logging + +### Custom Logging + +By default, logging is done with `console`. You may specify your own logger with the following methods: `debug`, `log`, `info`, `warn`, `error`, and `critical`. Each one expects a string argument as a message. + +```js +var customLogger = { + debug: function (message) { + console.debug(message); + }, + log: function (message) { + console.log(message); + }, + info: function (message) { + console.info(message); + }, + warn: function (message) { + console.warn(message); + }, + error: function (message) { + console.error(message); + }, + critical: function (message) { + console.error(message); + } +}; + +migration.init( + connection, + __dirname + '/migrations', + function() {}, + [ + "--logger", + customLogger + ] +); +``` + +### Logging Threshold + +The logs can be fairly verbose. You can adjust the `--log-level ` according to the logs that you want to see. + +| Threshold | Logs | +| --- | --- | +| ALL | CRITICAL, ERROR, WARN, INFO, LOG, DEBUG | +| DEBUG | CRITICAL, ERROR, WARN, INFO, LOG, DEBUG | +| LOG | CRITICAL, ERROR, WARN, INFO, LOG | +| INFO | CRITICAL, ERROR, WARN, INFO | +| WARN | CRITICAL, ERROR, WARN | +| ERROR | CRITICAL, ERROR | +| CRITICAL | CRITICAL | +| NONE | *(nothing/silent)* | + +```js +migration.init( + connection, + __dirname + '/migrations', + function() {}, + [ + "--log-level ERROR" + ] +); +``` + +NOTE: The `--log-level` option is not evaluated before calling methods on custom loggers specified via the `--logger` option. + ## Pending >>Test cases: Will pick up when I get time.