You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
6.1 KiB
242 lines
6.1 KiB
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;
|
|
}
|
|
|
|
// attempt to create the directory
|
|
const mkdir = (dir) => {
|
|
try {
|
|
return fs.mkdirSync(dir, {recursive: true});
|
|
}
|
|
// backward-compatible fs.mkdirSync for nodejs pre-10.12.0 (without recursive option)
|
|
catch (e) {
|
|
// recursive creation of parent first
|
|
if (e.code === 'ENOENT') {
|
|
mkdir(path.dirname(dir));
|
|
return mkdir(dir);
|
|
}
|
|
|
|
// throw error for all except EEXIST and EROFS (read-only filesystem)
|
|
if (e.code !== 'EEXIST' && e.code !== 'EROFS') {
|
|
throw e;
|
|
}
|
|
|
|
// EEXIST: throw if file and not directory
|
|
// EROFS : throw if directory not found
|
|
else {
|
|
try {
|
|
if (fs.statSync(dir).isDirectory()) {
|
|
return dir;
|
|
}
|
|
throw e;
|
|
} catch (err) {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
mkdir(path.dirname(file));
|
|
|
|
// 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;
|
|
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 (that.backups === 0) {
|
|
fs.truncateSync(filename, 0);
|
|
} else 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 && numBackups !== 0) ? 5 : 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 || 0o600
|
|
};
|
|
|
|
return fileAppender(
|
|
config.filename,
|
|
layout,
|
|
config.maxLogSize,
|
|
config.backups,
|
|
config.timezoneOffset,
|
|
options
|
|
);
|
|
}
|
|
|
|
module.exports.configure = configure;
|