"use strict";
|
|
|
|
var compress = require("./compress");
|
|
var fs = require("fs");
|
|
var interval = require("./interval");
|
|
var path = require("path");
|
|
var util = require("util");
|
|
var utils = require("./utils");
|
|
var Writable = require("stream").Writable;
|
|
|
|
function RotatingFileStream(filename, options) {
|
|
if(! (this instanceof RotatingFileStream)) return new RotatingFileStream(filename, options);
|
|
|
|
options = utils.checkOptions(options);
|
|
|
|
if(typeof filename === "function") this.generator = filename;
|
|
else if(typeof filename === "string")
|
|
if(options.rotate) this.generator = utils.createClassical(filename);
|
|
else this.generator = utils.createGenerator(filename);
|
|
else throw new Error("Don't know how to handle 'filename' type: " + typeof filename);
|
|
|
|
if(options.path) {
|
|
var generator = this.generator;
|
|
|
|
this.generator = function(time, index) {
|
|
return path.join(options.path, generator(time, index));
|
|
};
|
|
}
|
|
|
|
var opt = {};
|
|
|
|
if(options.highWaterMark) opt.highWaterMark = options.highWaterMark;
|
|
|
|
if(options.mode) opt.mode = options.mode;
|
|
|
|
Writable.call(this, opt);
|
|
|
|
this.chunks = [];
|
|
this.options = options;
|
|
this.size = 0;
|
|
this.write = this.write; // https://github.com/iccicci/rotating-file-stream/issues/19
|
|
|
|
utils.setEvents(this);
|
|
|
|
process.nextTick(this.firstOpen.bind(this));
|
|
}
|
|
|
|
util.inherits(RotatingFileStream, Writable);
|
|
|
|
RotatingFileStream.prototype._close = function(done) {
|
|
if(this.stream) {
|
|
this.stream.on("finish", done);
|
|
this.stream.end();
|
|
this.stream = null;
|
|
}
|
|
else done();
|
|
};
|
|
|
|
RotatingFileStream.prototype._rewrite = function() {
|
|
const self = this;
|
|
const callback = function() {
|
|
if(self.ending) self._close(Writable.prototype.end.bind(self));
|
|
};
|
|
|
|
if(this.err) {
|
|
const chunks = this.chunks;
|
|
|
|
this.chunks = [];
|
|
chunks.map(e => {
|
|
if(e.cb) e.cb();
|
|
});
|
|
|
|
return callback();
|
|
}
|
|
|
|
if(this.writing || this.rotation) return;
|
|
if(this.options.size && this.size >= this.options.size) return this.rotate();
|
|
if(! this.stream) return;
|
|
if(! this.chunks.length) return callback();
|
|
|
|
const chunk = this.chunks[0];
|
|
|
|
this.chunks.shift();
|
|
this.size += chunk.chunk.length;
|
|
this.writing = true;
|
|
|
|
this.stream.write(chunk.chunk, function(err) {
|
|
self.writing = false;
|
|
|
|
if(err) self.emit("error", err);
|
|
|
|
if(chunk.cb) chunk.cb();
|
|
|
|
process.nextTick(self._rewrite.bind(self));
|
|
});
|
|
};
|
|
|
|
RotatingFileStream.prototype._write = function(chunk, encoding, callback) {
|
|
this.chunks.push({ chunk: chunk, cb: callback });
|
|
this._rewrite();
|
|
};
|
|
|
|
RotatingFileStream.prototype._writev = function(chunks, callback) {
|
|
chunks[chunks.length - 1].cb = callback;
|
|
this.chunks = this.chunks.concat(chunks);
|
|
this._rewrite();
|
|
};
|
|
|
|
RotatingFileStream.prototype.end = function() {
|
|
var args = [];
|
|
|
|
for(var i = 0; i < arguments.length; ++i) {
|
|
if("function" === typeof arguments[i]) {
|
|
this.once("finish", arguments[i]);
|
|
|
|
break;
|
|
}
|
|
|
|
if(i > 1) break;
|
|
|
|
args.push(arguments[i]);
|
|
}
|
|
|
|
this.ending = true;
|
|
|
|
if(args.length) this.write.apply(this, args);
|
|
else this._rewrite();
|
|
};
|
|
|
|
RotatingFileStream.prototype.firstOpen = function() {
|
|
var self = this;
|
|
|
|
if(this.options.immutable) return this.immutate(true);
|
|
|
|
try {
|
|
this.name = this.generator(null);
|
|
}
|
|
catch(e) {
|
|
return this.emit("error", e);
|
|
}
|
|
|
|
this.once("open", this.interval.bind(this));
|
|
|
|
fs.stat(this.name, function(err, stats) {
|
|
if(err) {
|
|
if(err.code === "ENOENT") return self.open();
|
|
|
|
return self.emit("error", err);
|
|
}
|
|
|
|
if(! stats.isFile()) return self.emit("error", new Error("Can't write on: " + self.name + " (it is not a file)"));
|
|
|
|
if(self.options.initialRotation) {
|
|
var prev;
|
|
|
|
self._interval(self.now());
|
|
prev = self.prev;
|
|
self._interval(stats.mtime.getTime());
|
|
|
|
if(prev !== self.prev) return self.rotate();
|
|
}
|
|
|
|
self.size = stats.size;
|
|
|
|
if(! self.options.size || stats.size < self.options.size) return self.open();
|
|
|
|
if(self.options.interval) self._interval(self.now());
|
|
|
|
self.rotate();
|
|
});
|
|
};
|
|
|
|
RotatingFileStream.prototype.immutate = function(first, index, now) {
|
|
if(! index) {
|
|
index = 1;
|
|
now = new Date(this.now());
|
|
}
|
|
|
|
if(index >= 1001) return this.emit("error", this.exhausted());
|
|
|
|
try {
|
|
this.name = this.generator(now, index);
|
|
}
|
|
catch(e) {
|
|
return this.emit("error", e);
|
|
}
|
|
|
|
var open = function(size) {
|
|
this.size = size;
|
|
this.open();
|
|
this.once(
|
|
"open",
|
|
function() {
|
|
if(! first) this.emit("rotated", this.last);
|
|
|
|
this.last = this.name;
|
|
this.interval();
|
|
}.bind(this)
|
|
);
|
|
}.bind(this);
|
|
|
|
fs.stat(
|
|
this.name,
|
|
function(err, stats) {
|
|
if(err) {
|
|
if(err.code === "ENOENT") return open(0);
|
|
|
|
return this.emit("error", err);
|
|
}
|
|
|
|
if(! stats.isFile()) return this.emit("error", new Error("Can't write on: " + this.name + " (it is not a file)"));
|
|
|
|
if(this.options.size && stats.size >= this.options.size) return this.immutate(first, index + 1, now);
|
|
|
|
open(stats.size);
|
|
}.bind(this)
|
|
);
|
|
};
|
|
|
|
RotatingFileStream.prototype.move = function(retry) {
|
|
var name;
|
|
var self = this;
|
|
|
|
var callback = function(err) {
|
|
if(err) return self.emit("error", err);
|
|
|
|
self.open();
|
|
|
|
if(self.options.compress) self.compress(name);
|
|
else {
|
|
self.emit("rotated", name);
|
|
self.interval();
|
|
}
|
|
};
|
|
|
|
this.findName({}, self.options.compress, function(err, found) {
|
|
if(err) return callback(err);
|
|
|
|
name = found;
|
|
|
|
fs.rename(self.name, name, function(err) {
|
|
if(err && err.code !== "ENOENT" && ! retry) return callback(err);
|
|
|
|
if(! err) return callback();
|
|
|
|
utils.makePath(name, function(err) {
|
|
if(err) return callback(err);
|
|
|
|
self.move(true);
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
RotatingFileStream.prototype.now = function() {
|
|
return Date.now();
|
|
};
|
|
|
|
RotatingFileStream.prototype.open = function(retry) {
|
|
var fd;
|
|
var self = this;
|
|
var options = { flags: "a" };
|
|
var callback = function(err) {
|
|
if(err) self.emit("error", err);
|
|
|
|
process.nextTick(self._rewrite.bind(self));
|
|
};
|
|
|
|
if("mode" in this.options) options.mode = this.options.mode;
|
|
|
|
var stream = fs.createWriteStream(this.name, options);
|
|
|
|
stream.once("open", function() {
|
|
self.stream = stream;
|
|
self.emit("open", self.name);
|
|
|
|
callback();
|
|
});
|
|
|
|
stream.once("error", function(err) {
|
|
if(err.code !== "ENOENT" && ! retry) return callback(err);
|
|
|
|
utils.makePath(self.name, function(err) {
|
|
if(err) return callback(err);
|
|
|
|
self.open(true);
|
|
});
|
|
});
|
|
};
|
|
|
|
RotatingFileStream.prototype.rotate = function() {
|
|
this.size = 0;
|
|
this.rotation = new Date();
|
|
|
|
this.emit("rotation");
|
|
this._clear();
|
|
this._close(this.options.rotate ? this.classical.bind(this, this.options.rotate) : this.options.immutable ? this.immutate.bind(this) : this.move.bind(this));
|
|
};
|
|
|
|
for(var i in compress) RotatingFileStream.prototype[i] = compress[i];
|
|
for(i in interval) RotatingFileStream.prototype[i] = interval[i];
|
|
|
|
module.exports = RotatingFileStream;
|
|
module.exports.default = RotatingFileStream;
|