logrotate
Version:
Log rotation executable / library
207 lines (175 loc) • 3.97 kB
JavaScript
/**
* 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;
}