| 'use strict'; |
| |
| const debug = require('debug')('log4js:fileSync'); |
| const path = require('path'); |
| const fs = require('fs'); |
| const os = require('os'); |
| |
| const eol = os.EOL || '\n'; |
| |
| function touchFile(file, options) { |
| // if the file exists, nothing to do |
| if (fs.existsSync(file)) { |
| return; |
| } |
| |
| // touch the file to apply flags (like w to truncate the file) |
| const id = fs.openSync(file, options.flags, options.mode); |
| fs.closeSync(id); |
| } |
| |
| class RollingFileSync { |
| constructor(filename, size, backups, options) { |
| debug('In RollingFileStream'); |
| |
| function throwErrorIfArgumentsAreNotValid() { |
| if (!filename || !size || size <= 0) { |
| throw new Error('You must specify a filename and file size'); |
| } |
| } |
| |
| throwErrorIfArgumentsAreNotValid(); |
| |
| this.filename = filename; |
| this.size = size; |
| this.backups = backups || 1; |
| this.options = options; |
| this.currentSize = 0; |
| |
| function currentFileSize(file) { |
| let fileSize = 0; |
| |
| try { |
| fileSize = fs.statSync(file).size; |
| } catch (e) { |
| // file does not exist |
| touchFile(file, options); |
| } |
| return fileSize; |
| } |
| |
| this.currentSize = currentFileSize(this.filename); |
| } |
| |
| shouldRoll() { |
| debug('should roll with current size %d, and max size %d', this.currentSize, this.size); |
| return this.currentSize >= this.size; |
| } |
| |
| roll(filename) { |
| const that = this; |
| const nameMatcher = new RegExp(`^${path.basename(filename)}`); |
| |
| function justTheseFiles(item) { |
| return nameMatcher.test(item); |
| } |
| |
| function index(filename_) { |
| return parseInt(filename_.substring((`${path.basename(filename)}.`).length), 10) || 0; |
| } |
| |
| function byIndex(a, b) { |
| if (index(a) > index(b)) { |
| return 1; |
| } |
| if (index(a) < index(b)) { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| function increaseFileIndex(fileToRename) { |
| const idx = index(fileToRename); |
| debug(`Index of ${fileToRename} is ${idx}`); |
| if (idx < that.backups) { |
| // on windows, you can get a EEXIST error if you rename a file to an existing file |
| // so, we'll try to delete the file we're renaming to first |
| try { |
| fs.unlinkSync(`${filename}.${idx + 1}`); |
| } catch (e) { |
| // ignore err: if we could not delete, it's most likely that it doesn't exist |
| } |
| |
| debug(`Renaming ${fileToRename} -> ${filename}.${idx + 1}`); |
| fs.renameSync(path.join(path.dirname(filename), fileToRename), `${filename}.${idx + 1}`); |
| } |
| } |
| |
| function renameTheFiles() { |
| // roll the backups (rename file.n to file.n+1, where n <= numBackups) |
| debug('Renaming the old files'); |
| |
| const files = fs.readdirSync(path.dirname(filename)); |
| files.filter(justTheseFiles).sort(byIndex).reverse().forEach(increaseFileIndex); |
| } |
| |
| debug('Rolling, rolling, rolling'); |
| renameTheFiles(); |
| } |
| |
| /* eslint no-unused-vars:0 */ |
| write(chunk, encoding) { |
| const that = this; |
| |
| |
| function writeTheChunk() { |
| debug('writing the chunk to the file'); |
| that.currentSize += chunk.length; |
| fs.appendFileSync(that.filename, chunk); |
| } |
| |
| debug('in write'); |
| |
| |
| if (this.shouldRoll()) { |
| this.currentSize = 0; |
| this.roll(this.filename); |
| } |
| |
| writeTheChunk(); |
| } |
| } |
| |
| /** |
| * File Appender writing the logs to a text file. Supports rolling of logs by size. |
| * |
| * @param file file log messages will be written to |
| * @param layout a function that takes a logevent and returns a string |
| * (defaults to basicLayout). |
| * @param logSize - the maximum size (in bytes) for a log file, |
| * if not provided then logs won't be rotated. |
| * @param numBackups - the number of log files to keep after logSize |
| * has been reached (default 5) |
| * @param timezoneOffset - optional timezone offset in minutes |
| * (default system local) |
| * @param options - passed as is to fs options |
| */ |
| function fileAppender(file, layout, logSize, numBackups, timezoneOffset, options) { |
| debug('fileSync appender created'); |
| file = path.normalize(file); |
| numBackups = numBackups === undefined ? 5 : numBackups; |
| // there has to be at least one backup if logSize has been specified |
| numBackups = numBackups === 0 ? 1 : numBackups; |
| |
| function openTheStream(filePath, fileSize, numFiles) { |
| let stream; |
| |
| if (fileSize) { |
| stream = new RollingFileSync( |
| filePath, |
| fileSize, |
| numFiles, |
| options |
| ); |
| } else { |
| stream = (((f) => { |
| // touch the file to apply flags (like w to truncate the file) |
| touchFile(f, options); |
| |
| return { |
| write(data) { |
| fs.appendFileSync(f, data); |
| } |
| }; |
| }))(filePath); |
| } |
| |
| return stream; |
| } |
| |
| const logFile = openTheStream(file, logSize, numBackups); |
| |
| return (loggingEvent) => { |
| logFile.write(layout(loggingEvent, timezoneOffset) + eol); |
| }; |
| } |
| |
| function configure(config, layouts) { |
| let layout = layouts.basicLayout; |
| if (config.layout) { |
| layout = layouts.layout(config.layout.type, config.layout); |
| } |
| |
| const options = { |
| flags: config.flags || 'a', |
| encoding: config.encoding || 'utf8', |
| mode: config.mode || 0o644 |
| }; |
| |
| return fileAppender( |
| config.filename, |
| layout, |
| config.maxLogSize, |
| config.backups, |
| config.timezoneOffset, |
| options |
| ); |
| } |
| |
| module.exports.configure = configure; |