Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

rotating-file-stream

Package Overview
Dependencies
Maintainers
1
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rotating-file-stream - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

constructor.js

316

index.js
"use strict";
var fs = require("fs");
var util = require("util");
var Writable = require("stream").Writable;
function checkMeasure(v, what, units) {
var ret = {};
var RotatingFileStream = require("./constructor");
ret.num = parseInt(v);
if(isNaN(ret.num))
throw new Error("Unknown 'options." + what + "' format: " + v);
if(ret.num <= 0)
throw new Error("A positive integer number is expected for 'options." + what + "'");
ret.unit = v.replace(/^[ 0]*/g, "").substr((ret.num + "").length, 1);
if(ret.unit.length === 0)
throw new Error("Missing unit for 'options." + what + "'");
if(! units[ret.unit])
throw new Error("Unknown 'options." + what + "' unit: " + ret.unit);
return ret;
function unexpected(msg) {
throw new Error("Unexpected case ( https://www.npmjs.com/package/rotating-file-stream#unexpected ): " + msg);
}
var intervalUnits = {
m: true,
h: true,
d: true
};
RotatingFileStream.prototype._write = function(chunk, encoding, callback) {
if(this.err)
unexpected("_write after error");
function checkInterval(v) {
var ret = checkMeasure(v, "interval", intervalUnits);
if(this.callback)
unexpected("_write before callback");
switch(ret.unit) {
case "m":
if(parseInt(60 / ret.num) * ret.num != 60)
throw new Error("An integer divider of 60 is expected as minutes for 'options.interval'");
break;
if(! this.stream) {
this.buffer += chunk;
this.callback = callback;
case "h":
if(parseInt(24 / ret.num) * ret.num != 24)
throw new Error("An integer divider of 24 is expected as hours for 'options.interval'");
break;
return;
}
return ret;
}
var self = this;
var sizeUnits = {
K: true,
M: true,
G: true
};
this.size += chunk.length;
this.stream.write(chunk, function(err) {
if(err)
return callback(err);
function checkSize(v) {
var ret = checkMeasure(v, "size", sizeUnits);
if(self.options.size && self.size >= self.options.size)
return self.rotate(callback);
if(ret.unit == "K")
return ret.num * 1024;
callback();
});
};
if(ret.unit == "M")
return ret.num * 1048576;
RotatingFileStream.prototype._writev = function(chunks, callback) {
if(this.err)
unexpected("_writev after error");
return ret.num * 1073741824;
}
if(this.callback)
unexpected("_writev before callback");
function checkOptions(options) {
for(var opt in options) {
var val = options[opt];
var typ = typeof val;
if(! this.stream)
unexpected("_writev while initial rotation");
switch(opt) {
case "compress":
if(! val)
throw new Error("A value for 'options.compress' must be specified");
var buffer = "";
var enough = true;
var i;
var self = this;
if(typ == "boolean")
options.compress = function(src, dst) { return "cat " + src + " | gzip -t9 > " + dst; };
else
if(typ == "string") {
if(val != "bzip" && val != "gzip" && val != "zip")
throw new Error("Don't know how to handle compression method: " + val);
}
else
if(typ != "function")
throw new Error("Don't know how to handle 'options.compress' type: " + typ);
break;
for(i = 0; i < chunks.length && enough; ++i) {
buffer += chunks[i].chunk;
case "highWaterMark":
break;
case "interval":
if(typ != "string")
throw new Error("Don't know how to handle 'options.interval' type: " + typ);
options.interval = checkInterval(val);
break;
case "mode":
break;
case "size":
if(typ != "string")
throw new Error("Don't know how to handle 'options.size' type: " + typ);
options.size = checkSize(val);
break;
default:
throw new Error("Unknown option: " + opt);
}
if(this.options.size && (buffer.length + this.size >= this.options.size))
enough = false;
}
}
function pad(num) {
return (num + "").length == 1 ? "0" + num : num;
}
this.size += buffer.length;
this.stream.write(buffer, function(err) {
if(err)
return self.error(err, callback);
function createGenerator(filename) {
return function(time, index) {
if(! time)
return filename;
if(enough)
return callback();
var month = time.getFullYear() + "" + pad(time.getMonth() + 1);
var day = pad(time.getDate());
var hour = pad(time.getHours());
var minute = pad(time.getMinutes());
for(0; i < chunks.length; ++i)
self.buffer += chunks[i].chunk;
return month + day + "-" + hour + minute + "-" + pad(index) + "-" + filename;
};
}
self.rotate(callback);
});
};
function RotatingFileStream(filename, options) {
if(! (this instanceof RotatingFileStream))
return new RotatingFileStream(filename, options);
RotatingFileStream.prototype.error = function(err, callback) {
if(this.callback)
callback = this.callback;
var generator;
var opt = {};
this.callback = null;
if(typeof filename == "function")
generator = filename;
else
if(typeof filename == "string")
generator = createGenerator(filename);
else
throw new Error("Don't know how to handle 'filename' type: " + typeof filename);
if(callback)
return callback(err);
if(! options)
options = {};
else
if(typeof options != "object")
throw new Error("Don't know how to handle 'options' type: " + typeof options);
this.emit("error", err);
};
checkOptions(options);
if(options.highWaterMark)
opt.highWaterMark = options.highWaterMark;
Writable.call(this);
this.generator = generator;
this.options = options;
this.firstOpen();
}
util.inherits(RotatingFileStream, Writable);
RotatingFileStream.prototype.firstOpen = function() {

@@ -185,5 +100,4 @@ try {

// if file needs to be rotated at start time, do not open it: it will be opened by rotation
if(this.firstRotation())
return;
this.open();
};

@@ -199,3 +113,3 @@

if(e.code == "ENOENT")
return false;
return true;

@@ -208,14 +122,118 @@ throw e;

if(stats.size < this.options.size)
return false;
this.size = stats.size;
this.rotate();
if((! this.options.size) || stats.size < this.options.size)
return true;
return true;
process.nextTick(this.rotate.bind(this));
return false;
};
RotatingFileStream.prototype.rotate = function() {
RotatingFileStream.prototype.move = function(attempts) {
if(! attempts)
attempts = {};
var count = 0;
for(var i in attempts)
count += attempts[i];
if(count >= 1000) {
var err = new Error("Too many destination file attempts");
err.attempts = attempts;
return this.error(err, this.callback);
}
if(this.options.interval)
throw new Error("not implemented yet");
var name = this.generator(this.rotation, count + 1);
var self = this;
fs.stat(name, function(err) {
if((! err) || err.code != "ENOENT" ) {
if(name in attempts)
attempts[name]++;
else
attempts[name] = 1;
return self.move(attempts);
}
if(self.options.compress)
throw new Error("not implemented yet");
fs.rename(self.name, name, function(err) {
if(err)
return self.error(err, this.callback);
self.emit("rotated", name);
self.open();
});
});
};
RotatingFileStream.prototype.open = function() {
var fd;
var self = this;
var callback = function(err) {
var cb = self.callback;
if(cb) {
self.callback = null;
return cb(err);
}
if(err)
throw err;
};
try {
var options = { flags: "a" };
if("mode" in this.options)
options.mode = this.options.mode;
this.stream = fs.createWriteStream(this.name, options);
}
catch(e) {
return callback(e);
}
if(! this.buffer.length)
return callback();
this.stream.write(this.buffer, function(err) {
if(err)
return self.error(err, callback);
callback();
});
this.size += this.buffer.length;
this.buffer = "";
};
RotatingFileStream.prototype.rotate = function(callback) {
if(callback)
this.callback = callback;
this.size = 0;
this.rotation = new Date();
this.emit("rotation");
if(this.stream) {
this.stream.on("finish", this.move.bind(this));
this.stream.end();
this.stream = null;
}
else
this.move();
};
module.exports = RotatingFileStream;
{
"name": "rotating-file-stream",
"version": "0.0.1",
"version": "0.0.2",
"description": "Opens a stream.Writable to a file rotated by interval and/or size. A logrotate alternative.",
"scripts": {
"test": "echo '.jshintrc\\n.gitignore\\n.travis.yml\\ntest' > .npmignore ; cat .gitignore >> .npmignore\n ./node_modules/.bin/jshint index.js test || exit 1\n ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --recursive test"
"test": "rm -rf *log ; echo '.jshintrc\\n.gitignore\\n.travis.yml\\ntest\\nsleep.js' > .npmignore ; cat .gitignore >> .npmignore\n ./node_modules/.bin/jshint index.js test || exit 1\n ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --recursive test"
},

@@ -8,0 +8,0 @@ "bugs": "https://github.com/iccicci/rotating-file-stream/issues",

@@ -28,3 +28,3 @@ # rotating-file-stream

Please check the [TODO list](https://github.com/iccicci/rotating-file-stream#todo) to be aware of what is missing.
Please check the [TODO list](#todo) to be aware of what is missing.

@@ -56,4 +56,4 @@ ### Installation

* time: {Date} The start time of rotation period. If __null__, the not rotated file name must be returned.
* index {Number} The progressive index of rotation by size in the same rotation period. Starts from 1.
* time: {Date} If rotation by interval is enabled, the start time of rotation period, otherwise the time when rotation job started. If __null__, the not rotated file name must be returned.
* index {Number} The progressive index of rotation by size in the same rotation period.

@@ -76,3 +76,4 @@ An example of a complex rotated file name generator function could be:

return "/storage/" + month + "/" + month + day + "-" + hour + minute + "-" + index + "-" + filename;
return "/storage/" + month + "/" +
month + day + "-" + hour + minute + "-" + index + "-" + filename;
}

@@ -88,3 +89,2 @@

__Note:__
If part of returned destination path does not exists, the rotation job will try to create it.

@@ -104,2 +104,3 @@

* __B__: Bites
* __K__: KiloBites

@@ -125,2 +126,3 @@ * __M__: MegaBytes

* __s__: seconds. Accepts integer divider of 60.
* __m__: minutes. Accepts integer divider of 60.

@@ -151,6 +153,8 @@ * __h__: hours. Accepts integer divider of 24.

* gzip
* zip
To enable external compression, a _function_ can be used or simple the _boolean_ __true__ value to use default
external compression. The two following code snippets have exactly the same effect:
external compression.
The function should accept _source_ and _dest_ file names and must return the shell command to be executed to
archive the file.
The two following code snippets have exactly the same effect:

@@ -169,4 +173,4 @@ ```javascript

size: '10M',
compress: function(src, dst) {
return "cat " + src + " | gzip -t9 > " + dst;
compress: function(source, dest) {
return "cat " + source + " | gzip -t9 > " + dest;
}

@@ -176,2 +180,6 @@ });

__Note:__
The shell command to archive the rotated file should not remove the source file, it will be removed by the package
if archive job complete with success.
### Events

@@ -211,6 +219,23 @@

To not waste CPU power checking size for rotation at each _write_, a timer is set up to check size at
every second. This means that rotated file size will be a bit greater than how much specified with
__options.size__ parameter.
### Unexpected
```
If I understood correctly, there are some case which should never happen.
Anyway I want to be sure, so I decided to throw an Error if code runs
through one of these cases.
If it happen that you catch one of these, please make me aware of that as
soon as possible in order to handle the case.
The author
```
### Compatibility
This package is written following __Node.js 4.0__ specifications always taking care about backward
compatibility. The package it tested under following versions:
* 4.0
* 0.12
* 0.11
* 0.10
### Licence

@@ -226,12 +251,13 @@

* Write tests
* Write code
* Emit events
* Rotate by interval
* Create missing directories in paths
* External compression
* Internal compression gzip
* Internal compression bzip
* Internal compression zip
* Test all error case handling
### Changelog
* 2015-09-17 - v0.0.2
* Rotation by size
* 2015-09-14 - v0.0.1

@@ -238,0 +264,0 @@ * README.md

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc