categories.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. const debug = require('debug')('log4js:categories');
  2. const configuration = require('./configuration');
  3. const levels = require('./levels');
  4. const appenders = require('./appenders');
  5. const categories = new Map();
  6. /**
  7. * Add inherited config to this category. That includes extra appenders from parent,
  8. * and level, if none is set on this category.
  9. * This is recursive, so each parent also gets loaded with inherited appenders.
  10. * Inheritance is blocked if a category has inherit=false
  11. * @param {*} config
  12. * @param {*} category the child category
  13. * @param {string} categoryName dotted path to category
  14. * @return {void}
  15. */
  16. function inheritFromParent(config, category, categoryName) {
  17. if (category.inherit === false) return;
  18. const lastDotIndex = categoryName.lastIndexOf('.');
  19. if (lastDotIndex < 0) return; // category is not a child
  20. const parentCategoryName = categoryName.substring(0, lastDotIndex);
  21. let parentCategory = config.categories[parentCategoryName];
  22. if (!parentCategory) {
  23. // parent is missing, so implicitly create it, so that it can inherit from its parents
  24. parentCategory = { inherit: true, appenders: [] };
  25. }
  26. // make sure parent has had its inheritance taken care of before pulling its properties to this child
  27. inheritFromParent(config, parentCategory, parentCategoryName);
  28. // if the parent is not in the config (because we just created it above),
  29. // and it inherited a valid configuration, add it to config.categories
  30. if (!config.categories[parentCategoryName]
  31. && parentCategory.appenders
  32. && parentCategory.appenders.length
  33. && parentCategory.level) {
  34. config.categories[parentCategoryName] = parentCategory;
  35. }
  36. category.appenders = category.appenders || [];
  37. category.level = category.level || parentCategory.level;
  38. // merge in appenders from parent (parent is already holding its inherited appenders)
  39. parentCategory.appenders.forEach((ap) => {
  40. if (!category.appenders.includes(ap)) {
  41. category.appenders.push(ap);
  42. }
  43. });
  44. category.parent = parentCategory;
  45. }
  46. /**
  47. * Walk all categories in the config, and pull down any configuration from parent to child.
  48. * This includes inherited appenders, and level, where level is not set.
  49. * Inheritance is skipped where a category has inherit=false.
  50. * @param {*} config
  51. */
  52. function addCategoryInheritance(config) {
  53. if (!config.categories) return;
  54. const categoryNames = Object.keys(config.categories);
  55. categoryNames.forEach((name) => {
  56. const category = config.categories[name];
  57. // add inherited appenders and level to this category
  58. inheritFromParent(config, category, name);
  59. });
  60. }
  61. configuration.addPreProcessingListener(config => addCategoryInheritance(config));
  62. configuration.addListener((config) => {
  63. configuration.throwExceptionIf(
  64. config,
  65. configuration.not(configuration.anObject(config.categories)),
  66. 'must have a property "categories" of type object.'
  67. );
  68. const categoryNames = Object.keys(config.categories);
  69. configuration.throwExceptionIf(
  70. config,
  71. configuration.not(categoryNames.length),
  72. 'must define at least one category.'
  73. );
  74. categoryNames.forEach((name) => {
  75. const category = config.categories[name];
  76. configuration.throwExceptionIf(
  77. config,
  78. [
  79. configuration.not(category.appenders),
  80. configuration.not(category.level)
  81. ],
  82. `category "${name}" is not valid (must be an object with properties "appenders" and "level")`
  83. );
  84. configuration.throwExceptionIf(
  85. config,
  86. configuration.not(Array.isArray(category.appenders)),
  87. `category "${name}" is not valid (appenders must be an array of appender names)`
  88. );
  89. configuration.throwExceptionIf(
  90. config,
  91. configuration.not(category.appenders.length),
  92. `category "${name}" is not valid (appenders must contain at least one appender name)`
  93. );
  94. if (Object.prototype.hasOwnProperty.call(category, 'enableCallStack')) {
  95. configuration.throwExceptionIf(
  96. config,
  97. typeof category.enableCallStack !== 'boolean',
  98. `category "${name}" is not valid (enableCallStack must be boolean type)`
  99. );
  100. }
  101. category.appenders.forEach((appender) => {
  102. configuration.throwExceptionIf(
  103. config,
  104. configuration.not(appenders.get(appender)),
  105. `category "${name}" is not valid (appender "${appender}" is not defined)`
  106. );
  107. });
  108. configuration.throwExceptionIf(
  109. config,
  110. configuration.not(levels.getLevel(category.level)),
  111. `category "${name}" is not valid (level "${category.level}" not recognised;`
  112. + ` valid levels are ${levels.levels.join(', ')})`
  113. );
  114. });
  115. configuration.throwExceptionIf(
  116. config,
  117. configuration.not(config.categories.default),
  118. 'must define a "default" category.'
  119. );
  120. });
  121. const setup = (config) => {
  122. categories.clear();
  123. const categoryNames = Object.keys(config.categories);
  124. categoryNames.forEach((name) => {
  125. const category = config.categories[name];
  126. const categoryAppenders = [];
  127. category.appenders.forEach((appender) => {
  128. categoryAppenders.push(appenders.get(appender));
  129. debug(`Creating category ${name}`);
  130. categories.set(
  131. name,
  132. {
  133. appenders: categoryAppenders,
  134. level: levels.getLevel(category.level),
  135. enableCallStack: category.enableCallStack || false
  136. }
  137. );
  138. });
  139. });
  140. };
  141. setup({ categories: { default: { appenders: ['out'], level: 'OFF' } } });
  142. configuration.addListener(setup);
  143. const configForCategory = (category) => {
  144. debug(`configForCategory: searching for config for ${category}`);
  145. if (categories.has(category)) {
  146. debug(`configForCategory: ${category} exists in config, returning it`);
  147. return categories.get(category);
  148. }
  149. if (category.indexOf('.') > 0) {
  150. debug(`configForCategory: ${category} has hierarchy, searching for parents`);
  151. return configForCategory(category.substring(0, category.lastIndexOf('.')));
  152. }
  153. debug('configForCategory: returning config for default category');
  154. return configForCategory('default');
  155. };
  156. const appendersForCategory = category => configForCategory(category).appenders;
  157. const getLevelForCategory = category => configForCategory(category).level;
  158. const setLevelForCategory = (category, level) => {
  159. let categoryConfig = categories.get(category);
  160. debug(`setLevelForCategory: found ${categoryConfig} for ${category}`);
  161. if (!categoryConfig) {
  162. const sourceCategoryConfig = configForCategory(category);
  163. debug('setLevelForCategory: no config found for category, '
  164. + `found ${sourceCategoryConfig} for parents of ${category}`);
  165. categoryConfig = { appenders: sourceCategoryConfig.appenders };
  166. }
  167. categoryConfig.level = level;
  168. categories.set(category, categoryConfig);
  169. };
  170. const getEnableCallStackForCategory = category => configForCategory(category).enableCallStack === true;
  171. const setEnableCallStackForCategory = (category, useCallStack) => {
  172. configForCategory(category).enableCallStack = useCallStack;
  173. };
  174. module.exports = {
  175. appendersForCategory,
  176. getLevelForCategory,
  177. setLevelForCategory,
  178. getEnableCallStackForCategory,
  179. setEnableCallStackForCategory,
  180. };