index.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. const path = require('path');
  2. const debug = require('debug')('log4js:appenders');
  3. const configuration = require('../configuration');
  4. const clustering = require('../clustering');
  5. const levels = require('../levels');
  6. const layouts = require('../layouts');
  7. const adapters = require('./adapters');
  8. // pre-load the core appenders so that webpack can find them
  9. const coreAppenders = new Map();
  10. coreAppenders.set('console', require('./console'));
  11. coreAppenders.set('stdout', require('./stdout'));
  12. coreAppenders.set('stderr', require('./stderr'));
  13. coreAppenders.set('logLevelFilter', require('./logLevelFilter'));
  14. coreAppenders.set('categoryFilter', require('./categoryFilter'));
  15. coreAppenders.set('noLogFilter', require('./noLogFilter'));
  16. coreAppenders.set('file', require('./file'));
  17. coreAppenders.set('dateFile', require('./dateFile'));
  18. coreAppenders.set('fileSync', require('./fileSync'));
  19. coreAppenders.set('tcp', require('./tcp'));
  20. const appenders = new Map();
  21. const tryLoading = (modulePath, config) => {
  22. let resolvedPath;
  23. try {
  24. const modulePathCJS = `${modulePath}.cjs`;
  25. resolvedPath = require.resolve(modulePathCJS);
  26. debug('Loading module from ', modulePathCJS);
  27. } catch (e) {
  28. resolvedPath = modulePath;
  29. debug('Loading module from ', modulePath);
  30. }
  31. try {
  32. // eslint-disable-next-line global-require, import/no-dynamic-require
  33. return require(resolvedPath);
  34. } catch (e) {
  35. // if the module was found, and we still got an error, then raise it
  36. configuration.throwExceptionIf(
  37. config,
  38. e.code !== 'MODULE_NOT_FOUND',
  39. `appender "${modulePath}" could not be loaded (error was: ${e})`
  40. );
  41. return undefined;
  42. }
  43. };
  44. const loadAppenderModule = (type, config) =>
  45. coreAppenders.get(type) ||
  46. tryLoading(`./${type}`, config) ||
  47. tryLoading(type, config) ||
  48. (require.main &&
  49. require.main.filename &&
  50. tryLoading(path.join(path.dirname(require.main.filename), type), config)) ||
  51. tryLoading(path.join(process.cwd(), type), config);
  52. const appendersLoading = new Set();
  53. const getAppender = (name, config) => {
  54. if (appenders.has(name)) return appenders.get(name);
  55. if (!config.appenders[name]) return false;
  56. if (appendersLoading.has(name))
  57. throw new Error(`Dependency loop detected for appender ${name}.`);
  58. appendersLoading.add(name);
  59. debug(`Creating appender ${name}`);
  60. // eslint-disable-next-line no-use-before-define
  61. const appender = createAppender(name, config);
  62. appendersLoading.delete(name);
  63. appenders.set(name, appender);
  64. return appender;
  65. };
  66. const createAppender = (name, config) => {
  67. const appenderConfig = config.appenders[name];
  68. const appenderModule = appenderConfig.type.configure
  69. ? appenderConfig.type
  70. : loadAppenderModule(appenderConfig.type, config);
  71. configuration.throwExceptionIf(
  72. config,
  73. configuration.not(appenderModule),
  74. `appender "${name}" is not valid (type "${appenderConfig.type}" could not be found)`
  75. );
  76. if (appenderModule.appender) {
  77. process.emitWarning(
  78. `Appender ${appenderConfig.type} exports an appender function.`,
  79. 'DeprecationWarning',
  80. 'log4js-node-DEP0001'
  81. );
  82. debug(
  83. '[log4js-node-DEP0001]',
  84. `DEPRECATION: Appender ${appenderConfig.type} exports an appender function.`
  85. );
  86. }
  87. if (appenderModule.shutdown) {
  88. process.emitWarning(
  89. `Appender ${appenderConfig.type} exports a shutdown function.`,
  90. 'DeprecationWarning',
  91. 'log4js-node-DEP0002'
  92. );
  93. debug(
  94. '[log4js-node-DEP0002]',
  95. `DEPRECATION: Appender ${appenderConfig.type} exports a shutdown function.`
  96. );
  97. }
  98. debug(`${name}: clustering.isMaster ? ${clustering.isMaster()}`);
  99. debug(
  100. // eslint-disable-next-line global-require
  101. `${name}: appenderModule is ${require('util').inspect(appenderModule)}`
  102. );
  103. return clustering.onlyOnMaster(
  104. () => {
  105. debug(
  106. `calling appenderModule.configure for ${name} / ${appenderConfig.type}`
  107. );
  108. return appenderModule.configure(
  109. adapters.modifyConfig(appenderConfig),
  110. layouts,
  111. (appender) => getAppender(appender, config),
  112. levels
  113. );
  114. },
  115. /* istanbul ignore next: fn never gets called by non-master yet needed to pass config validation */ () => {}
  116. );
  117. };
  118. const setup = (config) => {
  119. appenders.clear();
  120. appendersLoading.clear();
  121. if (!config) {
  122. return;
  123. }
  124. const usedAppenders = [];
  125. Object.values(config.categories).forEach((category) => {
  126. usedAppenders.push(...category.appenders);
  127. });
  128. Object.keys(config.appenders).forEach((name) => {
  129. // dodgy hard-coding of special case for tcp-server and multiprocess which may not have
  130. // any categories associated with it, but needs to be started up anyway
  131. if (
  132. usedAppenders.includes(name) ||
  133. config.appenders[name].type === 'tcp-server' ||
  134. config.appenders[name].type === 'multiprocess'
  135. ) {
  136. getAppender(name, config);
  137. }
  138. });
  139. };
  140. const init = () => {
  141. setup();
  142. };
  143. init();
  144. configuration.addListener((config) => {
  145. configuration.throwExceptionIf(
  146. config,
  147. configuration.not(configuration.anObject(config.appenders)),
  148. 'must have a property "appenders" of type object.'
  149. );
  150. const appenderNames = Object.keys(config.appenders);
  151. configuration.throwExceptionIf(
  152. config,
  153. configuration.not(appenderNames.length),
  154. 'must define at least one appender.'
  155. );
  156. appenderNames.forEach((name) => {
  157. configuration.throwExceptionIf(
  158. config,
  159. configuration.not(config.appenders[name].type),
  160. `appender "${name}" is not valid (must be an object with property "type")`
  161. );
  162. });
  163. });
  164. configuration.addListener(setup);
  165. module.exports = appenders;
  166. module.exports.init = init;