fileSync.js 5.3 KB

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