You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

304 lines
6.7 KiB

"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;