diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..036841c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = exports = { + extends: 'xo', + env: { + node: true, + mocha: true + }, + rules: { + indent: ['error', 4], + camelcase: ['error', {properties: 'never'}] + } +}; diff --git a/.gitignore b/.gitignore index 07cf972..6067a47 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Logs -logs -*.log -*.log.* +*log* +test.js # Runtime data pids diff --git a/.travis.yml b/.travis.yml index 6bdf26d..877f4a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,14 @@ sudo: false language: node_js node_js: - - "0.10" - - "0.12" - - "4.1" + - "4" + - "6" + - "8" + +env: + - WINSTON_VER=winston@^2 + - WINSTON_VER=winston@next + +before_install: + - npm install $WINSTON_VER + diff --git a/README.md b/README.md index 531dd87..e68e599 100644 --- a/README.md +++ b/README.md @@ -2,19 +2,41 @@ [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] -> A transport for winston which logs to a rotating file each day. +A transport for [winston](https://github.com/winstonjs/winston) which logs to a rotating file. Logs can be rotated based on a date, size limit, and old logs can be removed based on count or elapsed days. -## Usage +Starting with version 2.0.0, the transport has been refactored to leverage the the [file-stream-rotator](https://github.com/rogerc/file-stream-rotator/) module. _Some of the options in the 1.x versions of the transport have changed._ Please review the options below to identify any changes needed. + +## Install +``` +npm install winston-daily-rotate-file +``` +## Options +The DailyRotateFile transport can rotate files by minute, hour, day, month, year or weekday. In addition to the options accepted by the logger, `winston-daily-rotate-file` also accepts the following options: + +* **datePattern:** A string representing the [moment.js date format](http://momentjs.com/docs/#/displaying/format/) to be used for rotating. The meta characters used in this string will dictate the frequency of the file rotation. For example, if your datePattern is simply 'HH' you will end up with 24 log files that are picked up and appended to every day. (default 'YYYY-MM-DD') +* **zippedArchive:** A boolean to define whether or not to gzip archived log files. (default 'false') +* **filename:** Filename to be used to log to. This filename can include the `%DATE%` placeholder which will include the formatted datePattern at that point in the filename. (default: 'winston.log.%DATE%) +* **dirname:** The directory name to save log files to. (default: '.') +* **stream:** Write directly to a custom stream and bypass the rotation capabilities. (default: null) +* **maxSize:** Maximum size of the file after which it will rotate. This can be a number of bytes, or units of kb, mb, and gb. If using the units, add 'k', 'm', or 'g' as the suffix. The units need to directly follow the number. (default: null) +* **maxFiles:** Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null) + +## Usage ``` js var winston = require('winston'); require('winston-daily-rotate-file'); var transport = new (winston.transports.DailyRotateFile)({ - filename: './log', - datePattern: 'yyyy-MM-dd.', - prepend: true, - level: process.env.ENV === 'development' ? 'debug' : 'info' + filename: 'application-%DATE%.log', + datePattern: 'YYYY-MM-DD-HH', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d' + }); + + transport.on('rotate', function(oldFilename, newFilename) { + // do something fun }); var logger = new (winston.Logger)({ @@ -26,30 +48,7 @@ logger.info('Hello World!'); ``` -The DailyRotateFile transport can rotate files by minute, hour, day, month, year or weekday. In addition to the options accepted by the File transport, the Daily Rotate File Transport also accepts the following options: - -* __datePattern:__ A string representing the pattern to be used when appending the date to the filename (default 'yyyy-MM-dd'). The meta characters used in this string will dictate the frequency of the file rotation. For example, if your datePattern is simply 'HH' you will end up with 24 log files that are picked up and appended to every day. -* __prepend:__ Defines if the rolling time of the log file should be prepended at the beginning of the filename (default 'false'). -* __localTime:__ A boolean to define whether time stamps should be local (default 'false' means that UTC time will be used). -* __zippedArchive:__ A boolean to define whether or not to gzip archived log files (default 'false'). -* __maxDays:__ A number representing the maximum number of days a log file will be saved. Any log file older than this specified number of days will be removed. If not value or a 0, no log files will be removed (default 0). -* __createTree:__ When combined with a `datePattern` that includes path delimiters, the transport will create the entire folder tree to the log file. Example: `datePattern: '/yyyy/MM/dd.log', createTree: true` will create the entire path to the log file prior to writing an entry. - -Valid meta characters in the datePattern are: - -* __yy:__ Last two digits of the year. -* __yyyy:__ Full year. -* __M:__ The month. -* __MM:__ The zero padded month. -* __d:__ The day. -* __dd:__ The zero padded day. -* __H:__ The hour. -* __HH:__ The zero padded hour. -* __m:__ The minute. -* __mm:__ The zero padded minute. -* __ddd:__ The weekday (Mon, Tue, ..., Sun). - -*Metadata:* Logged via util.inspect(meta); +You can listen for the *rotate* custom event. The rotate event will pass two parameters to the callback (*oldFilename*, *newFilename*). ## LICENSE MIT diff --git a/daily-rotate-file.js b/daily-rotate-file.js new file mode 100644 index 0000000..24d23ae --- /dev/null +++ b/daily-rotate-file.js @@ -0,0 +1,260 @@ +'use strict'; + +var fs = require('fs'); +var os = require('os'); +var path = require('path'); +var util = require('util'); +var semver = require('semver'); +var zlib = require('zlib'); +var winston = require('winston'); +var compat = require('winston-compat'); +var MESSAGE = require('triple-beam').MESSAGE; +var PassThrough = require('stream').PassThrough; +var Transport = semver.major(winston.version) === 2 ? compat.Transport : require('winston-transport'); + +var loggerDefaults = { + json: false, + colorize: false, + eol: os.EOL, + logstash: null, + prettyPrint: false, + label: null, + stringify: false, + depth: null, + showLevel: true, + timestamp: function () { + return new Date().toISOString(); + } +}; + +var DailyRotateFile = function (options) { + options = options || {}; + Transport.call(this, options); + + function throwIf(target /* , illegal... */) { + Array.prototype.slice.call(arguments, 1).forEach(function (name) { + if (options[name]) { + throw new Error('Cannot set ' + name + ' and ' + target + ' together'); + } + }); + } + + function getMaxSize(size) { + if (size && typeof size === 'string') { + var _s = size.toLowerCase().match(/^((?:0\.)?\d+)([k|m|g])$/); + if (_s) { + return size; + } + } else if (size && Number.isInteger(size)) { + var sizeK = Math.round(size / 1024); + return sizeK === 0 ? '1k' : sizeK + 'k'; + } + return null; + } + + this.options = Object.assign({}, loggerDefaults, options); + + if (options.stream) { + throwIf('stream', 'filename', 'maxsize'); + this.logStream = new PassThrough(); + this.logStream.pipe(options.stream); + } else { + this.filename = options.filename ? path.basename(options.filename) : 'winston.log'; + this.dirname = options.dirname || path.dirname(options.filename); + + var self = this; + + this.logStream = require('file-stream-rotator').getStream({ + filename: path.join(this.dirname, this.filename), + frequency: 'custom', + date_format: options.datePattern ? options.datePattern : 'YYYY-MM-DD', + verbose: false, + size: getMaxSize(options.maxSize), + max_logs: options.maxFiles + }); + + this.logStream.on('rotate', function (oldFile, newFile) { + self.emit('rotate', oldFile, newFile); + }); + + if (options.zippedArchive) { + this.logStream.on('rotate', function (oldFile) { + var gzip = zlib.createGzip(); + var inp = fs.createReadStream(oldFile); + var out = fs.createWriteStream(oldFile + '.gz'); + inp.pipe(gzip).pipe(out).on('finish', function () { + fs.unlinkSync(oldFile); + }); + }); + } + } +}; + +module.exports = DailyRotateFile; + +util.inherits(DailyRotateFile, Transport); + +DailyRotateFile.prototype.name = 'dailyRotateFile'; + +var noop = function () {}; +if (semver.major(winston.version) === 2) { + DailyRotateFile.prototype.log = function (level, msg, meta, callback) { + callback = callback || noop; + var options = Object.assign({}, this.options, { + level: level, + message: msg, + meta: meta + }); + + var output = compat.log(options) + options.eol; + this.logStream.write(output); + callback(null, true); + }; +} else { + DailyRotateFile.prototype.normalizeQuery = compat.Transport.prototype.normalizeQuery; + DailyRotateFile.prototype.log = function (info, callback) { + callback = callback || noop; + + this.logStream.write(info[MESSAGE] + this.options.eol); + this.emit('logged', info); + callback(null, true); + }; +} + +DailyRotateFile.prototype.close = function () { + var self = this; + if (this.logStream) { + this.logStream.end(function () { + self.emit('finish'); + }); + } +}; + +DailyRotateFile.prototype.query = function (options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + if (!this.filename) { + throw new Error('query() may not be used when initializing with a stream'); + } + + var self = this; + var results = []; + var row = 0; + options = self.normalizeQuery(options); + + var logFiles = (function () { + var fileRegex = new RegExp(self.filename.replace('%DATE%', '.*'), 'i'); + return fs.readdirSync(self.dirname).filter(function (file) { + return path.basename(file).match(fileRegex); + }); + })(); + + if (logFiles.length === 0 && callback) { + callback(null, results); + } + + (function processLogFile(file) { + if (!file) { + return; + } + var logFile = path.join(self.dirname, file); + var buff = ''; + + var stream = fs.createReadStream(logFile, { + encoding: 'utf8' + }); + + stream.on('error', function (err) { + if (stream.readable) { + stream.destroy(); + } + if (!callback) { + return; + } + return err.code === 'ENOENT' ? callback(null, results) : callback(err); + }); + + stream.on('data', function (data) { + data = (buff + data).split(/\n+/); + var l = data.length - 1; + + for (var i = 0; i < l; i++) { + if (!options.start || row >= options.start) { + add(data[i]); + } + row++; + } + + buff = data[l]; + }); + + stream.on('close', function () { + if (buff) { + add(buff, true); + } + + if (options.order === 'desc') { + results = results.reverse(); + } + + if (logFiles.length) { + processLogFile(logFiles.shift()); + } else if (callback) { + callback(null, results); + } + }); + + function add(buff, attempt) { + try { + var log = JSON.parse(buff); + if (check(log)) { + push(log); + } + } catch (e) { + if (!attempt) { + stream.emit('error', e); + } + } + } + + function check(log) { + if (!log || typeof log !== 'object') { + return; + } + + var time = new Date(log.timestamp); + if ((options.from && time < options.from) || (options.until && time > options.until)) { + return; + } + + return true; + } + + function push(log) { + if (options.rows && results.length >= options.rows && options.order !== 'desc') { + if (stream.readable) { + stream.destroy(); + } + return; + } + + if (options.fields) { + var obj = {}; + options.fields.forEach(function (key) { + obj[key] = log[key]; + }); + log = obj; + } + + if (options.order === 'desc') { + if (results.length >= options.rows) { + results.shift(); + } + } + results.push(log); + } + })(logFiles.shift()); +}; diff --git a/index.js b/index.js index 42416f3..c818660 100644 --- a/index.js +++ b/index.js @@ -1,853 +1,7 @@ 'use strict'; -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var common = require('winston/lib/winston/common'); -var Transport = require('winston').Transport; -var Stream = require('stream').Stream; -var os = require('os'); var winston = require('winston'); -var mkdirp = require('mkdirp'); -var zlib = require('zlib'); +var DailyRotateFile = require('./daily-rotate-file'); -var weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - -// -// ### function DailyRotateFile (options) -// #### @options {Object} Options for this instance. -// Constructor function for the DailyRotateFile transport object responsible -// for persisting log messages and metadata to one or more files. -// -var DailyRotateFile = module.exports = function (options) { - Transport.call(this, options); - - // - // Helper function which throws an `Error` in the event - // that any of the rest of the arguments is present in `options`. - // - function throwIf(target /* , illegal... */) { - Array.prototype.slice.call(arguments, 1).forEach(function (name) { - if (options[name]) { - throw new Error('Cannot set ' + name + ' and ' + target + 'together'); - } - }); - } - - if (options.filename || options.dirname) { - throwIf('filename or dirname', 'stream'); - this._basename = this.filename = options.filename ? - path.basename(options.filename) : - 'winston.log'; - - this.dirname = options.dirname || path.dirname(options.filename); - this.options = options.options || {flags: 'a'}; - - // - // "24 bytes" is maybe a good value for logging lines. - // - this.options.highWaterMark = this.options.highWaterMark || 24; - } else if (options.stream) { - throwIf('stream', 'filename', 'maxsize'); - this._stream = options.stream; - var self = this; - this._stream.on('error', function (error) { - self.emit('error', error); - }); - - // - // We need to listen for drain events when - // write() returns false. This can make node - // mad at times. - // - this._stream.setMaxListeners(Infinity); - } else { - throw new Error('Cannot log to file without filename or stream.'); - } - - this.json = options.json !== false; - this.colorize = options.colorize || false; - this.maxsize = options.maxsize || null; - this.logstash = options.logstash || null; - this.maxFiles = options.maxFiles || null; - this.label = options.label || null; - this.prettyPrint = options.prettyPrint || false; - this.showLevel = options.showLevel === undefined ? true : options.showLevel; - this.timestamp = options.timestamp === undefined ? true : options.timestamp; - this.datePattern = options.datePattern ? options.datePattern : '.yyyy-MM-dd'; - this.depth = options.depth || null; - this.eol = options.eol || os.EOL; - this.maxRetries = options.maxRetries || 2; - this.prepend = options.prepend || false; - this.createTree = options.createTree || false; - this.localTime = options.localTime || false; - this.zippedArchive = options.zippedArchive || false; - this.maxDays = options.maxDays || 0; - - if (this.json) { - this.stringify = options.stringify; - } - - // - // Internal state variables representing the number - // of files this instance has created and the current - // size (in bytes) of the current logfile. - // - this._size = 0; - this._created = 0; - this._buffer = []; - this._draining = false; - this._failures = 0; - this._archive = false; - - // Internal variable which will hold a record of all files - // belonging to this transport which are currently in the - // log directory in chronological order. - // - this._currentFiles = function () { - // - // Only proceed if maxsize is not configured for this transport. - if (!this.maxsize) { - try { - return fs.readdirSync(this.dirname).filter(function (file) { - return file.includes(this._basename); - }.bind(this)).map(function (file) { - return { - name: file, - time: fs.statSync(path.join(this.dirname, file)).mtime.getTime() - }; - }.bind(this)).sort(function (a, b) { - return a.time - b.time; - }).map(function (v) { - return v.name; - }); - } catch (e) { - // directory doesnt exist so there are no files. Do nothing. - } - } - return []; - }.bind(this)(); - - this._year = this._getTime('year'); - this._month = this._getTime('month'); - this._date = this._getTime('date'); - this._hour = this._getTime('hour'); - this._minute = this._getTime('minute'); - this._weekday = weekday[this._getTime('day')]; - var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhM])\1?/g; - var pad = function (val, len) { - val = String(val); - len = len || 2; - while (val.length < len) { - val = '0' + val; - } - return val; - }; - - this.getFormattedDate = function () { - // update the year, month, date... variables - this._year = this._getTime('year'); - this._month = this._getTime('month'); - this._date = this._getTime('date'); - this._hour = this._getTime('hour'); - this._minute = this._getTime('minute'); - this._weekday = weekday[this._getTime('day')]; - - var flags = { - yy: String(this._year).slice(2), - yyyy: this._year, - M: this._month + 1, - MM: pad(this._month + 1), - d: this._date, - dd: pad(this._date), - H: this._hour, - HH: pad(this._hour), - m: this._minute, - mm: pad(this._minute), - ddd: this._weekday - }; - return this.datePattern.replace(token, function ($0) { - return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); - }); - }; -}; - -// -// Inherit from `winston.Transport`. -// -util.inherits(DailyRotateFile, Transport); - -/** - * Define a getter so that `winston.transports.DailyRotateFile` - * is available and thus backwards compatible. - */ winston.transports.DailyRotateFile = DailyRotateFile; - -// -// Expose the name of this Transport on the prototype -// -DailyRotateFile.prototype.name = 'dailyRotateFile'; - -// -// ### function log (level, msg, [meta], callback) -// #### @level {string} Level at which to log the message. -// #### @msg {string} Message to log -// #### @meta {Object} **Optional** Additional metadata to attach -// #### @callback {function} Continuation to respond to when complete. -// Core logging method exposed to Winston. Metadata is optional. -// -DailyRotateFile.prototype.log = function (level, msg, meta, callback) { - if (this.silent) { - return callback(null, true); - } - - // - // If failures exceeds maxRetries then we can't access the - // stream. In this case we need to perform a noop and return - // an error. - // - if (this._failures >= this.maxRetries) { - return callback(new Error('Transport is in a failed state.')); - } - - var self = this; - - var output = common.log({ - level: level, - message: msg, - meta: meta, - json: this.json, - colorize: this.colorize, - logstash: this.logstash, - prettyPrint: this.prettyPrint, - timestamp: this.timestamp, - label: this.label, - stringify: this.stringify, - showLevel: this.showLevel, - depth: this.depth, - formatter: this.formatter, - humanReadableUnhandledException: this.humanReadableUnhandledException - }) + this.eol; - - this._size += output.length; - - if (this.filename) { - this.open(function (err) { - if (err) { - // - // If there was an error enqueue the message - // - return self._buffer.push([output, callback]); - } - - self._write(output, callback); - self._lazyDrain(); - }); - } else { - // - // If there is no `filename` on this instance then it was configured - // with a raw `WriteableStream` instance and we should not perform any - // size restrictions. - // - this._write(output, callback); - this._lazyDrain(); - } -}; - -// -// ### function _write (data, cb) -// #### @data {String|Buffer} Data to write to the instance's stream. -// #### @cb {function} Continuation to respond to when complete. -// Write to the stream, ensure execution of a callback on completion. -// -DailyRotateFile.prototype._write = function (data, callback) { - // If this is a file write stream, we could use the builtin - // callback functionality, however, the stream is not guaranteed - // to be an fs.WriteStream. - var ret = this._stream.write(data); - if (!callback) { - return; - } - - if (ret === false) { - return this._stream.once('drain', function () { - callback(null, true); - }); - } - callback(null, true); -}; - -// -// ### function query (options, callback) -// #### @options {Object} Loggly-like query options for this instance. -// #### @callback {function} Continuation to respond to when complete. -// Query the transport. Options object is optional. -// -DailyRotateFile.prototype.query = function (options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - var self = this; - - // TODO when maxfilesize rotate occurs - var createdFiles = self._currentFiles.slice(0); // Clone already sorted _currentFiles array - var results = []; - var row = 0; - options = self.normalizeQuery(options); - - if (createdFiles.length === 0 && callback) { - callback(null, results); - } - - // Edit so that all created files are read: - (function readNextFile(nextFile) { - if (!nextFile) { - return; - } - var file = path.join(self.dirname, nextFile); - var buff = ''; - - var stream = fs.createReadStream(file, { - encoding: 'utf8' - }); - - stream.on('error', function (err) { - if (stream.readable) { - stream.destroy(); - } - if (!callback) { - return; - } - return err.code === 'ENOENT' ? callback(null, results) : callback(err); - }); - - stream.on('data', function (data) { - data = (buff + data).split(/\n+/); - var l = data.length - 1; - var i = 0; - - for (; i < l; i++) { - if (!options.start || row >= options.start) { - add(data[i]); - } - row++; - } - - buff = data[l]; - }); - - stream.on('close', function () { - if (buff) { - add(buff, true); - } - if (options.order === 'desc') { - results = results.reverse(); - } - - if (createdFiles.length) { - readNextFile(createdFiles.shift()); - } else if (callback) { - callback(null, results); - } - }); - - function add(buff, attempt) { - try { - var log = JSON.parse(buff); - if (check(log)) { - push(log); - } - } catch (e) { - if (!attempt) { - stream.emit('error', e); - } - } - } - - function push(log) { - if (options.rows && results.length >= options.rows && options.order !== 'desc') { - if (stream.readable) { - stream.destroy(); - } - return; - } - - if (options.fields) { - var obj = {}; - options.fields.forEach(function (key) { - obj[key] = log[key]; - }); - log = obj; - } - - if (options.order === 'desc') { - if (results.length >= options.rows) { - results.shift(); - } - } - results.push(log); - } - - function check(log) { - if (!log) { - return; - } - - if (typeof log !== 'object') { - return; - } - - var time = new Date(log.timestamp); - if ((options.from && time < options.from) || - (options.until && time > options.until)) { - return; - } - - return true; - } - })(createdFiles.shift());// executes the function -}; - -// -// ### function stream (options) -// #### @options {Object} Stream options for this instance. -// Returns a log stream for this transport. Options object is optional. -// -DailyRotateFile.prototype.stream = function (options) { - var file = path.join(this.dirname, this._getFilename()); - options = options || {}; - var stream = new Stream(); - - var tail = { - file: file, - start: options.start - }; - - stream.destroy = common.tailFile(tail, function (err, line) { - if (err) { - return stream.emit('error', err); - } - - try { - stream.emit('data', line); - line = JSON.parse(line); - stream.emit('log', line); - } catch (e) { - stream.emit('error', e); - } - }); - - if (stream.resume) { - stream.resume(); - } - - return stream; -}; - -// -// ### function open (callback) -// #### @callback {function} Continuation to respond to when complete -// Checks to see if a new file needs to be created based on the `maxsize` -// (if any) and the current size of the file used. -// -DailyRotateFile.prototype.open = function (callback) { - if (this.opening) { - // - // If we are already attempting to open the next - // available file then respond with a value indicating - // that the message should be buffered. - // - return callback(true); - } else if (!this._stream || (this.maxsize && this._size >= this.maxsize) || - this._filenameHasExpired()) { - this._cleanOldFiles(); - // - // If we dont have a stream or have exceeded our size, then create - // the next stream and respond with a value indicating that - // the message should be buffered. - // - callback(true); - return this._createStream(); - } - - // - // Otherwise we have a valid (and ready) stream. - // - callback(); -}; - -// -// ### function close () -// Closes the stream associated with this instance. -// -DailyRotateFile.prototype.close = function () { - var self = this; - - if (this._stream) { - this._stream.end(); - this._stream.destroySoon(); - - this._stream.once('drain', function () { - self.emit('flush'); - self.emit('closed'); - }); - } -}; - -// -// ### function flush () -// Flushes any buffered messages to the current `stream` -// used by this instance. -// -DailyRotateFile.prototype.flush = function () { - var self = this; - - // - // Iterate over the `_buffer` of enqueued messaged - // and then write them to the newly created stream. - // - this._buffer.forEach(function (item) { - var str = item[0]; - var callback = item[1]; - - process.nextTick(function () { - self._write(str, callback); - self._size += str.length; - }); - }); - - // - // Quickly truncate the `_buffer` once the write operations - // have been started - // - self._buffer.length = 0; - - // - // When the stream has drained we have flushed - // our buffer. - // - self._stream.once('drain', function () { - self.emit('flush'); - self.emit('logged'); - }); -}; - -// -// ### @private function _createStream () -// Attempts to open the next appropriate file for this instance -// based on the common state (such as `maxsize` and `_basename`). -// -DailyRotateFile.prototype._createStream = function () { - var self = this; - this.opening = true; - - (function checkFile(target) { - var fullname = path.join(self.dirname, target); - // - // Creates the `WriteStream` and then flushes any - // buffered messages. - // - function createAndFlush(size) { - if (self._stream) { - self._archive = self.zippedArchive ? self._stream.path : false; - - self._stream.end(); - self._stream.destroySoon(); - } - - if (self.createTree) { - mkdirp.sync(path.dirname(fullname)); - } - - self._size = size; - self.filename = target; - self._stream = fs.createWriteStream(fullname, self.options); - self._stream.on('error', function (error) { - if (self._failures < self.maxRetries) { - self._createStream(); - self._failures++; - } else { - self.emit('error', error); - } - }); - - // - // We need to listen for drain events when - // write() returns false. This can make node - // mad at times. - // - self._stream.setMaxListeners(Infinity); - - // - // When the current stream has finished flushing - // then we can be sure we have finished opening - // and thus can emit the `open` event. - // - self.once('flush', function () { - // Because "flush" event is based on native stream "drain" event, - // logs could be written inbetween "self.flush()" and here - // Therefore, we need to flush again to make sure everything is flushed - self.flush(); - - self.opening = false; - self.emit('open', fullname); - }); - - // - // Remark: It is possible that in the time it has taken to find the - // next logfile to be written more data than `maxsize` has been buffered, - // but for sensible limits (10s - 100s of MB) this seems unlikely in less - // than one second. - // - self.flush(); - compressFile(); - } - - function compressFile() { - var logfile = self._archive; - self._archive = false; - if (logfile && fs.existsSync(String(logfile))) { - var gzip = zlib.createGzip(); - - var inp = fs.createReadStream(String(logfile)); - var out = fs.createWriteStream(logfile + '.gz'); - - inp.pipe(gzip).pipe(out); - fs.unlinkSync(String(logfile)); - } - } - - fs.stat(fullname, function (err, stats) { - if (err) { - if (err.code !== 'ENOENT') { - return self.emit('error', err); - } - - return createAndFlush(0); - } - - if (!stats || (self.maxsize && stats.size >= self.maxsize)) { - // - // If `stats.size` is greater than the `maxsize` for - // this instance then try again - // - return checkFile(self._getFile(true)); - } - - if (self._filenameHasExpired()) { - self._year = self._getTime('year'); - self._month = self._getTime('month'); - self._date = self._getTime('date'); - self._hour = self._getTime('hour'); - self._minute = self._getTime('minute'); - self._weekday = weekday[self._getTime('day')]; - self._created = 0; - return checkFile(self._getFile()); - } - - createAndFlush(stats.size); - }); - })(this._getFile()); -}; - -// -// ### @private function _getFile () -// Gets the next filename to use for this instance -// in the case that log filesizes are being capped. -// -DailyRotateFile.prototype._getFile = function (inc) { - var filename = this._getFilename(); - var remaining; - - if (inc) { - // - // Increment the number of files created or - // checked by this instance. - // - // Check for maxFiles option and delete file - if (this.maxFiles && (this._created >= (this.maxFiles - 1))) { - remaining = this._created - (this.maxFiles - 1); - if (remaining === 0) { - try { - fs.unlinkSync(path.join(this.dirname, filename)); - } catch (e) {} - } else { - try { - fs.unlinkSync(path.join(this.dirname, filename + '.' + remaining)); - } catch (e) {} - } - } - - this._created += 1; - } else if (!this.maxsize) { - // - // If the filename does not exist in the _currentFiles array then add it. - if (this._currentFiles.indexOf(filename) === -1) { - this._currentFiles.push(filename); - } - - // While the _currentFiles array contains more file names than is configured - // in maxFiles loop the _currentFiles array and delete the file found at el - // 0. - while (this.maxFiles && (this._currentFiles.length > this.maxFiles)) { - try { - fs.unlinkSync(path.join(this.dirname, this._currentFiles[0])); - } catch (e) { - // File isn't accessible, do nothing. - } - - // Remove the filename that was just deleted from the _currentFiles array. - this._currentFiles = this._currentFiles.slice(1); - } - } - - return this._created ? filename + '.' + this._created : filename; -}; - -// -// ### @private function _getFilename () -// Returns the log filename depending on `this.prepend` option value -// -DailyRotateFile.prototype._getFilename = function () { - var formattedDate = this.getFormattedDate(); - - if (this.prepend) { - if (this.datePattern === '.yyyy-MM-dd') { - this.datePattern = 'yyyy-MM-dd.'; - formattedDate = this.getFormattedDate(); - } - - return formattedDate + this._basename; - } - - return this._basename + formattedDate; -}; - -// -// ### @private function _lazyDrain () -// Lazily attempts to emit the `logged` event when `this.stream` has -// drained. This is really just a simple mutex that only works because -// Node.js is single-threaded. -// -DailyRotateFile.prototype._lazyDrain = function () { - var self = this; - - if (!this._draining && this._stream) { - this._draining = true; - - this._stream.once('drain', function () { - this._draining = false; - self.emit('logged'); - }); - } -}; - -// -// ### @private function _filenameHasExpired () -// Checks whether the current log file is valid -// based on given datepattern -// -DailyRotateFile.prototype._filenameHasExpired = function () { - // searching for m is enough to say minute in date pattern - if (this.datePattern.match(/m/)) { - return (this._year < this._getTime('year') || this._month < this._getTime('month') || this._date < this._getTime('date') || this._hour < this._getTime('hour') || this._minute < this._getTime('minute')); - } else if (this.datePattern.match(/H/)) { - return (this._year < this._getTime('year') || this._month < this._getTime('month') || this._date < this._getTime('date') || this._hour < this._getTime('hour')); - } else if (this.datePattern.match(/d/)) { - return (this._year < this._getTime('year') || this._month < this._getTime('month') || this._date < this._getTime('date')); - } else if (this.datePattern.match(/M/)) { - return (this._year < this._getTime('year') || this._month < this._getTime('month')); - } else if (this.datePattern.match(/yy/)) { - return (this._year < this._getTime('year')); - } - return false; -}; - -// -// ### @private function _getTime () -// Get current date/time -// based on localTime config -// -DailyRotateFile.prototype._getTime = function (timeType) { - var now = new Date(); - - if (this.localTime) { - if (timeType === 'year') { - return now.getFullYear(); - } else if (timeType === 'month') { - return now.getMonth(); - } else if (timeType === 'date') { - return now.getDate(); - } else if (timeType === 'hour') { - return now.getHours(); - } else if (timeType === 'minute') { - return now.getMinutes(); - } else if (timeType === 'day') { - return now.getDay(); - } - } - if (timeType === 'year') { - return now.getUTCFullYear(); - } else if (timeType === 'month') { - return now.getUTCMonth(); - } else if (timeType === 'date') { - return now.getUTCDate(); - } else if (timeType === 'hour') { - return now.getUTCHours(); - } else if (timeType === 'minute') { - return now.getUTCMinutes(); - } else if (timeType === 'day') { - return now.getUTCDay(); - } -}; - -// ### @private function _cleanOldFiles () -// Remove old log files -// based on "maxDays" option -DailyRotateFile.prototype._cleanOldFiles = function () { - var self = this; - var millisecondsInDay = 86400000; - var now = Date.now(); - - function removeOldFile(file) { - fs.unlink(self.dirname + path.sep + file, function (errUnlink) { - if (errUnlink) { - console.error('Error removing file ', file); - } - }); - } - - function tryToRemoveLogFile(file) { - var completeFileName = self.dirname + path.sep + file; - fs.stat(completeFileName, function (errStats, stats) { - if (errStats) { - console.error('Error stats file ', file, errStats); - return; - } - - var lastChangeTimestamp = ((stats.mtime && stats.mtime.getTime()) || 0); - var lifeTime = now - lastChangeTimestamp; - if (stats.isFile() && lifeTime > (millisecondsInDay * self.maxDays)) { - removeOldFile(file); - } - }); - } - - // if not maxDays specified, do not remove old log files - if (self.maxDays) { - fs.readdir(self.dirname, function (err, files) { - if (err) { - console.error('Error reading directory ', self.dirname, err); - return; - } - - var fileNameReg = new RegExp(self._basename, 'g'); - files.forEach(function (file) { - if (/.log/.test(file) && fileNameReg.test(file)) { - tryToRemoveLogFile(file); - } - }); - }); - } -}; +module.exports = DailyRotateFile; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..35ddf47 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1340 @@ +{ + "name": "winston-daily-rotate-file", + "version": "2.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "ansi-escapes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + }, + "dependencies": { + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "1.1.0", + "deep-eql": "0.1.3", + "type-detect": "1.0.0" + } + }, + "chalk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.1" + } + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.5.2" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "2.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.16.0.tgz", + "integrity": "sha512-YVXV4bDhNoHHcv0qzU4Meof7/P26B4EuaktMi5L1Tnt52Aov85KmYA8c5D+xyZr/BkhvwUqr011jDSD/QTULxg==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "babel-code-frame": "6.26.0", + "chalk": "2.3.0", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "3.1.0", + "doctrine": "2.1.0", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "1.0.0", + "espree": "3.5.2", + "esquery": "1.0.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "11.3.0", + "ignore": "3.3.7", + "imurmurhash": "0.1.4", + "inquirer": "3.3.0", + "is-resolvable": "1.1.0", + "js-yaml": "3.10.0", + "json-stable-stringify-without-jsonify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "7.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.5.0", + "strip-ansi": "4.0.0", + "strip-json-comments": "2.0.1", + "table": "4.0.2", + "text-table": "0.2.0" + } + }, + "eslint-config-xo": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.19.0.tgz", + "integrity": "sha512-RMGQv8UGWS/r8/2RT143H5BmESncz6i2LMEs/unUw1OjJwbeR8qaUOt4eAOHTggdWQlQlR8Y6XhPfgaPXDLKMA==", + "dev": true + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", + "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "dev": true, + "requires": { + "acorn": "5.3.0", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "integrity": "sha512-E44iT5QVOUJBKij4IIV3uvxuNlbKS38Tw1HiupxEIHPv9qtC2PrDYohbXV5U+1jnfIXttny8gUhj+oZvflFlzA==", + "dev": true, + "requires": { + "chardet": "0.4.2", + "iconv-lite": "0.4.19", + "tmp": "0.0.33" + } + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "file-stream-rotator": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.2.1.tgz", + "integrity": "sha1-DW/qGpp6uiWofP0xtuJp5E6PCvI=", + "requires": { + "moment": "2.20.1" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz", + "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz", + "integrity": "sha1-Sy3sjZB+k9szZiTc7AGDUC+MlCg=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + }, + "ignore": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "dev": true, + "requires": { + "ansi-escapes": "3.0.0", + "chalk": "2.3.0", + "cli-cursor": "2.1.0", + "cli-width": "2.2.0", + "external-editor": "2.1.0", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.4.5.tgz", + "integrity": "sha1-FRdo3Sh161G8gpXpgAAm6fK7OY8=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.3", + "growl": "1.8.1", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "glob": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", + "integrity": "sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=", + "dev": true, + "requires": { + "graceful-fs": "2.0.3", + "inherits": "2.0.3", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz", + "integrity": "sha1-fNLNsiiko/Nule+mzBQt59GhNtA=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + } + } + }, + "moment": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", + "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "rimraf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", + "integrity": "sha1-YrqUf6TAtDY4Oa7+zU8PutYFlyY=", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + } + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "dev": true, + "requires": { + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "chalk": "2.3.0", + "lodash": "4.17.4", + "slice-ansi": "1.0.0", + "string-width": "2.1.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "triple-beam": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.1.0.tgz", + "integrity": "sha1-KsOHyMS9BL0mxh34kaYHn4WS/hA=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "winston-compat": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.0.1.tgz", + "integrity": "sha1-iAn6rNP/sc5qPjfbn1SDS3QGhp0=", + "requires": { + "cycle": "1.0.3" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } +} diff --git a/package.json b/package.json index a8bed67..c94e474 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,11 @@ { "name": "winston-daily-rotate-file", - "version": "1.7.2", + "version": "2.0.0", "description": "A transport for winston which logs to a rotating file each day.", "main": "index.js", + "engines": { + "node": ">=4" + }, "scripts": { "test": "mocha && eslint .", "preversion": "npm test", @@ -25,25 +28,20 @@ }, "homepage": "https://github.com/winstonjs/winston-daily-rotate-file#readme", "peerDependencies": { - "winston": "2.x" + "winston": "^2 <= 3.0.0-rc2 || ^3" }, "devDependencies": { "chai": "3.5.0", - "eslint": "2.10.2", - "eslint-config-xo-space": "0.13.0", + "eslint": "^4.10.0", + "eslint-config-xo": "^0.19.0", "mocha": "2.4.5", - "moment": "2.13.0", - "rimraf": "2.5.2", - "timekeeper": "^0.1.1" - }, - "eslintConfig": { - "extends": "xo-space", - "env": { - "node": true, - "mocha": true - } + "moment": "^2.19.1", + "rimraf": "2.5.2" }, "dependencies": { - "mkdirp": "0.5.1" + "file-stream-rotator": "^0.2.1", + "semver": "^5.5.0", + "triple-beam": "^1.1.0", + "winston-compat": "0.0.1" } } diff --git a/test/memory-stream.js b/test/memory-stream.js index 6e944cb..ccafbe1 100644 --- a/test/memory-stream.js +++ b/test/memory-stream.js @@ -8,31 +8,31 @@ module.exports = WritableStream; util.inherits(WritableStream, Stream.Writable); function WritableStream(options) { - Stream.Writable.call(this, options); + Stream.Writable.call(this, options); } WritableStream.prototype.write = function () { - var ret = Stream.Writable.prototype.write.apply(this, arguments); - if (!ret) { - this.emit('drain'); - } + var ret = Stream.Writable.prototype.write.apply(this, arguments); + if (!ret) { + this.emit('drain'); + } - return ret; + return ret; }; WritableStream.prototype._write = function (chunk, encoding, callback) { - this.write(chunk, encoding, callback); + this.write(chunk, encoding, callback); }; WritableStream.prototype.toString = function () { - return this.toBuffer().toString(); + return this.toBuffer().toString(); }; WritableStream.prototype.toBuffer = function () { - var buffers = []; - this._writableState.buffer.forEach(function (data) { - buffers.push(data.chunk); - }); + var buffers = []; + this._writableState.getBuffer().forEach(function (data) { + buffers.push(data.chunk); + }); - return Buffer.concat(buffers); + return Buffer.concat(buffers); }; diff --git a/test/random-string.js b/test/random-string.js new file mode 100644 index 0000000..31ae7f3 --- /dev/null +++ b/test/random-string.js @@ -0,0 +1,29 @@ +var uppers = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; +var lowers = 'abcdefghijklmnopqrstuvwxyz'; +var numbers = '0123456789'; +var specials = '_-|@.,?/!~#$%^&*(){}[]+='; +var charClasses = [uppers, lowers, numbers, specials]; +var minLen = charClasses.length; +function chooseRandom(x) { + var i = Math.floor(Math.random() * x.length); + return (typeof (x) === 'string') ? x.substr(i, 1) : x[i]; +} + +module.exports = function (maxLen) { + maxLen = (maxLen || 36); + if (maxLen < minLen) { + throw new Error('length must be >= ' + minLen); + } + var str = ''; + var usedClasses = {}; + var charClass; + do { // Append a random char from a random char class. + while (str.length < maxLen) { + charClass = chooseRandom(charClasses); + usedClasses[charClass] = true; + str += chooseRandom(charClass); + } + // Ensure we have picked from every char class. + } while (Object.keys(usedClasses).length !== charClasses.length); + return str; +}; diff --git a/test/simple.tests.js b/test/simple.tests.js deleted file mode 100644 index 8bfb7f8..0000000 --- a/test/simple.tests.js +++ /dev/null @@ -1,793 +0,0 @@ -/* eslint-disable max-nested-callbacks,no-unused-expressions */ -'use strict'; - -var path = require('path'); -var expect = require('chai').expect; -var winston = require('winston'); -var rimraf = require('rimraf'); -var mkdirp = require('mkdirp'); -var moment = require('moment'); -var fs = require('fs'); -var tk = require('timekeeper'); -var MemoryStream = require('./memory-stream'); - -var DailyRotateFile = require('../'); - -var fixturesDir = path.join(__dirname, 'fixtures'); - -var transports = { - 'file': new DailyRotateFile({ - filename: path.join(fixturesDir, 'testfilename.log'), - prepend: false - }), - 'stream': new DailyRotateFile({stream: new MemoryStream()}), - 'prepended file': new DailyRotateFile({ - filename: path.join(fixturesDir, 'testfilename.log'), - prepend: true - }), - 'weekday file': new DailyRotateFile({ - filename: path.join(fixturesDir, 'testfilename_weekday'), - datePattern: '.ddd.log' - }), - 'prepend weekday file': new DailyRotateFile({ - filename: path.join(fixturesDir, 'testfilename_prepend_weekday.log'), - datePattern: 'ddd-', - prepend: true - }) -}; - -describe('winston/transports/daily-rotate-file', function () { - before(function () { - rimraf.sync(fixturesDir); - mkdirp.sync(fixturesDir); - }); - - describe('an instance of the transport', function () { - describe('with / characters in the datePattern', function () { - it('should create the full path', function (done) { - var now = moment(); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'application'), - datePattern: '/yyyy/MM/dd.log', - createTree: true - }); - - transport.log('info', 'test message', {}, function (err) { - if (err) { - done(err); - } - - fs.readFile(path.join(fixturesDir, 'application', now.format('YYYY'), now.format('MM'), now.format('DD') + '.log'), 'utf8', function (err, contents) { - if (err) { - done(err); - } - - var lines = contents.split('\n').filter(function (n) { - return n !== ''; - }); - - expect(lines.length).to.equal(1); - done(); - }); - }); - }); - }); - - describe('with default datePatterns', function () { - it('should have a proper filename when prepend option is false', function () { - var now = moment().utc().format('YYYY-MM-DD'); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'prepend-false.log'), - prepend: false - }); - - expect(transport._getFilename()).to.equal('prepend-false.log.' + now); - }); - - it('should have a proper filename when prepend option is false (localtime)', function () { - var now = moment().format('YYYY-MM-DD'); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'prepend-false.log'), - localTime: true, - prepend: false - }); - - expect(transport._getFilename()).to.equal('prepend-false.log.' + now); - }); - - it('should have a proper filename when prepend options is true', function () { - var now = moment().utc().format('YYYY-MM-DD'); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'prepend-true.log'), - prepend: true - }); - - expect(transport._getFilename()).to.equal(now + '.prepend-true.log'); - }); - - it('should remove leading dot if one is provided with datePattern', function () { - var now = moment().utc().format('YYYYMMDD'); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'prepend-false.log'), - prepend: false, - datePattern: '.yyyyMMdd' - }); - - expect(transport._getFilename()).to.equal('prepend-false.log.' + now); - }); - - it('should not add leading dot if one is not provided with datePattern', function () { - var now = moment().utc().format('YYYY-MM-DD'); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'log'), - datePattern: '-yyyy-MM-dd.log' - }); - - expect(transport._getFilename()).to.equal('log-' + now + '.log'); - }); - - it('should remove leading dot if one is provided with datePattern when prepend option is true', function () { - var now = moment().utc().format('YYYY-MM-DD'); - var transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'prepend-true.log'), - prepend: true, - datePattern: '.yyyy-MM-dd' - }); - - expect(transport._getFilename()).to.equal(now + '.prepend-true.log'); - }); - }); - - Object.keys(transports).forEach(function (t) { - describe('when passed a valid ' + t, function () { - var transport; - - beforeEach(function () { - transport = transports[t]; - }); - - it('should have the proper methods defined', function () { - expect(transport).to.be.instanceOf(DailyRotateFile); - expect(transport).to.respondTo('log'); - }); - - var levels = winston.config.npm.levels; - Object.keys(levels).forEach(function (level) { - describe('with the ' + level + ' level', function () { - it('should respond with true when passed no metadata', function (done) { - transport.log(level, 'test message', {}, function (err, logged) { - expect(err).to.be.null; - expect(logged).to.be.true; - done(); - }); - }); - - var circular = { }; - circular.metadata = circular; - - var params = { - no: {}, - object: {metadata: true}, - primitive: 'metadata', - circular: circular - }; - - Object.keys(params).forEach(function (param) { - it('should respond with true when passed ' + param + ' metadata', function (done) { - transport.log(level, 'test message', params[param], function (err, logged) { - expect(err).to.be.null; - expect(logged).to.be.true; - done(); - }); - }); - }); - }); - }); - }); - }); - - describe('when passed an invalid filename', function () { - var transport; - - beforeEach(function () { - transport = new DailyRotateFile({ - filename: path.join(fixturesDir, 'invalid', 'testfilename.log') - }); - }); - - it('should have proper methods defined', function () { - expect(transport).to.be.instanceOf(DailyRotateFile); - expect(transport).to.respondTo('log'); - }); - - it('should enter noop failed state', function (done) { - transport.on('error', function (emitErr) { - expect(emitErr).to.be.instanceOf(Error); - expect(emitErr.code, 'ENOENT'); - expect(transport._failures).to.equal(transport.maxRetries); - done(); - }); - - transport.log('error', 'test message'); - }); - }); - - describe('when passed an valid filename with different date patterns for log rotation', function () { - // patterns having one start timestamp for which log file will be creted, - // then one mid timestamp for which log file should not be rotated, - // and finally one end timestamp for which log file should be rotated and - // new logfile should be created. - var patterns = { - 'full year pattern .yyyy': { - pattern: '.yyyy', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1874993560000, // GMT: Fri, 01 Jun 2029 07:32:40 GMT - end: 1893483160000, // GMT: Tue, 01 Jan 2030 07:32:40 GMT - oldfile: 'test-rotation.log.2029', - newfile: 'test-rotation.log.2030' - }, - 'small year pattern .yy': { - pattern: '.yy', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1874993560000, // GMT: Fri, 01 Jun 2029 07:32:40 GMT - end: 1893483160000, // GMT: Tue, 01 Jan 2030 07:32:40 GMT - oldfile: 'test-rotation.log.29', - newfile: 'test-rotation.log.30' - }, - 'month pattern .M': { - pattern: '.M', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - end: 1864625560000, // GMT: Thu, 01 Feb 2029 07:32:40 GMT - oldfile: 'test-rotation.log.1', - newfile: 'test-rotation.log.2' - }, - 'zero padded month pattern .MM': { - pattern: '.MM', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - end: 1864625560000, // GMT: Thu, 01 Feb 2029 07:32:40 GMT - oldfile: 'test-rotation.log.01', - newfile: 'test-rotation.log.02' - }, - 'daypattern .d': { - pattern: '.d', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861986760000, // GMT: Mon, 01 Jan 2029 18:32:40 GMT - end: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - oldfile: 'test-rotation.log.1', - newfile: 'test-rotation.log.15' - }, - 'zero padded day pattern .dd': { - pattern: '.dd', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861986760000, // GMT: Mon, 01 Jan 2029 18:32:40 GMT - end: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - oldfile: 'test-rotation.log.01', - newfile: 'test-rotation.log.15' - }, - 'hour pattern .H': { - pattern: '.H', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - end: 1861950760000, // GMT: Mon, 01 Jan 2029 08:32:40 GMT - oldfile: 'test-rotation.log.7', - newfile: 'test-rotation.log.8' - }, - 'zero padded hour pattern .HH': { - pattern: '.HH', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - end: 1861950760000, // GMT: Mon, 01 Jan 2029 08:32:40 GMT - oldfile: 'test-rotation.log.07', - newfile: 'test-rotation.log.08' - }, - 'minute pattern .m': { - pattern: '.m', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - mid: 1861947170000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - end: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - oldfile: 'test-rotation.log.32', - newfile: 'test-rotation.log.42' - }, - 'zero padded minute pattern .mm': { - pattern: '.mm', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - mid: 1861947170000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - end: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - oldfile: 'test-rotation.log.32', - newfile: 'test-rotation.log.42' - }, - 'daily rotation pattern .yyyy-MM-dd': { - pattern: '.yyyy-MM-dd', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861965828000, // GMT: Mon, 01 Jan 2029 12:43:48 GMT - end: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - oldfile: 'test-rotation.log.2029-01-01', - newfile: 'test-rotation.log.2029-01-15' - } - }; - Object.keys(patterns).forEach(function (pattern) { - describe('when passed the pattern ' + pattern, function () { - var transport; - var rotationLogPath = path.join(fixturesDir, 'rotations'); - - beforeEach(function (done) { - this.time = new Date(patterns[pattern].start); - tk.travel(this.time); - rimraf.sync(rotationLogPath); - mkdirp.sync(rotationLogPath); - transport = new DailyRotateFile({ - filename: path.join(rotationLogPath, 'test-rotation.log'), - datePattern: patterns[pattern].pattern - }); - - done(); - }); - - afterEach(function (done) { - tk.reset(); - done(); - }); - - it('should create log with proper timestamp', function (done) { - var self = this; - - transport.log('error', 'test message', {}, function (err) { - if (err) { - done(err); - } - - var filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(1); - expect(filesCreated).to.include(patterns[pattern].oldfile); - self.time = new Date(patterns[pattern].mid); - tk.travel(self.time); - transport.log('error', '2nd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(1); - expect(filesCreated).to.include(patterns[pattern].oldfile); - self.time = new Date(patterns[pattern].end); - tk.travel(self.time); - transport.log('error', '3rd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(2); - expect(filesCreated).to.include(patterns[pattern].newfile); - transport.close(); - - done(); - }); - }); - }); - }); - }); - }); - }); - - describe('when passed with maxsize and maxfiles', function () { - var dailyRotationPattern = { - pattern: '.yyyy-MM-dd', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861986760000, // GMT: Mon, 01 Jan 2029 18:32:40 GMT - file1: 'test-rotation.log.2029-01-01', - file2: 'test-rotation.log.2029-01-01.1', - file3: 'test-rotation.log.2029-01-01.2' - }; - - describe('when passed the pattern ' + dailyRotationPattern.pattern, function () { - var transport; - var rotationLogPath = path.join(fixturesDir, 'rotations'); - - beforeEach(function (done) { - this.time = new Date(dailyRotationPattern.start); - tk.travel(this.time); - rimraf.sync(rotationLogPath); - mkdirp.sync(rotationLogPath); - transport = new DailyRotateFile({ - filename: path.join(rotationLogPath, 'test-rotation.log'), - datePattern: dailyRotationPattern.pattern, - maxFiles: 2, - maxsize: 100 - }); - - done(); - }); - - afterEach(function (done) { - tk.reset(); - done(); - }); - - it('should properly rotate log with old files getting deleted', function (done) { - var self = this; - - transport.log('error', 'test message with more than 100 bytes data', {}, function (err) { - if (err) { - done(err); - } - - transport.log('error', '2nd test with more than 100 bytes data', {}, function (err) { - if (err) { - done(err); - } - - self.time = new Date(dailyRotationPattern.mid); - tk.travel(self.time); - transport.log('error', '3rd test', {}, function (err) { - if (err) { - done(err); - } - - transport.log('error', '4th test message with more than 100 bytes data', {}, function (err) { - if (err) { - done(err); - } - - var filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(2); - expect(filesCreated).not.to.include(dailyRotationPattern.file1); - expect(filesCreated).to.include(dailyRotationPattern.file2); - expect(filesCreated).to.include(dailyRotationPattern.file3); - done(); - }); - }); - }); - }); - }); - }); - }); - - describe('when passed with maxfiles set and maxsize not set', function () { - var dailyRotationPattern = { - pattern: '.yyyy-MM-dd', - jan1: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - jan2: 1862033560000, // GMT: Mon, 02 Jan 2029 07:32:40 GMT - jan3: 1862119960000, // GMT: Mon, 03 Jan 2029 07:32:40 GMT - file1: 'test-rotation-no-maxsize.log.2029-01-01', - file2: 'test-rotation-no-maxsize.log.2029-01-02', - file3: 'test-rotation-no-maxsize.log.2029-01-03' - }; - - describe('when passed the pattern ' + dailyRotationPattern.pattern + ' and no maxsize', function () { - var transport; - var rotationLogPath = path.join(fixturesDir, 'rotations_no_maxsize'); - - beforeEach(function (done) { - rimraf.sync(rotationLogPath); - mkdirp.sync(rotationLogPath); - transport = new DailyRotateFile({ - filename: path.join(rotationLogPath, 'test-rotation-no-maxsize.log'), - datePattern: dailyRotationPattern.pattern, - maxFiles: 2 - }); - - done(); - }); - - afterEach(function () { - tk.reset(); - }); - - it('should properly rotate log without maxzsize set and with old files getting deleted', function (done) { - var self = this; - self.time = new Date(dailyRotationPattern.jan1); - tk.travel(self.time); - - transport.log('error', 'test message on Jan 1st', {}, function (err) { - if (err) { - done(err); - } - - self.time = new Date(dailyRotationPattern.jan2); - tk.travel(self.time); - - transport.log('error', 'test message on Jan 2nd', {}, function (err) { - if (err) { - done(err); - } - - self.time = new Date(dailyRotationPattern.jan3); - tk.travel(self.time); - - transport.log('error', 'test message on Jan 3rd', {}, function (err) { - if (err) { - done(err); - } - - self.time = new Date(dailyRotationPattern.jan3); - tk.travel(self.time); - - transport.log('error', 'second test message on Jan 3rd', {}, function (err) { - if (err) { - done(err); - } - - var filesCreated = fs.readdirSync(rotationLogPath); - console.log('files : ' + filesCreated); - expect(filesCreated.length).to.eql(2); - expect(filesCreated).not.to.include(dailyRotationPattern.file1); - expect(filesCreated).to.include(dailyRotationPattern.file2); - expect(filesCreated).to.include(dailyRotationPattern.file3); - done(); - }); - }); - }); - }); - }); - }); - }); - - describe('when zippedArchive is true and passed an valid filename with different date patterns for log rotation', function () { - // patterns having one start timestamp for which log file will be creted, - // then one mid timestamp for which log file should not be rotated, - // and finally one end timestamp for which log file should be rotated and - // new logfile should be created. - var patterns = { - 'full year pattern .yyyy': { - pattern: '.yyyy', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1874993560000, // GMT: Fri, 01 Jun 2029 07:32:40 GMT - end: 1893483160000, // GMT: Tue, 01 Jan 2030 07:32:40 GMT - oldfile: 'test-rotation.log.2029', - newfile: 'test-rotation.log.2030' - }, - 'small year pattern .yy': { - pattern: '.yy', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1874993560000, // GMT: Fri, 01 Jun 2029 07:32:40 GMT - end: 1893483160000, // GMT: Tue, 01 Jan 2030 07:32:40 GMT - oldfile: 'test-rotation.log.29', - newfile: 'test-rotation.log.30' - }, - 'month pattern .M': { - pattern: '.M', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - end: 1864625560000, // GMT: Thu, 01 Feb 2029 07:32:40 GMT - oldfile: 'test-rotation.log.1', - newfile: 'test-rotation.log.2' - }, - 'zero padded month pattern .MM': { - pattern: '.MM', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - end: 1864625560000, // GMT: Thu, 01 Feb 2029 07:32:40 GMT - oldfile: 'test-rotation.log.01', - newfile: 'test-rotation.log.02' - }, - 'daypattern .d': { - pattern: '.d', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861986760000, // GMT: Mon, 01 Jan 2029 18:32:40 GMT - end: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - oldfile: 'test-rotation.log.1', - newfile: 'test-rotation.log.15' - }, - 'zero padded day pattern .dd': { - pattern: '.dd', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861986760000, // GMT: Mon, 01 Jan 2029 18:32:40 GMT - end: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - oldfile: 'test-rotation.log.01', - newfile: 'test-rotation.log.15' - }, - 'hour pattern .H': { - pattern: '.H', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - end: 1861950760000, // GMT: Mon, 01 Jan 2029 08:32:40 GMT - oldfile: 'test-rotation.log.7', - newfile: 'test-rotation.log.8' - }, - 'zero padded hour pattern .HH': { - pattern: '.HH', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - end: 1861950760000, // GMT: Mon, 01 Jan 2029 08:32:40 GMT - oldfile: 'test-rotation.log.07', - newfile: 'test-rotation.log.08' - }, - 'minute pattern .m': { - pattern: '.m', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - mid: 1861947170000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - end: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - oldfile: 'test-rotation.log.32', - newfile: 'test-rotation.log.42' - }, - 'zero padded minute pattern .mm': { - pattern: '.mm', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - mid: 1861947170000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - end: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - oldfile: 'test-rotation.log.32', - newfile: 'test-rotation.log.42' - }, - 'daily rotation pattern .yyyy-MM-dd': { - pattern: '.yyyy-MM-dd', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:40 GMT - mid: 1861965828000, // GMT: Mon, 01 Jan 2029 12:43:48 GMT - end: 1863156760000, // GMT: Mon, 15 Jan 2029 07:32:40 GMT - oldfile: 'test-rotation.log.2029-01-01', - newfile: 'test-rotation.log.2029-01-15' - } - }; - Object.keys(patterns).forEach(function (pattern) { - describe('when passed the pattern ' + pattern, function () { - var transport; - var rotationLogPath = path.join(fixturesDir, 'rotations'); - - beforeEach(function (done) { - this.time = new Date(patterns[pattern].start); - tk.travel(this.time); - rimraf.sync(rotationLogPath); - mkdirp.sync(rotationLogPath); - transport = new DailyRotateFile({ - filename: path.join(rotationLogPath, 'test-rotation.log'), - datePattern: patterns[pattern].pattern, - zippedArchive: true - }); - - done(); - }); - - afterEach(function () { - tk.reset(); - }); - - it('should create log with proper timestamp', function (done) { - var self = this; - - transport.log('error', 'test message', {}, function (err) { - if (err) { - done(err); - } - - var filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(1); - expect(filesCreated).to.include(patterns[pattern].oldfile); - self.time = new Date(patterns[pattern].mid); - tk.travel(self.time); - transport.log('error', '2nd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(1); - expect(filesCreated).to.include(patterns[pattern].oldfile); - self.time = new Date(patterns[pattern].end); - tk.travel(self.time); - transport.log('error', '3rd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(2); - expect(filesCreated).to.include(patterns[pattern].newfile); - expect(filesCreated).to.include(patterns[pattern].oldfile + '.gz'); - transport.close(); - - done(); - }); - }); - }); - }); - }); - }); - }); - }); - - describe('when zippedArchive is true', function () { - var pattern = { - pattern: '.m', - start: 1861947160000, // GMT: Mon, 01 Jan 2029 07:32:50 GMT - mid: 1861947240000, // GMT: Mon, 01 Jan 2029 07:34:00 GMT - end: 1861947760000, // GMT: Mon, 01 Jan 2029 07:42:40 GMT - oldfile: 'test-rotation.log.32', - midfile: 'test-rotation.log.34', - newfile: 'test-rotation.log.42' - }; - - var transport; - var rotationLogPath = path.join(fixturesDir, 'rotations'); - beforeEach(function (done) { - this.time = new Date(pattern.start); - tk.travel(this.time); - rimraf.sync(rotationLogPath); - mkdirp.sync(rotationLogPath); - transport = new DailyRotateFile({ - filename: path.join(rotationLogPath, 'test-rotation.log'), - datePattern: pattern.pattern, - zippedArchive: true - }); - - done(); - }); - - afterEach(function () { - tk.reset(); - }); - - it('should compress logfile even if there is only one log message', function (done) { - var self = this; - - transport.log('error', 'test message', {}, function (err) { - if (err) { - done(err); - } - - var filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(1); - expect(filesCreated).to.include(pattern.oldfile); - self.time = new Date(pattern.end); - tk.travel(self.time); - transport.log('error', '2nd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(2); - expect(filesCreated).to.include(pattern.newfile); - expect(filesCreated).to.include(pattern.oldfile + '.gz'); - transport.close(); - - done(); - }); - }); - }); - - it('should compress logfile without adding count to file extension if there are no files with the same name', function (done) { - var self = this; - - transport.log('error', 'test message', {}, function (err) { - if (err) { - done(err); - } - - var filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(1); - expect(filesCreated).to.include(pattern.oldfile); - self.time = new Date(pattern.mid); - tk.travel(self.time); - transport.log('error', '2nd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(2); - expect(filesCreated).to.include(pattern.oldfile + '.gz'); - expect(filesCreated).to.include(pattern.midfile); - self.time = new Date(pattern.end); - tk.travel(self.time); - transport.log('error', '3rd test message', {}, function (err) { - if (err) { - done(err); - } - - filesCreated = fs.readdirSync(rotationLogPath); - expect(filesCreated.length).to.eql(3); - expect(filesCreated).to.not.include(pattern.newfile + '.1'); - expect(filesCreated).to.include(pattern.newfile); - expect(filesCreated).to.include(pattern.midfile + '.gz'); - expect(filesCreated).to.include(pattern.oldfile + '.gz'); - transport.close(); - - done(); - }); - }); - }); - }); - }); -}); diff --git a/test/transport-tests.js b/test/transport-tests.js new file mode 100644 index 0000000..4839507 --- /dev/null +++ b/test/transport-tests.js @@ -0,0 +1,191 @@ +/* eslint-disable max-nested-callbacks,no-unused-expressions,handle-callback-err */ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var expect = require('chai').expect; +var rimraf = require('rimraf'); +var moment = require('moment'); +var semver = require('semver'); +var winston = require('winston'); +var MemoryStream = require('./memory-stream'); +var randomString = require('./random-string'); +var DailyRotateFile = require('../daily-rotate-file'); + +function sendLogItem(transport, level, message, meta, cb) { // eslint-disable-line max-params + if (semver.major(winston.version) === 2) { + transport.log(level, message, meta, cb); + } else { + var logger = winston.createLogger({ + transports: [transport] + }); + + transport.on('logged', function () { + if (cb) { + cb(null, true); + } + }); + + logger.info({ + level: level, + message: message + }); + } +} + +describe('winston/transports/daily-rotate-file', function () { + beforeEach(function () { + this.stream = new MemoryStream(); + this.transport = new DailyRotateFile({ + json: true, + stream: this.stream + }); + }); + + it('should have the proper methods defined', function () { + var transport = new DailyRotateFile({stream: new MemoryStream()}); + expect(transport).to.be.instanceOf(DailyRotateFile); + expect(transport).to.respondTo('log'); + expect(transport).to.respondTo('query'); + }); + + it('should write to the stream', function (done) { + var self = this; + sendLogItem(this.transport, 'info', 'this message should write to the stream', {}, function (err, logged) { + expect(err).to.be.null; + expect(logged).to.be.true; + var logEntry = JSON.parse(self.stream.toString()); + expect(logEntry.level).to.equal('info'); + expect(logEntry.message).to.equal('this message should write to the stream'); + done(); + }); + }); + + describe('when passed metadata', function () { + var circular = {}; + circular.metadata = circular; + + var params = { + no: {}, + object: {metadata: true}, + primitive: 'metadata', + circular: circular + }; + + Object.keys(params).forEach(function (param) { + it('should accept log messages with ' + param + ' metadata', function (done) { + sendLogItem(this.transport, 'info', 'test log message', params[param], function (err, logged) { + expect(err).to.be.null; + expect(logged).to.be.true; + // TODO parse the metadata value to make sure its set properly + done(); + }); + }); + }); + }); + + describe('when using a filename or dirname', function () { + var logDir = path.join(__dirname, 'logs'); + var now = moment().format('YYYY-MM-DD-HH'); + var filename = path.join(logDir, 'application-' + now + '.log'); + var options = { + json: true, + dirname: logDir, + filename: 'application-%DATE%.log', + datePattern: 'YYYY-MM-DD-HH' + }; + + beforeEach(function (done) { + var self = this; + rimraf(logDir, function () { + self.transport = new DailyRotateFile(options); + done(); + }); + }); + + it('should write to the file', function (done) { + this.transport.on('finish', function () { + var logEntries = fs.readFileSync(filename).toString().split('\n').slice(0, -1); + expect(logEntries.length).to.equal(1); + + var logEntry = JSON.parse(logEntries[0]); + expect(logEntry.level).to.equal('info'); + expect(logEntry.message).to.equal('this message should write to the file'); + done(); + }); + + sendLogItem(this.transport, 'info', 'this message should write to the file', {}, function (err, logged) { + expect(err).to.be.null; + expect(logged).to.be.true; + }); + + this.transport.close(); + }); + + it('should not allow the stream to be set', function () { + var opts = Object.assign({}, options); + opts.stream = new MemoryStream(); + expect(function () { + var transport = new DailyRotateFile(opts); + expect(transport).to.not.be.null; + }).to.throw(); + }); + + describe('when setting zippedArchive', function () { + it('should archive the log after rotating', function (done) { + var self = this; + var opts = Object.assign({}, options); + opts.zippedArchive = true; + opts.maxSize = '1k'; + + this.transport = new DailyRotateFile(opts); + + this.transport.on('finish', function () { + fs.readdir(logDir, function (err, files) { + expect(files.filter(function (file) { + return path.extname(file) === '.gz'; + }).length).to.equal(1); + done(); + }); + }); + sendLogItem(this.transport, 'info', randomString(1056)); + sendLogItem(this.transport, 'info', randomString(1056)); + self.transport.close(); + }); + }); + + describe('query', function () { + it('should call callback when no files are present', function () { + this.transport.query(function (err, results) { + expect(results).to.not.be.null; + expect(results.length).to.equal(0); + }); + }); + + it('should raise error when calling with stream', function () { + expect(function () { + var transport = new DailyRotateFile({stream: new MemoryStream()}); + transport.query(null); + }).to.throw(); + }); + + it('should return log entries that match the query', function (done) { + sendLogItem(this.transport, 'info', randomString(1056)); + sendLogItem(this.transport, 'info', randomString(1056)); + sendLogItem(this.transport, 'info', randomString(1056)); + sendLogItem(this.transport, 'info', randomString(1056)); + + var self = this; + this.transport.on('finish', function () { + self.transport.query(function (err, results) { + expect(results).to.not.be.null; + expect(results.length).to.equal(4); + done(); + }); + }); + + this.transport.close(); + }); + }); + }); +});