file.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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(file, layout, logSize, numBackups, options, timezoneOffset) {
  27. if (typeof file !== "string" || file.length === 0) {
  28. throw new Error(`Invalid filename: ${file}`);
  29. } else if (file.endsWith(path.sep)) {
  30. throw new Error(`Filename is a directory: ${file}`);
  31. } else {
  32. // handle ~ expansion: https://github.com/nodejs/node/issues/684
  33. // exclude ~ and ~filename as these can be valid files
  34. file = file.replace(new RegExp(`^~(?=${path.sep}.+)`), os.homedir());
  35. }
  36. file = path.normalize(file);
  37. numBackups = (!numBackups && numBackups !== 0) ? 5 : numBackups;
  38. debug(
  39. 'Creating file appender (',
  40. file, ', ',
  41. logSize, ', ',
  42. numBackups, ', ',
  43. options, ', ',
  44. timezoneOffset, ')'
  45. );
  46. function openTheStream(filePath, fileSize, numFiles, opt) {
  47. const stream = new streams.RollingFileStream(
  48. filePath,
  49. fileSize,
  50. numFiles,
  51. opt
  52. );
  53. stream.on('error', (err) => {
  54. // eslint-disable-next-line no-console
  55. console.error('log4js.fileAppender - Writing to file %s, error happened ', filePath, err);
  56. });
  57. stream.on('drain', () => {
  58. process.emit("log4js:pause", false);
  59. });
  60. return stream;
  61. }
  62. let writer = openTheStream(file, logSize, numBackups, options);
  63. const app = function (loggingEvent) {
  64. if (!writer.writable) {
  65. return;
  66. }
  67. if (options.removeColor === true) {
  68. // eslint-disable-next-line no-control-regex
  69. const regex = /\x1b[[0-9;]*m/g;
  70. loggingEvent.data = loggingEvent.data.map(d => {
  71. if (typeof d === 'string') return d.replace(regex, '');
  72. return d;
  73. });
  74. }
  75. if (!writer.write(layout(loggingEvent, timezoneOffset) + eol, "utf8")) {
  76. process.emit('log4js:pause', true);
  77. }
  78. };
  79. app.reopen = function () {
  80. writer.end(() => { writer = openTheStream(file, logSize, numBackups, options); });
  81. };
  82. app.sighupHandler = function () {
  83. debug('SIGHUP handler called.');
  84. app.reopen();
  85. };
  86. app.shutdown = function (complete) {
  87. sighupListeners.delete(app);
  88. if (sighupListeners.size === 0 && mainSighupListenerStarted) {
  89. process.removeListener('SIGHUP', mainSighupHandler);
  90. mainSighupListenerStarted = false;
  91. }
  92. writer.end('', 'utf-8', complete);
  93. };
  94. // On SIGHUP, close and reopen all files. This allows this appender to work with
  95. // logrotate. Note that if you are using logrotate, you should not set
  96. // `logSize`.
  97. sighupListeners.add(app);
  98. if (!mainSighupListenerStarted) {
  99. process.on('SIGHUP', mainSighupHandler);
  100. mainSighupListenerStarted = true;
  101. }
  102. return app;
  103. }
  104. function configure(config, layouts) {
  105. let layout = layouts.basicLayout;
  106. if (config.layout) {
  107. layout = layouts.layout(config.layout.type, config.layout);
  108. }
  109. // security default (instead of relying on streamroller default)
  110. config.mode = config.mode || 0o600;
  111. return fileAppender(
  112. config.filename,
  113. layout,
  114. config.maxLogSize,
  115. config.backups,
  116. config,
  117. config.timezoneOffset
  118. );
  119. }
  120. module.exports.configure = configure;