UNPKG

logrotate

Version:

Log rotation executable / library

207 lines (175 loc) 3.97 kB
/** * Module dependencies. */ var Emitter = require('events').EventEmitter; var debug = require('debug')('logrotate'); var Batch = require('batch'); var path = require('path'); var bytes = require('bytes'); var basename = path.basename; var dirname = path.dirname; var extname = path.extname; var resolve = path.resolve; var cp = require('cp'); var fs = require('fs'); var ms = require('ms'); /** * Expose `Rotator`. */ module.exports = Rotator; /** * Initialize a new `Rotator` with the given `opts`. * * - `maxSize` max file size * - `maxFiles` max file count * * @param {Object} [opts] * @api public */ function Rotator(opts) { opts = opts || {}; this.files = []; this.maxFiles = opts.maxFiles || 5; this.maxSize = opts.maxSize || bytes('50mb'); } /** * Inherit from `Emitter.prototype`. */ Rotator.prototype.__proto__ = Emitter.prototype; /** * Mark `files` for watching. * * @param {Array|String} file or files * @api public */ Rotator.prototype.watch = function(files){ if (Array.isArray(files)) { debug('watch %j', files); this.files = this.files.concat(files); } else { this.watch([files]); } }; /** * Get a destination filename for `file`'s copy. * * @param {Object} file * @param {function} fn * @api private */ Rotator.prototype.destination = function(file, fn){ var ext = extname(file.path); var base = basename(file.path, ext); var dir = dirname(file.path); var now = new Date; var ts = [now.getFullYear(), now.getMonth(), now.getDate()].join('-'); var dst = resolve(dir, base + '--' + ts + ext); fn(null, dst); }; /** * Perform GC on old rotated files. * * @param {Object} file * @param {Function} fn * @api private */ Rotator.prototype.gc = function(file, fn){ debug('gc %s', file); var ext = extname(file.path); var base = basename(file.path, ext); var dir = dirname(file.path); // TODO: finish me }; /** * Truncate `file`. * * @param {String} file * @param {Function} fn * @api private */ Rotator.prototype.truncate = function(file, fn){ fs.open(file, 'w', function(err, fd) { if (err) return fn(err); fs.truncate(fd, 0, function(err) { fs.close(fd, function(err2) { fn(err || err2); }); }); }); }; /** * Rotate `file`. * * @param {Object} file * @param {Function} fn * @api private */ Rotator.prototype.rotate = function(file, fn){ var self = this; // debug('rotate %s', file.path); // this.destination(file, function(err, dst){ // if (err) return fn(err); self.emit('start', file); // debug('to %s', dst); // cp(file.path, dst, function(err){ // if (err) return fn(err); // debug('copied -- truncating %s', file.path); debug('truncating %s', file.path); self.truncate(file.path, function(err){ if (err) return fn(err); debug('truncated %s', file.path); self.emit('complete', file); fn(); // self.gc(file, fn); }); // }); // }); }; /** * Check if `file` needs to be rotated. * * @param {String} file * @param {Function} fn * @api private */ Rotator.prototype.check = function(file, fn){ var self = this; debug('check %s', file); fs.stat(file, function(err, s){ if (err) return fn(err); s.path = file; // check size debug('size of %s is %s', file, bytes(s.size)); if (s.size > self.maxSize) { self.rotate(s, fn); return; } fn(null, s); }); }; /** * Start rotation check and invoke `fn()`. * * @param {Function} fn * @api public */ Rotator.prototype.start = function(fn){ var self = this; var batch = new Batch; batch.concurrency(10); this.files.forEach(function(file){ batch.push(function(done){ self.check(file, done); }); }); batch.end(function(err, files){ if (err) return fn(err); fn(null, files.filter(empty)); }); }; /** * Filter undefineds. */ function empty(f) { return null != f; }