file.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. const debug = require('debug')('log4js:file');
  2. const path = require('path');
  3. const streams = require('streamroller');
  4. const os = require('os');
  5. const eol = os.EOL;
  6. let mainSighupListenerStarted = false;
  7. const sighupListeners = new Set();
  8. function mainSighupHandler() {
  9. sighupListeners.forEach((app) => {
  10. app.sighupHandler();
  11. });
  12. }
  13. /**
  14. * File Appender writing the logs to a text file. Supports rolling of logs by size.
  15. *
  16. * @param file the file log messages will be written to
  17. * @param layout a function that takes a logEvent and returns a string
  18. * (defaults to basicLayout).
  19. * @param logSize - the maximum size (in bytes) for a log file,
  20. * if not provided then logs won't be rotated.
  21. * @param numBackups - the number of log files to keep after logSize
  22. * has been reached (default 5)
  23. * @param options - options to be passed to the underlying stream
  24. * @param timezoneOffset - optional timezone offset in minutes (default system local)
  25. */
  26. function fileAppender(
  27. file,
  28. layout,
  29. logSize,
  30. numBackups,
  31. options,
  32. timezoneOffset
  33. ) {
  34. if (typeof file !== 'string' || file.length === 0) {
  35. throw new Error(`Invalid filename: ${file}`);
  36. } else if (file.endsWith(path.sep)) {
  37. throw new Error(`Filename is a directory: ${file}`);
  38. } else if (file.indexOf(`~${path.sep}`) === 0) {
  39. // handle ~ expansion: https://github.com/nodejs/node/issues/684
  40. // exclude ~ and ~filename as these can be valid files
  41. file = file.replace('~', os.homedir());
  42. }
  43. file = path.normalize(file);
  44. numBackups = !numBackups && numBackups !== 0 ? 5 : numBackups;
  45. debug(
  46. 'Creating file appender (',
  47. file,
  48. ', ',
  49. logSize,
  50. ', ',
  51. numBackups,
  52. ', ',
  53. options,
  54. ', ',
  55. timezoneOffset,
  56. ')'
  57. );
  58. function openTheStream(filePath, fileSize, numFiles, opt) {
  59. const stream = new streams.RollingFileStream(
  60. filePath,
  61. fileSize,
  62. numFiles,
  63. opt
  64. );
  65. stream.on('error', (err) => {
  66. // eslint-disable-next-line no-console
  67. console.error(
  68. 'log4js.fileAppender - Writing to file %s, error happened ',
  69. filePath,
  70. err
  71. );
  72. });
  73. stream.on('drain', () => {
  74. process.emit('log4js:pause', false);
  75. });
  76. return stream;
  77. }
  78. let writer = openTheStream(file, logSize, numBackups, options);
  79. const app = function (loggingEvent) {
  80. if (!writer.writable) {
  81. return;
  82. }
  83. if (options.removeColor === true) {
  84. // eslint-disable-next-line no-control-regex
  85. const regex = /\x1b[[0-9;]*m/g;
  86. loggingEvent.data = loggingEvent.data.map((d) => {
  87. if (typeof d === 'string') return d.replace(regex, '');
  88. return d;
  89. });
  90. }
  91. if (!writer.write(layout(loggingEvent, timezoneOffset) + eol, 'utf8')) {
  92. process.emit('log4js:pause', true);
  93. }
  94. };
  95. app.reopen = function () {
  96. writer.end(() => {
  97. writer = openTheStream(file, logSize, numBackups, options);
  98. });
  99. };
  100. app.sighupHandler = function () {
  101. debug('SIGHUP handler called.');
  102. app.reopen();
  103. };
  104. app.shutdown = function (complete) {
  105. sighupListeners.delete(app);
  106. if (sighupListeners.size === 0 && mainSighupListenerStarted) {
  107. process.removeListener('SIGHUP', mainSighupHandler);
  108. mainSighupListenerStarted = false;
  109. }
  110. writer.end('', 'utf-8', complete);
  111. };
  112. // On SIGHUP, close and reopen all files. This allows this appender to work with
  113. // logrotate. Note that if you are using logrotate, you should not set
  114. // `logSize`.
  115. sighupListeners.add(app);
  116. if (!mainSighupListenerStarted) {
  117. process.on('SIGHUP', mainSighupHandler);
  118. mainSighupListenerStarted = true;
  119. }
  120. return app;
  121. }
  122. function configure(config, layouts) {
  123. let layout = layouts.basicLayout;
  124. if (config.layout) {
  125. layout = layouts.layout(config.layout.type, config.layout);
  126. }
  127. // security default (instead of relying on streamroller default)
  128. config.mode = config.mode || 0o600;
  129. return fileAppender(
  130. config.filename,
  131. layout,
  132. config.maxLogSize,
  133. config.backups,
  134. config,
  135. config.timezoneOffset
  136. );
  137. }
  138. module.exports.configure = configure;