[6a3a178] | 1 | const debug = require('debug')('log4js:fileSync');
|
---|
| 2 | const path = require('path');
|
---|
| 3 | const fs = require('fs');
|
---|
| 4 | const os = require('os');
|
---|
| 5 |
|
---|
| 6 | const eol = os.EOL || '\n';
|
---|
| 7 |
|
---|
| 8 | function touchFile(file, options) {
|
---|
| 9 | // if the file exists, nothing to do
|
---|
| 10 | if (fs.existsSync(file)) {
|
---|
| 11 | return;
|
---|
| 12 | }
|
---|
| 13 |
|
---|
| 14 | // touch the file to apply flags (like w to truncate the file)
|
---|
| 15 | const id = fs.openSync(file, options.flags, options.mode);
|
---|
| 16 | fs.closeSync(id);
|
---|
| 17 | }
|
---|
| 18 |
|
---|
| 19 | class RollingFileSync {
|
---|
| 20 | constructor(filename, size, backups, options) {
|
---|
| 21 | debug('In RollingFileStream');
|
---|
| 22 |
|
---|
| 23 | function throwErrorIfArgumentsAreNotValid() {
|
---|
| 24 | if (!filename || !size || size <= 0) {
|
---|
| 25 | throw new Error('You must specify a filename and file size');
|
---|
| 26 | }
|
---|
| 27 | }
|
---|
| 28 |
|
---|
| 29 | throwErrorIfArgumentsAreNotValid();
|
---|
| 30 |
|
---|
| 31 | this.filename = filename;
|
---|
| 32 | this.size = size;
|
---|
| 33 | this.backups = backups || 1;
|
---|
| 34 | this.options = options;
|
---|
| 35 | this.currentSize = 0;
|
---|
| 36 |
|
---|
| 37 | function currentFileSize(file) {
|
---|
| 38 | let fileSize = 0;
|
---|
| 39 |
|
---|
| 40 | try {
|
---|
| 41 | fileSize = fs.statSync(file).size;
|
---|
| 42 | } catch (e) {
|
---|
| 43 | // file does not exist
|
---|
| 44 | touchFile(file, options);
|
---|
| 45 | }
|
---|
| 46 | return fileSize;
|
---|
| 47 | }
|
---|
| 48 |
|
---|
| 49 | this.currentSize = currentFileSize(this.filename);
|
---|
| 50 | }
|
---|
| 51 |
|
---|
| 52 | shouldRoll() {
|
---|
| 53 | debug('should roll with current size %d, and max size %d', this.currentSize, this.size);
|
---|
| 54 | return this.currentSize >= this.size;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | roll(filename) {
|
---|
| 58 | const that = this;
|
---|
| 59 | const nameMatcher = new RegExp(`^${path.basename(filename)}`);
|
---|
| 60 |
|
---|
| 61 | function justTheseFiles(item) {
|
---|
| 62 | return nameMatcher.test(item);
|
---|
| 63 | }
|
---|
| 64 |
|
---|
| 65 | function index(filename_) {
|
---|
| 66 | return parseInt(filename_.substring((`${path.basename(filename)}.`).length), 10) || 0;
|
---|
| 67 | }
|
---|
| 68 |
|
---|
| 69 | function byIndex(a, b) {
|
---|
| 70 | if (index(a) > index(b)) {
|
---|
| 71 | return 1;
|
---|
| 72 | }
|
---|
| 73 | if (index(a) < index(b)) {
|
---|
| 74 | return -1;
|
---|
| 75 | }
|
---|
| 76 |
|
---|
| 77 | return 0;
|
---|
| 78 | }
|
---|
| 79 |
|
---|
| 80 | function increaseFileIndex(fileToRename) {
|
---|
| 81 | const idx = index(fileToRename);
|
---|
| 82 | debug(`Index of ${fileToRename} is ${idx}`);
|
---|
| 83 | if (idx < that.backups) {
|
---|
| 84 | // on windows, you can get a EEXIST error if you rename a file to an existing file
|
---|
| 85 | // so, we'll try to delete the file we're renaming to first
|
---|
| 86 | try {
|
---|
| 87 | fs.unlinkSync(`${filename}.${idx + 1}`);
|
---|
| 88 | } catch (e) {
|
---|
| 89 | // ignore err: if we could not delete, it's most likely that it doesn't exist
|
---|
| 90 | }
|
---|
| 91 |
|
---|
| 92 | debug(`Renaming ${fileToRename} -> ${filename}.${idx + 1}`);
|
---|
| 93 | fs.renameSync(path.join(path.dirname(filename), fileToRename), `${filename}.${idx + 1}`);
|
---|
| 94 | }
|
---|
| 95 | }
|
---|
| 96 |
|
---|
| 97 | function renameTheFiles() {
|
---|
| 98 | // roll the backups (rename file.n to file.n+1, where n <= numBackups)
|
---|
| 99 | debug('Renaming the old files');
|
---|
| 100 |
|
---|
| 101 | const files = fs.readdirSync(path.dirname(filename));
|
---|
| 102 | files.filter(justTheseFiles).sort(byIndex).reverse().forEach(increaseFileIndex);
|
---|
| 103 | }
|
---|
| 104 |
|
---|
| 105 | debug('Rolling, rolling, rolling');
|
---|
| 106 | renameTheFiles();
|
---|
| 107 | }
|
---|
| 108 |
|
---|
| 109 | /* eslint no-unused-vars:0 */
|
---|
| 110 | write(chunk, encoding) {
|
---|
| 111 | const that = this;
|
---|
| 112 |
|
---|
| 113 |
|
---|
| 114 | function writeTheChunk() {
|
---|
| 115 | debug('writing the chunk to the file');
|
---|
| 116 | that.currentSize += chunk.length;
|
---|
| 117 | fs.appendFileSync(that.filename, chunk);
|
---|
| 118 | }
|
---|
| 119 |
|
---|
| 120 | debug('in write');
|
---|
| 121 |
|
---|
| 122 |
|
---|
| 123 | if (this.shouldRoll()) {
|
---|
| 124 | this.currentSize = 0;
|
---|
| 125 | this.roll(this.filename);
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | writeTheChunk();
|
---|
| 129 | }
|
---|
| 130 | }
|
---|
| 131 |
|
---|
| 132 | /**
|
---|
| 133 | * File Appender writing the logs to a text file. Supports rolling of logs by size.
|
---|
| 134 | *
|
---|
| 135 | * @param file file log messages will be written to
|
---|
| 136 | * @param layout a function that takes a logevent and returns a string
|
---|
| 137 | * (defaults to basicLayout).
|
---|
| 138 | * @param logSize - the maximum size (in bytes) for a log file,
|
---|
| 139 | * if not provided then logs won't be rotated.
|
---|
| 140 | * @param numBackups - the number of log files to keep after logSize
|
---|
| 141 | * has been reached (default 5)
|
---|
| 142 | * @param timezoneOffset - optional timezone offset in minutes
|
---|
| 143 | * (default system local)
|
---|
| 144 | * @param options - passed as is to fs options
|
---|
| 145 | */
|
---|
| 146 | function fileAppender(file, layout, logSize, numBackups, timezoneOffset, options) {
|
---|
| 147 | debug('fileSync appender created');
|
---|
| 148 | file = path.normalize(file);
|
---|
| 149 | numBackups = numBackups === undefined ? 5 : numBackups;
|
---|
| 150 | // there has to be at least one backup if logSize has been specified
|
---|
| 151 | numBackups = numBackups === 0 ? 1 : numBackups;
|
---|
| 152 |
|
---|
| 153 | function openTheStream(filePath, fileSize, numFiles) {
|
---|
| 154 | let stream;
|
---|
| 155 |
|
---|
| 156 | if (fileSize) {
|
---|
| 157 | stream = new RollingFileSync(
|
---|
| 158 | filePath,
|
---|
| 159 | fileSize,
|
---|
| 160 | numFiles,
|
---|
| 161 | options
|
---|
| 162 | );
|
---|
| 163 | } else {
|
---|
| 164 | stream = (((f) => {
|
---|
| 165 | // touch the file to apply flags (like w to truncate the file)
|
---|
| 166 | touchFile(f, options);
|
---|
| 167 |
|
---|
| 168 | return {
|
---|
| 169 | write(data) {
|
---|
| 170 | fs.appendFileSync(f, data);
|
---|
| 171 | }
|
---|
| 172 | };
|
---|
| 173 | }))(filePath);
|
---|
| 174 | }
|
---|
| 175 |
|
---|
| 176 | return stream;
|
---|
| 177 | }
|
---|
| 178 |
|
---|
| 179 | const logFile = openTheStream(file, logSize, numBackups);
|
---|
| 180 |
|
---|
| 181 | return (loggingEvent) => {
|
---|
| 182 | logFile.write(layout(loggingEvent, timezoneOffset) + eol);
|
---|
| 183 | };
|
---|
| 184 | }
|
---|
| 185 |
|
---|
| 186 | function configure(config, layouts) {
|
---|
| 187 | let layout = layouts.basicLayout;
|
---|
| 188 | if (config.layout) {
|
---|
| 189 | layout = layouts.layout(config.layout.type, config.layout);
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | const options = {
|
---|
| 193 | flags: config.flags || 'a',
|
---|
| 194 | encoding: config.encoding || 'utf8',
|
---|
| 195 | mode: config.mode || 0o644
|
---|
| 196 | };
|
---|
| 197 |
|
---|
| 198 | return fileAppender(
|
---|
| 199 | config.filename,
|
---|
| 200 | layout,
|
---|
| 201 | config.maxLogSize,
|
---|
| 202 | config.backups,
|
---|
| 203 | config.timezoneOffset,
|
---|
| 204 | options
|
---|
| 205 | );
|
---|
| 206 | }
|
---|
| 207 |
|
---|
| 208 | module.exports.configure = configure;
|
---|