diff --git a/app.js b/app.js index 73f5227370bff0fa4dbca3630d27f68d963edd78..7370194a6ee4c038dfa088663ba4fbf700e69d0c 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,11 @@ // User Imports. const { userConfirmation } = require('./src/helper_functions.js'); -const testLogLevels = require('./src/logging.js'); +const { Logging, testLogLevels } = require('./src/logging.js'); + + +// Initialize logging. +let logger = new Logging('./logging/'); /** @@ -19,7 +23,7 @@ async function main() { console.log(''); console.log('Testing log levels...'); console.log(''); - testLogLevels(); + testLogLevels(logger); console.log(''); console.log('Log level tests complete.'); console.log(''); diff --git a/src/logging.js b/src/logging.js index d6b9d9d4b8ba32805cfd1a5e4c5a4c3663a1c07a..eaff4dd0467a9333548c1cc0aa670df8ad91b046 100644 --- a/src/logging.js +++ b/src/logging.js @@ -3,7 +3,7 @@ * Simply include this file at the top of other files to get extra logging logic. * * https://git.brandon-rodriguez.com/javascript/custom_logger - * Version 1.0 + * Version 1.1 */ @@ -11,210 +11,234 @@ const chalk = require('chalk'); const fs = require('fs'); const path = require('path'); +const { Console } = require('console'); // User Imports. const { getCurrentDate } = require('./helper_functions.js'); /** - * Essentially "upgrades" default Javascript console output to be more informative. - * Adds DEBUG, INFO, WARNING, and ERROR output levels. - * These all display file and line number before statement. - * - * The LOG output level effectively stays as-is, using JavaScripts default logging implementation. + * Logging class. */ -['debug', 'info', 'warning', 'warn', 'error', 'log'].forEach((methodName) => { - - // Parse log method value. - let originalLoggingMethod = null; +class Logging { + test_var = 'test_var string'; + #console_debug = 'debug test'; + #debug_file_logger; + #info_file_logger; + #warning_file_logger; + #error_file_logger; + + /** + * Class Constructor. + */ + constructor(logging_dir) { + + // Attempt to create logging folder. + fs.mkdir(logging_dir, err => { + // Ignore "dir already exists" errors. Raise all others. + if (err && err.code != 'EEXIST') { + throw err; + } + }); - // Correct "WARNING" output level value. - if (methodName == 'warning') { - originalLoggingMethod = console['warn']; + // Initialize DEBUG file logging stream. + this.#debug_file_logger = new Console({ + stdout: fs.createWriteStream(logging_dir.concat('debug.log'), {'flags': 'a'}), + stderr: fs.createWriteStream(logging_dir.concat('error.log'), {'flags': 'a'}), + ignoreErrors: true, + colorMode: false + }); - } else { - // Leave all other values as-is. - originalLoggingMethod = console[methodName]; - } + // Initialize INFO file logging stream. + this.#info_file_logger = new Console({ + stdout: fs.createWriteStream(logging_dir.concat('info.log'), {'flags': 'a'}), + stderr: fs.createWriteStream(logging_dir.concat('error.log'), {'flags': 'a'}), + ignoreErrors: true, + colorMode: false + }); + // Initialize WARNING file logging stream. + this.#warning_file_logger = new Console({ + stdout: fs.createWriteStream(logging_dir.concat('warning.log'), {'flags': 'a'}), + stderr: fs.createWriteStream(logging_dir.concat('error.log'), {'flags': 'a'}), + ignoreErrors: true, + colorMode: false + }); - console[methodName] = (firstArgument, ...otherArguments) => { + // Initialize ERROR file logging stream. + this.#error_file_logger = new Console({ + stdout: fs.createWriteStream(logging_dir.concat('error.log'), {'flags': 'a'}), + ignoreErrors: true, + colorMode: false + }); + } - // Magic to get our line num and file name. + /** + * Returns calling file and line num location. + */ + getCallerInfo() { let originalPrepareStackTrace = Error.prepareStackTrace; Error.prepareStackTrace = (_, stack) => stack; - let callee = new Error().stack[1]; + let callee = new Error().stack[2]; Error.prepareStackTrace = originalPrepareStackTrace; let relativeFileName = path.relative(process.cwd(), callee.getFileName()); + return [relativeFileName, callee.getLineNumber()] + } - // Generate and log our text to console/file. - let prefix = ''; - let console_string = ''; - let log_string = ''; - if (methodName == 'debug') { - // Handling for DEBUG level. - - // Generate and print Console text. - prefix = ` ${chalk.green('[DEBUG]')} [${relativeFileName} ${callee.getLineNumber()}] `; - console_string = prefix.concat(firstArgument, ...otherArguments); - originalLoggingMethod(console_string); - - // Generate and print Log File text. - prefix = `${getCurrentDate()} [DEBUG] [${relativeFileName} ${callee.getLineNumber()}] `; - log_string = prefix.concat(firstArgument, ...otherArguments, '\n'); - logToFile(log_string, 'debug'); - - } else if (methodName == 'info') { - // Handling for INFO level. - - // Generate and print Console text. - prefix = ` ${chalk.blue('[INFO]')} [${relativeFileName} ${callee.getLineNumber()}] `; - console_string = prefix.concat(firstArgument, ...otherArguments); - originalLoggingMethod(console_string); - - // Generate and print Log File text. - prefix = `${getCurrentDate()} [INFO] [${relativeFileName} ${callee.getLineNumber()}] `; - log_string = prefix.concat(firstArgument, ...otherArguments, '\n'); - logToFile(log_string, 'info'); - - } else if (methodName == 'warning' || methodName == 'warn') { - // Handling for WARNING level. - - // Generate and print Console text. - prefix = ` ${chalk.yellow('[WARN]')} [${relativeFileName} ${callee.getLineNumber()}] `; - console_string = prefix.concat(firstArgument, ...otherArguments); - originalLoggingMethod(console_string); - - // Generate and print Log File text. - prefix = `${getCurrentDate()} [WARN] [${relativeFileName} ${callee.getLineNumber()}] `; - log_string = prefix.concat(firstArgument, ...otherArguments, '\n'); - logToFile(log_string, 'warn'); - - } else if (methodName == 'error') { - // Prepend text for ERROR level. - - // Generate and print Console text. - prefix = ` ${chalk.red('[ERROR]')} [${relativeFileName} ${callee.getLineNumber()}] `; - console_string = prefix.concat(firstArgument, ...otherArguments); - originalLoggingMethod(console_string); - - // Generate and print Log File text. - prefix = `${getCurrentDate()} [ERROR] [${relativeFileName} ${callee.getLineNumber()}] `; - log_string = prefix.concat(firstArgument, ...otherArguments, '\n'); - logToFile(log_string, 'error'); - - } else if (methodName == 'log') { - // LOG level does not get prepend text. Nor does it save to file. - // This is basically intended for "console only" output. - prefix = ' '; - console_string = prefix.concat(firstArgument, ...otherArguments); - originalLoggingMethod(console_string); - - } else { - // Fallback prepend text for all other cases. - prefix = ` [${methodName}] [${relativeFileName} ${callee.getLineNumber()}] `; - console_string = prefix.concat(firstArgument, ...otherArguments); - originalLoggingMethod(console_string); - } + /** + * LOG logging method. + */ + log(firstArgument, ...otherArguments) { + // LOG level does not get prepend text. Nor does it save to file. + // This is basically intended for "console only" output. + console.log(firstArgument, ...otherArguments); + } - }; -}); + /** + * DEBUG logging method. + */ + debug(firstArgument, ...otherArguments) { + // Get calling file name and line number. + let [relativeFileName, lineNum] = this.getCallerInfo(); + + // // Generate and print Console text. + let prefix = ` ${chalk.green('[DEBUG]')} [${relativeFileName} ${lineNum}] `; + let console_string = prefix.concat(firstArgument, ...otherArguments); + console.log(console_string); + + // Generate and print Log File text. + prefix = `${getCurrentDate()} [DEBUG] [${relativeFileName} ${lineNum}] `; + let log_string = prefix.concat(firstArgument, ...otherArguments); + this.#logToFile(log_string, 10); + } + /** + * INFO logging method. + */ + info(firstArgument, ...otherArguments) { + // Get calling file name and line number. + let [relativeFileName, lineNum] = this.getCallerInfo(); + + // // Generate and print Console text. + let prefix = ` ${chalk.blue('[INFO]')} [${relativeFileName} ${lineNum}] `; + let console_string = prefix.concat(firstArgument, ...otherArguments); + console.log(console_string); + + // Generate and print Log File text. + prefix = `${getCurrentDate()} [INFO] [${relativeFileName} ${lineNum}] `; + let log_string = prefix.concat(firstArgument, ...otherArguments); + this.#logToFile(log_string, 20); + } -/** - * Writes provided string to log files. - */ -function logToFile(log_string, level) { - let directory = './logging/'; - let file_location = './logging/info.log'; - let log_level = getLogLevelNum(level); - - // Attempt to create logging folder. - fs.mkdir(directory, err => { - // Ignore "dir already exists" errors. Raise all others. - if (err && err.code != 'EEXIST') { - throw err; - } - }); - - // Attempt to write to appropriate files, based on logging level. - if (log_level >= 40) { - // Error level logging or higher. - fs.appendFile(directory.concat('error.log'), log_string, (err) => { - if (err) { - console.log(err); - throw err; - } - }); + /** + * Alias function for WARNING level. + */ + warn(firstArgument, ...otherArguments) { + // Get calling file name and line number. + let [relativeFileName, lineNum] = this.getCallerInfo(); + + // // Generate and print Console text. + let prefix = ` ${chalk.yellow('[WARN]')} [${relativeFileName} ${lineNum}] `; + let console_string = prefix.concat(firstArgument, ...otherArguments); + console.log(console_string); + + // Generate and print Log File text. + prefix = `${getCurrentDate()} [WARN] [${relativeFileName} ${lineNum}] `; + let log_string = prefix.concat(firstArgument, ...otherArguments); + this.#logToFile(log_string, 30); } - if (log_level >= 30) { - // Warning level logging or higher. - fs.appendFile(directory.concat('warn.log'), log_string, (err) => { - if (err) { - console.log(err); - throw err; - } - }); + + /** + * WARNING logging method. + */ + warning(firstArgument, ...otherArguments) { + // Get calling file name and line number. + let [relativeFileName, lineNum] = this.getCallerInfo(); + + // // Generate and print Console text. + let prefix = ` ${chalk.yellow('[WARN]')} [${relativeFileName} ${lineNum}] `; + let console_string = prefix.concat(firstArgument, ...otherArguments); + console.log(console_string); + + // Generate and print Log File text. + prefix = `${getCurrentDate()} [WARN] [${relativeFileName} ${lineNum}] `; + let log_string = prefix.concat(firstArgument, ...otherArguments); + this.#logToFile(log_string, 30); } - if (log_level >= 20) { - // Info level logging or higher. - fs.appendFile(directory.concat('info.log'), log_string, (err) => { - if (err) { - console.log(err); - throw err; - } - }); + + /** + * ERROR logging method. + */ + error(firstArgument, ...otherArguments) { + // Get calling file name and line number. + let [relativeFileName, lineNum] = this.getCallerInfo(); + + // // Generate and print Console text. + let prefix = ` ${chalk.red('[ERROR]')} [${relativeFileName} ${lineNum}] `; + let console_string = prefix.concat(firstArgument, ...otherArguments); + console.log(console_string); + + // Generate and print Log File text. + prefix = `${getCurrentDate()} [ERROR] [${relativeFileName} ${lineNum}] `; + let log_string = prefix.concat(firstArgument, ...otherArguments); + this.#logToFile(log_string, 40); } - if (log_level >= 10) { - // Debug level logging or higher. - fs.appendFile(directory.concat('debug.log'), log_string, (err) => { - if (err) { - console.log(err); + + /** + * Writes provided string to log files. + */ + #logToFile(log_string, log_level) { + let directory = './logging/'; + let file_location = './logging/info.log'; + + // Attempt to create logging folder. + fs.mkdir(directory, err => { + // Ignore "dir already exists" errors. Raise all others. + if (err && err.code != 'EEXIST') { throw err; } }); - } -} - -/** - * Get appropriate level of logger. Based off of Python log levels. - * https://docs.python.org/3/library/logging.html - */ -function getLogLevelNum(level) { - - if (level == 'error') { - return 40; - - } else if (level == 'warn') { - return 30; + // Attempt to write to appropriate files, based on logging level. + if (log_level >= 40) { + // Error level logging or higher. + this.#error_file_logger.log(log_string); + } - } else if (level == 'info') { - return 20; + if (log_level >= 30) { + // Warning level logging or higher. + this.#warning_file_logger.log(log_string); + } - } else if (level == 'debug') { - return 10; + if (log_level >= 20) { + // Info level logging or higher. + this.#info_file_logger.log(log_string); + } - } else { - return 0; + if (log_level >= 10) { + // Debug level logging or higher. + this.#debug_file_logger.log(log_string); + } } -} - -console.debug('Logging has been initialized.'); +} /** * Simple test to make sure all loging levels work as expected. */ -function testLogLevels() { - console.debug('Debug level test'); - console.info('Info level test.'); - console.warn('Warn level test.'); - console.warning('Warning level test.'); - console.error('Error level test.'); - console.log('Log/default level test.'); +function testLogLevels(logger) { + logger.debug('DEBUG level test'); + logger.info('INFO level test.'); + logger.warn('WARN level test.'); + logger.warning('WARNING level test.'); + logger.error('ERROR level test.'); + logger.log('Log/default level test.'); + } -module.exports = testLogLevels; +module.exports = { + Logging, + testLogLevels, +};