diff --git a/config/env/development.js b/config/env/development.js index 1e9ac2b4..8b5fe269 100644 --- a/config/env/development.js +++ b/config/env/development.js @@ -16,19 +16,12 @@ module.exports = { // logging with Morgan - https://github.com/expressjs/morgan // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' format: 'dev', - options: { - // Stream defaults to process.stdout - // Uncomment/comment to toggle the logging to a log on the file system - // stream: { - // directoryPath: process.cwd(), - // fileName: 'access.log', - // rotatingLogs: { // for more info on rotating logs - https://github.com/holidayextras/file-stream-rotator#usage - // active: false, // activate to use rotating logs - // fileName: 'access-%DATE%.log', // if rotating logs are active, this fileName setting will be used - // frequency: 'daily', - // verbose: false - // } - // } + fileLogger: { + directoryPath: process.cwd(), + fileName: 'app.log', + maxsize: 10485760, + maxFiles: 2, + json: false } }, app: { diff --git a/config/env/production.js b/config/env/production.js index 581944ee..cee8552b 100644 --- a/config/env/production.js +++ b/config/env/production.js @@ -22,19 +22,12 @@ module.exports = { // logging with Morgan - https://github.com/expressjs/morgan // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' format: process.env.LOG_FORMAT || 'combined', - options: { - // Stream defaults to process.stdout - // Uncomment/comment to toggle the logging to a log on the file system - stream: { - directoryPath: process.env.LOG_DIR_PATH || process.cwd(), - fileName: process.env.LOG_FILE || 'access.log', - rotatingLogs: { // for more info on rotating logs - https://github.com/holidayextras/file-stream-rotator#usage - active: process.env.LOG_ROTATING_ACTIVE === 'true', // activate to use rotating logs - fileName: process.env.LOG_ROTATING_FILE || 'access-%DATE%.log', // if rotating logs are active, this fileName setting will be used - frequency: process.env.LOG_ROTATING_FREQUENCY || 'daily', - verbose: process.env.LOG_ROTATING_VERBOSE === 'true' - } - } + fileLogger: { + directoryPath: process.env.LOG_DIR_PATH || process.cwd(), + fileName: process.env.LOG_FILE || 'app.log', + maxsize: 10485760, + maxFiles: 2, + json: false } }, facebook: { diff --git a/config/env/test.js b/config/env/test.js index 7f1bf25b..8fdabb16 100644 --- a/config/env/test.js +++ b/config/env/test.js @@ -15,21 +15,14 @@ module.exports = { log: { // logging with Morgan - https://github.com/expressjs/morgan // Can specify one of 'combined', 'common', 'dev', 'short', 'tiny' - format: process.env.LOG_FORMAT || 'combined', - options: { - // Stream defaults to process.stdout - // Uncomment/comment to toggle the logging to a log on the file system - stream: { - directoryPath: process.cwd(), - fileName: 'access.log', - rotatingLogs: { // for more info on rotating logs - https://github.com/holidayextras/file-stream-rotator#usage - active: false, // activate to use rotating logs - fileName: 'access-%DATE%.log', // if rotating logs are active, this fileName setting will be used - frequency: 'daily', - verbose: false - } - } - } + // format: 'dev' + // fileLogger: { + // directoryPath: process.cwd(), + // fileName: 'app.log', + // maxsize: 10485760, + // maxFiles: 2, + // json: false + // } }, port: process.env.PORT || 3001, app: { diff --git a/config/lib/express.js b/config/lib/express.js index 2f7e1515..c3d4a88a 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -18,6 +18,7 @@ var config = require('../config'), flash = require('connect-flash'), consolidate = require('consolidate'), path = require('path'), + _ = require('lodash'), lusca = require('lusca'); /** @@ -68,8 +69,10 @@ module.exports.initMiddleware = function (app) { // Initialize favicon middleware app.use(favicon(app.locals.favicon)); - // Enable logger (morgan) - app.use(morgan(logger.getFormat(), logger.getOptions())); + // Enable logger (morgan) if enabled in the configuration file + if (_.has(config, 'log.format')) { + app.use(morgan(logger.getLogFormat(), logger.getMorganOptions())); + } // Environment dependent middleware if (process.env.NODE_ENV === 'development') { diff --git a/config/lib/logger.js b/config/lib/logger.js index 06175f49..10d39d5f 100644 --- a/config/lib/logger.js +++ b/config/lib/logger.js @@ -3,27 +3,128 @@ var _ = require('lodash'), config = require('../config'), chalk = require('chalk'), - fileStreamRotator = require('file-stream-rotator'), - fs = require('fs'); + fs = require('fs'), + winston = require('winston'); // list of valid formats for the logging var validFormats = ['combined', 'common', 'dev', 'short', 'tiny']; -// build logger service -var logger = { - getFormat: getLogFormat, // log format to use - getOptions: getLogOptions // log options to use +// Instantiating the default winston application logger with the Console +// transport +var logger = new winston.Logger({ + transports: [ + new winston.transports.Console({ + level: 'info', + colorize: true, + showLevel: true, + handleExceptions: true, + humanReadableUnhandledException: true + }) + ], + exitOnError: false +}); + +// A stream object with a write function that will call the built-in winston +// logger.info() function. +// Useful for integrating with stream-related mechanism like Morgan's stream +// option to log all HTTP requests to a file +logger.stream = { + write: function(msg) { + logger.info(msg); + } }; -// export the logger service -module.exports = logger; +/** + * Instantiate a winston's File transport for disk file logging + * + * @param logger a valid winston logger object + */ +logger.setupFileLogger = function setupFileLogger(options) { + + var fileLoggerTransport = this.getLogOptions(); + if (!fileLoggerTransport) { + return false; + } + + try { + // Check first if the configured path is writable and only then + // instantiate the file logging transport + if (fs.openSync(fileLoggerTransport.filename, 'a+')) { + logger.add(winston.transports.File, fileLoggerTransport); + } + + return true; + } catch (err) { + if (process.env.NODE_ENV !== 'test') { + console.log(); + console.log(chalk.red('An error has occured during the creation of the File transport logger.')); + console.log(chalk.red(err)); + console.log(); + } + + return false; + } + +}; + +/** + * The options to use with winston logger + * + * Returns a Winston object for logging with the File transport + */ +logger.getLogOptions = function getLogOptions(configOptions) { + + var _config = _.clone(config, true); + if (configOptions) { + _config = configOptions; + } + + var configFileLogger = _config.log.fileLogger; + + if (!_.has(_config, 'log.fileLogger.directoryPath') || !_.has(_config, 'log.fileLogger.fileName')) { + console.log('unable to find logging file configuration'); + return false; + } + + var logPath = configFileLogger.directoryPath + '/' + configFileLogger.fileName; + + return { + level: 'debug', + colorize: false, + filename: logPath, + timestamp: true, + maxsize: configFileLogger.maxsize ? configFileLogger.maxsize : 10485760, + maxFiles: configFileLogger.maxFiles ? configFileLogger.maxFiles : 2, + json: (_.has(configFileLogger, 'json')) ? configFileLogger.json : false, + eol: '\n', + tailable: true, + showLevel: true, + handleExceptions: true, + humanReadableUnhandledException: true + }; + +}; + +/** + * The options to use with morgan logger + * + * Returns a log.options object with a writable stream based on winston + * file logging transport (if available) + */ +logger.getMorganOptions = function getMorganOptions() { + + return { + stream: logger.stream + }; + +}; /** * The format to use with the logger * * Returns the log.format option set in the current environment configuration */ -function getLogFormat () { +logger.getLogFormat = function getLogFormat() { var format = config.log && config.log.format ? config.log.format.toString() : 'combined'; // make sure we have a valid format @@ -38,72 +139,8 @@ function getLogFormat () { } return format; -} +}; -/** - * The options to use with the logger - * - * Returns the log.options object set in the current environment configuration. - * NOTE: Any options, requiring special handling (e.g. 'stream'), that encounter an error will be removed from the options. - */ -function getLogOptions () { - var options = config.log && config.log.options ? _.clone(config.log.options, true) : {}; +logger.setupFileLogger({}); - // check if the current environment config has the log stream option set - if (_.has(options, 'stream')) { - - try { - - // check if we need to use rotating logs - if (_.has(options, 'stream.rotatingLogs') && options.stream.rotatingLogs.active) { - - if (options.stream.rotatingLogs.fileName.length && options.stream.directoryPath.length) { - - // ensure the log directory exists - if (!fs.existsSync(options.stream.directoryPath)) { - fs.mkdirSync(options.stream.directoryPath); - } - - options.stream = fileStreamRotator.getStream({ - filename: options.stream.directoryPath + '/' + options.stream.rotatingLogs.fileName, - frequency: options.stream.rotatingLogs.frequency, - verbose: options.stream.rotatingLogs.verbose - }); - - } else { - // throw a new error so we can catch and handle it gracefully - throw new Error('An invalid fileName or directoryPath was provided for the rotating logs option.'); - } - - } else { - - // create the WriteStream to use for the logs - if (options.stream.fileName.length && options.stream.directoryPath.length) { - - // ensure the log directory exists - if (!fs.existsSync(options.stream.directoryPath)) { - fs.mkdirSync(options.stream.directoryPath); - } - - options.stream = fs.createWriteStream(options.stream.directoryPath + '/' + config.log.options.stream.fileName, { flags: 'a' }); - } else { - // throw a new error so we can catch and handle it gracefully - throw new Error('An invalid fileName or directoryPath was provided for stream option.'); - } - } - } catch (err) { - - // remove the stream option - delete options.stream; - - if (process.env.NODE_ENV !== 'test') { - console.log(); - console.log(chalk.red('An error has occured during the creation of the WriteStream. The stream option has been omitted.')); - console.log(chalk.red(err)); - console.log(); - } - } - } - - return options; -} +module.exports = logger; diff --git a/modules/core/tests/server/core.server.config.tests.js b/modules/core/tests/server/core.server.config.tests.js index 310441dc..9eeeb9e7 100644 --- a/modules/core/tests/server/core.server.config.tests.js +++ b/modules/core/tests/server/core.server.config.tests.js @@ -426,53 +426,55 @@ describe('Configuration Tests:', function () { }); it('should retrieve the log format from the logger configuration', function () { + config.log = { format: 'tiny' }; - var format = logger.getFormat(); + var format = logger.getLogFormat(); format.should.be.equal('tiny'); }); - it('should retrieve the log options from the logger configuration', function () { - config.log = { - options: { - _test_log_option_: 'testing' - } - }; + it('should retrieve the log options from the logger configuration for a valid stream object', function () { + + var options = logger.getMorganOptions(); + + options.should.be.instanceof(Object); + options.should.have.property('stream'); - var options = logger.getOptions(); - should.deepEqual(options, config.log.options); }); - it('should verify that a writable stream was created using the logger configuration', function () { + it('should verify that a file logger object was created using the logger configuration', function () { var _dir = process.cwd(); var _filename = 'unit-test-access.log'; config.log = { - options: { - stream: { - directoryPath: _dir, - fileName: _filename - } + fileLogger: { + directoryPath: _dir, + fileName: _filename } }; - var options = logger.getOptions(); - options.stream.writable.should.equal(true); + var fileTransport = logger.getLogOptions(config); + fileTransport.should.be.instanceof(Object); + fileTransport.filename.should.equal(_dir + '/' + _filename); }); it('should use the default log format of "combined" when an invalid format was provided', function () { + + var _logger = require(path.resolve('./config/lib/logger')); + // manually set the config log format to be invalid config.log = { format: '_some_invalid_format_' }; - var format = logger.getFormat(); + var format = _logger.getLogFormat(); format.should.be.equal('combined'); }); - it('should remove the stream option when an invalid filename was supplied for the log stream option', function () { + it('should not create a file transport object if critical options are missing: filename', function () { + // manually set the config stream fileName option to an empty string config.log = { format: 'combined', @@ -484,112 +486,25 @@ describe('Configuration Tests:', function () { } }; - var options = logger.getOptions(); - should.not.exist(options.stream); + var fileTransport = logger.getLogOptions(config); + fileTransport.should.be.false(); }); - it('should remove the stream option when an invalid directory path was supplied for the log stream option', function () { + it('should not create a file transport object if critical options are missing: directory', function () { + // manually set the config stream fileName option to an empty string config.log = { format: 'combined', options: { stream: { directoryPath: '', - fileName: 'test.log' + fileName: 'app.log' } } }; - var options = logger.getOptions(); - should.not.exist(options.stream); - }); - - it('should confirm that the log directory is created if it does not already exist', function () { - var _dir = process.cwd() + '/temp-logs'; - var _filename = 'unit-test-access.log'; - - // manually set the config stream fileName option to an empty string - config.log = { - format: 'combined', - options: { - stream: { - directoryPath: _dir, - fileName: _filename - } - } - }; - - var options = logger.getOptions(); - options.stream.writable.should.equal(true); - }); - - it('should remove the stream option when an invalid filename was supplied for the rotating log stream option', function () { - // enable rotating logs - config.log = { - format: 'combined', - options: { - stream: { - directoryPath: process.cwd(), - rotatingLogs: { - active: true, - fileName: '', - frequency: 'daily', - verbose: false - } - } - } - }; - - var options = logger.getOptions(); - should.not.exist(options.stream); - }); - - it('should confirm that the rotating log is created using the logger configuration', function () { - var _dir = process.cwd(); - var _filename = 'unit-test-rotating-access-%DATE%.log'; - - // enable rotating logs - config.log = { - format: 'combined', - options: { - stream: { - directoryPath: _dir, - rotatingLogs: { - active: true, - fileName: _filename, - frequency: 'daily', - verbose: false - } - } - } - }; - - var options = logger.getOptions(); - should.exist(options.stream.write); - }); - - it('should confirm that the rotating log directory is created if it does not already exist', function () { - var _dir = process.cwd() + '/temp-rotating-logs'; - var _filename = 'unit-test-rotating-access-%DATE%.log'; - - // enable rotating logs - config.log = { - format: 'combined', - options: { - stream: { - directoryPath: _dir, - rotatingLogs: { - active: true, - fileName: _filename, - frequency: 'daily', - verbose: false - } - } - } - }; - - var options = logger.getOptions(); - should.exist(options.stream.write); + var fileTransport = logger.getLogOptions(config); + fileTransport.should.be.false(); }); }); }); diff --git a/package.json b/package.json index 90a679b4..fe1a4dae 100644 --- a/package.json +++ b/package.json @@ -40,12 +40,12 @@ "glob": "~7.0.0", "grunt": "0.4.5", "grunt-cli": "~0.1.13", + "gulp-wiredep": "~0.0.0", "helmet": "~1.3.0", "jasmine-core": "~2.4.1", "lodash": "~4.6.1", "lusca": "~1.3.0", "method-override": "~2.3.5", - "gulp-wiredep": "~0.0.0", "mocha": "~2.4.5", "mongoose": "~4.4.8", "morgan": "~1.7.0", @@ -64,7 +64,8 @@ "serve-favicon": "~2.3.0", "socket.io": "~1.4.5", "swig": "~1.4.2", - "validator": "~5.1.0" + "validator": "~5.1.0", + "winston": "^2.2.0" }, "devDependencies": { "coveralls": "~2.11.6",