blob: 1e2f07783bb44757679b6f9d2f4e6418679a7371 [file] [log] [blame]
'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;