123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- const debug = require('debug')('log4js:fileSync');
- const path = require('path');
- const fs = require('fs');
- const os = require('os');
- const eol = os.EOL;
- function touchFile(file, options) {
- // 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));
- // try to throw EISDIR, EROFS, EACCES
- fs.appendFileSync(file, "", { mode: options.mode, flag: options.flags });
- }
- class RollingFileSync {
- constructor(filename, maxLogSize, backups, options) {
- debug('In RollingFileStream');
- if (maxLogSize < 0) {
- throw new Error(`maxLogSize (${maxLogSize}) should be > 0`);
- }
- this.filename = filename;
- this.size = maxLogSize;
- 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_.slice((`${path.basename(filename)}.`).length), 10) || 0;
- }
- function byIndex(a, b) {
- return index(a) - index(b);
- }
- 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-disable-next-line no-unused-vars
- 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 the 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 options - options to be passed to the underlying stream
- * @param timezoneOffset - optional timezone offset in minutes (default system local)
- */
- function fileAppender(file, layout, logSize, numBackups, options, timezoneOffset) {
- if (typeof file !== "string" || file.length === 0) {
- throw new Error(`Invalid filename: ${file}`);
- } else if (file.endsWith(path.sep)) {
- throw new Error(`Filename is a directory: ${file}`);
- } else {
- // handle ~ expansion: https://github.com/nodejs/node/issues/684
- // exclude ~ and ~filename as these can be valid files
- file = file.replace(new RegExp(`^~(?=${path.sep}.+)`), os.homedir());
- }
- file = path.normalize(file);
- numBackups = (!numBackups && numBackups !== 0) ? 5 : numBackups;
- debug(
- 'Creating fileSync appender (',
- file, ', ',
- logSize, ', ',
- numBackups, ', ',
- options, ', ',
- timezoneOffset, ')'
- );
- 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,
- options,
- config.timezoneOffset
- );
- }
- module.exports.configure = configure;
|