fileSync.js 5.3 KB

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