simple-fs-rotator
Version:
Automated stream rotation useful for log files
267 lines (266 loc) • 11.6 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _FileStreamRotator_instances, _FileStreamRotator_write;
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const enums_1 = require("./enums");
const DefaultOptions_1 = require("./DefaultOptions");
const Rotator_1 = require("./Rotator");
const AuditManager_1 = require("./AuditManager");
const helper_1 = require("./helper");
const EventEmitter = require("events");
class FileStreamRotator extends EventEmitter {
// private logWatcher?: FSWatcher
constructor(options, debug = false) {
var _a, _b;
super();
_FileStreamRotator_instances.add(this);
this.writeBuffer = [];
this.writing = false;
this.config = {};
this.config = this.parseOptions(options);
helper_1.Logger.getInstance(options.verbose, debug);
this.auditManager = new AuditManager_1.default((_a = this.config.auditSettings) !== null && _a !== void 0 ? _a : DefaultOptions_1.default.auditSettings({}), this);
let lastEntry = this.auditManager.config.files.slice(-1).shift();
this.rotator = new Rotator_1.default(((_b = this.config.rotationSettings) !== null && _b !== void 0 ? _b : DefaultOptions_1.default.rotationSettings({})), lastEntry);
this.currentFile = this.rotator.getNewFilename();
this.createNewLog(this.currentFile);
this.emit('new', this.currentFile);
}
static getStream(options) {
return new FileStreamRotator(options);
}
parseOptions(options) {
var _a, _b, _c, _d;
let config = {};
config.options = DefaultOptions_1.default.fileStreamRotatorOptions(options);
config.fileOptions = DefaultOptions_1.default.fileOptions((_a = options.file_options) !== null && _a !== void 0 ? _a : {});
let auditSettings = DefaultOptions_1.default.auditSettings({});
if (options.audit_file) {
auditSettings.auditFilename = options.audit_file;
}
if (options.audit_hash_type) {
auditSettings.hashType = options.audit_hash_type;
}
if (options.extension) {
auditSettings.extension = options.extension;
}
if (options.max_logs) {
let params = DefaultOptions_1.default.extractParam(options.max_logs);
auditSettings.keepSettings = {
type: ((_b = params.letter) === null || _b === void 0 ? void 0 : _b.toLowerCase()) == "d" ? enums_1.KeepLogFiles.days : enums_1.KeepLogFiles.fileCount,
amount: params.number
};
}
config.auditSettings = auditSettings;
config.rotationSettings = DefaultOptions_1.default.rotationSettings({ filename: options.filename, extension: options.extension });
if (options.date_format && !options.frequency) {
config.rotationSettings.frequency = enums_1.Frequency.date;
}
else {
config.rotationSettings.frequency = enums_1.Frequency.none;
}
if (options.date_format) {
config.rotationSettings.format = options.date_format;
}
config.rotationSettings.utc = (_c = options.utc) !== null && _c !== void 0 ? _c : false;
switch (options.frequency) {
case "daily":
config.rotationSettings.frequency = enums_1.Frequency.daily;
break;
case "custom":
case "date":
config.rotationSettings.frequency = enums_1.Frequency.date;
break;
case "test":
config.rotationSettings.frequency = enums_1.Frequency.minutes;
config.rotationSettings.amount = 1;
break;
default:
if (options.frequency) {
let params = DefaultOptions_1.default.extractParam(options.frequency);
if ((_d = params.letter) === null || _d === void 0 ? void 0 : _d.match(/^([mh])$/)) {
config.rotationSettings.frequency = params.letter == "h" ? enums_1.Frequency.hours : enums_1.Frequency.minutes;
config.rotationSettings.amount = params.number;
}
}
}
if (options.size) {
let params = DefaultOptions_1.default.extractParam(options.size);
switch (params.letter) {
case 'k':
config.rotationSettings.maxSize = params.number * 1024;
break;
case 'm':
config.rotationSettings.maxSize = params.number * 1024 * 1024;
break;
case 'g':
config.rotationSettings.maxSize = params.number * 1024 * 1024 * 1024;
break;
}
}
config.rotationSettings.keepSettings = auditSettings.keepSettings;
this.rotator = new Rotator_1.default(config.rotationSettings);
return config;
}
rotate(force = false) {
var _a, _b;
let oldFile = this.currentFile;
this.rotator.rotate(force);
this.currentFile = this.rotator.getNewFilename();
// oldfile same as new file. do nothing
if (this.currentFile == oldFile) {
return;
}
// close old file and watcher if exists.
if (this.fs) {
// if (this.logWatcher) {
// this.logWatcher.close()
// }
if (((_a = this.config.options) === null || _a === void 0 ? void 0 : _a.end_stream) === true) {
this.fs.end();
}
else {
this.fs.destroy();
}
}
// add old file to audit
if (oldFile) {
this.auditManager.addLog(oldFile);
}
this.createNewLog(this.currentFile);
this.emit('new', this.currentFile);
if (((_b = this.config.options) === null || _b === void 0 ? void 0 : _b.rotate) && oldFile) {
this.rotatePromise = this.config.options.rotate(oldFile)
.catch(() => { })
.then(() => {
this.rotatePromise = undefined;
});
}
this.emit('rotate', oldFile, this.currentFile, force);
}
createNewLog(filename) {
var _a;
// create new directory if required
(0, helper_1.makeDirectory)(filename);
// add mew file tp audit
this.auditManager.addLog(filename);
// create new file
let streamOptions = {};
if (this.config.fileOptions) {
streamOptions = this.config.fileOptions;
}
this.fs = fs.createWriteStream(filename, streamOptions);
// setup dependencies: proxy events, emit events
this.bubbleEvents(this.fs, filename);
// setup symlink
if ((_a = this.config.options) === null || _a === void 0 ? void 0 : _a.create_symlink) {
this.createCurrentSymLink(filename);
}
}
write(str, encoding) {
this.writeBuffer.push({ str, encoding });
if (this.writing) {
return;
}
this.writing = true;
__classPrivateFieldGet(this, _FileStreamRotator_instances, "m", _FileStreamRotator_write).call(this);
}
flush(cb) {
__classPrivateFieldGet(this, _FileStreamRotator_instances, "m", _FileStreamRotator_write).call(this)
.then(() => {
if (cb)
cb();
});
}
end(str) {
if (this.fs) {
this.fs.end(str);
this.fs = undefined;
}
}
bubbleEvents(emitter, filename) {
emitter.on('close', () => { this.emit('close'); });
emitter.on('finish', () => { this.emit('finish'); });
emitter.on('error', (err) => { this.emit('error', err); });
emitter.on('open', (fd) => { this.emit('open', filename); });
}
createCurrentSymLink(logfile) {
var _a, _b;
if (!logfile) {
return;
}
let symLinkName = (_b = (_a = this.config.options) === null || _a === void 0 ? void 0 : _a.symlink_name) !== null && _b !== void 0 ? _b : "current.log";
let logPath = path.dirname(logfile);
let logfileName = path.basename(logfile);
let current = logPath + path.sep + symLinkName;
try {
if (fs.existsSync(current)) {
let stats = fs.lstatSync(current);
if (stats.isSymbolicLink()) {
fs.unlinkSync(current);
fs.symlinkSync(logfileName, current);
return;
}
helper_1.Logger.verbose("Could not create symlink file as file with the same name exists: ", current);
}
else {
fs.symlinkSync(logfileName, current);
}
}
catch (err) {
helper_1.Logger.verbose("[Could not create symlink file: ", current, ' -> ', logfileName);
helper_1.Logger.debug("error creating sym link", current, err);
}
}
test() {
return { config: this.config, rotator: this.rotator };
}
}
exports.default = FileStreamRotator;
_FileStreamRotator_instances = new WeakSet(), _FileStreamRotator_write = function _FileStreamRotator_write() {
return __awaiter(this, void 0, void 0, function* () {
const buffers = this.writeBuffer.splice(0);
for (const buffer of buffers) {
if (this.rotatePromise != null) {
try {
yield this.rotatePromise;
}
catch (_a) { }
}
yield new Promise(resolve => {
if (this.fs) {
this.fs.write(buffer.str, buffer.encoding || "utf8", err => {
if (err == null) {
this.rotator.addBytes(Buffer.byteLength(buffer.str, buffer.encoding));
}
resolve();
});
}
});
if (this.rotatePromise == null && this.rotator.hasMaxSizeReached()) {
this.rotate();
}
}
if (this.writeBuffer.length > 0) {
yield __classPrivateFieldGet(this, _FileStreamRotator_instances, "m", _FileStreamRotator_write).call(this);
}
else {
this.writing = false;
}
});
};