UNPKG

file-stream-rotator

Version:

Automated stream rotation useful for log files

281 lines (250 loc) 12 kB
import * as fs from "fs" import { toUSVString } from "util"; import { Frequency } from "./enums"; import { Logger } from "./helper"; import { AuditEntry, DateComponents, RotationSettings } from "./types"; // import { format } from 'date-fns' export default class Rotator { settings: RotationSettings private currentSize: number = 0 private lastDate: string = "" private fileIndx: number = 0 constructor(settings: RotationSettings, lastEntry?: AuditEntry){ this.settings = settings // if (!this.settings.format) { // this.settings.frequency = Frequency.none // Logger.log("[FileStreamRotator] Changing type to none as date format is missing"); // return // } switch(this.settings.frequency) { case Frequency.hours: if (!(this.settings.amount && this.settings.amount < 13)) { this.settings.amount = 12 } else if (!(this.settings.amount && this.settings.amount > 0)) { this.settings.amount = 1 } if (!this.isFormatValidForHour()) { // Logger.log(`[FileStreamRotator] Changing type to none as date format does not change every ${this.settings.amount} hours`); Logger.log("Date format not suitable for X hours rotation. Changing date format to 'YMDHm'"); this.settings.format = "YMDHm" // this.settings.frequency = Frequency.none } break; case Frequency.minutes: if (!(this.settings.amount && this.settings.amount < 31)) { this.settings.amount = 30 } else if (!(this.settings.amount && this.settings.amount > 0)) { this.settings.amount = 1 } if (!this.isFormatValidForMinutes()) { this.settings.format = "YMDHm" Logger.log("Date format not suitable for X minutes rotation. Changing date format to 'YMDHm'"); // Logger.log(`[FileStreamRotator] Changing type to none as date format does not change every ${this.settings.amount} minutes`); // this.settings.frequency = Frequency.none } break; case Frequency.daily: if (!this.isFormatValidForDaily()) { this.settings.format = "YMD" Logger.log("Date format not suitable for daily rotation. Changing date format to 'YMD'"); // Logger.log('[FileStreamRotator] Changing type to custom as date format changes more often than once a day or not every day'); // this.settings.frequency = Frequency.date } break; } if (this.settings.frequency !== Frequency.none && !this.settings.filename.match("%DATE%")) { this.settings.filename += ".%DATE%" Logger.log(`Appending date to the end of the filename`); } // if (this.settings.maxSize && lastEntry){ // let date = new Date(lastEntry.date) // let extension = this.settings.extension ?? "" // Logger.debug(this.getDateString(date) == this.getDateString(new Date()), this.getDateString(date), this.getDateString(new Date())) // if (this.getDateString(date) == this.getDateString(new Date())){ // let indx = lastEntry.name.match(RegExp("(\\d+)" + extension.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) // if (indx) { // this.fileIndx = Number(indx) // } // Logger.debug("index found", indx, RegExp("(\\d+)" + extension.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), lastEntry) // var lastEntryFileStats = fs.statSync(lastEntry.name); // if (lastEntryFileStats.size < this.settings.maxSize) { // this.currentSize = lastEntryFileStats.size // } else { // this.fileIndx += 1 // } // } // } this.lastDate = this.getDateString() if (this.settings.maxSize && lastEntry){ let date = new Date(lastEntry.date) let extension = this.settings.extension ?? "" Logger.debug(this.getDateString(date) == this.getDateString(new Date()), this.getDateString(date), this.getDateString(new Date())) if (this.getDateString(date) == this.getDateString(new Date())){ let indx = lastEntry.name.match(RegExp("(\\d+)" + extension.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) if (indx) { this.fileIndx = Number(indx[1]) } // Logger.debug("index found", indx, RegExp("(\\d+)" + extension.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), lastEntry) var fileSize = this.getSizeForFile(this.getNewFilename()) if (fileSize){ this.currentSize = fileSize } this.lastDate = this.getDateString(date) Logger.debug("LOADED LAST ENTRY", this.currentSize, this.lastDate, this.fileIndx) } else { var fileSize = this.getSizeForFile(this.getNewFilename()) if (fileSize){ this.currentSize = fileSize } Logger.debug("CURRENT FILE:", this.getNewFilename(), this.currentSize) } } } private getSizeForFile(file: string): number | undefined { try { if (fs.existsSync(file)) { var fileStats = fs.statSync(this.getNewFilename()); if (fileStats){ return fileStats.size } } } catch(error: any){ return undefined } return undefined } hasMaxSizeReached(): boolean { return this.settings.maxSize ? this.currentSize > this.settings.maxSize : false } shouldRotate(): boolean { let rotateBySize: boolean = this.hasMaxSizeReached() switch(this.settings.frequency) { case Frequency.none: return rotateBySize case Frequency.hours: case Frequency.minutes: case Frequency.date: case Frequency.daily: default: let newDate = this.getDateString() // console.log(">>>", this.lastDate != newDate, this.lastDate, newDate, rotateBySize, this.currentSize) if (this.lastDate != newDate) { return true } else { return rotateBySize } } // return false } private isFormatValidForDaily(): boolean { let date1 = new Date(2022,2,20,1,2,3) let date2 = new Date(2022,2,20,23,55,45) let date3 = new Date(2022,2,21,2,55,45) return this.getDateString(date1) === this.getDateString(date2) && this.getDateString(date1) !== this.getDateString(date3) } private isFormatValidForHour(): boolean { if (!this.settings.amount || this.settings.frequency != Frequency.hours) { return false } let date1 = new Date(2022,2,20,1,2,3) let date2 = new Date(2022,2,20,2+this.settings.amount,55,45) return this.getDateString(date1) !== this.getDateString(date2) } private isFormatValidForMinutes(): boolean { if (!this.settings.amount || this.settings.frequency != Frequency.minutes) { return false } let date1 = new Date(2022,2,20,1,2,3) let date2 = new Date(2022,2,20,1,2+this.settings.amount,45) return this.getDateString(date1) !== this.getDateString(date2) } getDateString(date?: Date): string { let _date: Date = date || new Date() let components: DateComponents = Rotator.getDateComponents(_date, this.settings.utc) let format = this.settings.format if (format) { switch(this.settings.frequency) { case Frequency.hours: if (this.settings.amount){ var hour = Math.floor(components.hour / this.settings.amount) * this.settings.amount components.hour = hour components.minute = 0 components.second = 0 } case Frequency.minutes: if (this.settings.amount){ var minute = Math.floor(components.minute / this.settings.amount) * this.settings.amount components.minute = minute components.second = 0 } } return format?.replace(/D+/, components.day.toString().padStart(2,"0")) .replace(/M+/, components.month.toString().padStart(2,"0")) .replace(/Y+/, components.year.toString()) .replace(/H+/, components.hour.toString().padStart(2,"0")) .replace(/m+/, components.minute.toString().padStart(2,"0")) .replace(/s+/, components.second.toString().padStart(2,"0")) .replace(/A+/, components.hour > 11 ? "PM" : "AM") } return "" } private getFilename(name: string, extension?: string): string { // console.log(name.replace("%DATE%",this.lastDate) + (this.settings.maxSize ? "." + this.fileIndx : "") + (extension ? extension : "")) return name.replace("%DATE%",this.lastDate) + (this.settings.maxSize || this.fileIndx > 0 ? "." + this.fileIndx : "") + (extension ? extension : "") } getNewFilename(): string { return this.getFilename(this.settings.filename, this.settings.extension) } addBytes(bytes: number) { this.currentSize += bytes } rotate(force: boolean = false): string{ // Logger.debug("ROTATE", this.getNewFilename(), this.fileIndx, this.currentSize, this.settings.maxSize) if (force){ this.fileIndx += 1 this.currentSize = 0 this.lastDate = this.getDateString() } else if (this.shouldRotate()){ if (this.hasMaxSizeReached()){ this.fileIndx += 1 } else { this.fileIndx = 0 } this.currentSize = 0 this.lastDate = this.getDateString() } // Logger.debug("ROTATE", this.getNewFilename(), this.fileIndx, this.currentSize, this.settings.maxSize) return this.getNewFilename() } static getDateComponents(date: Date, utc: boolean): DateComponents { if (utc) { return { day: date.getUTCDate(), month: date.getUTCMonth() + 1, year: date.getUTCFullYear(), hour: date.getUTCHours(), minute: date.getUTCMinutes(), second: date.getUTCSeconds(), utc: utc, source: date } } else { return { day: date.getDate(), month: date.getMonth() + 1, year: date.getFullYear(), hour: date.getHours(), minute: date.getMinutes(), second:date.getSeconds(), utc: utc, source: date } } } static createDate(components: DateComponents, utc: boolean): Date { if (utc) { new Date(Date.UTC(components.year, components.month, components.day, components.hour, components.minute, components.second)) } return new Date(components.year, components.month, components.day, components.hour, components.minute, components.second) } }