categories.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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.slice(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 (
  31. !config.categories[parentCategoryName] &&
  32. parentCategory.appenders &&
  33. parentCategory.appenders.length &&
  34. parentCategory.level
  35. ) {
  36. config.categories[parentCategoryName] = parentCategory;
  37. }
  38. category.appenders = category.appenders || [];
  39. category.level = category.level || parentCategory.level;
  40. // merge in appenders from parent (parent is already holding its inherited appenders)
  41. parentCategory.appenders.forEach((ap) => {
  42. if (!category.appenders.includes(ap)) {
  43. category.appenders.push(ap);
  44. }
  45. });
  46. category.parent = parentCategory;
  47. }
  48. /**
  49. * Walk all categories in the config, and pull down any configuration from parent to child.
  50. * This includes inherited appenders, and level, where level is not set.
  51. * Inheritance is skipped where a category has inherit=false.
  52. * @param {*} config
  53. */
  54. function addCategoryInheritance(config) {
  55. if (!config.categories) return;
  56. const categoryNames = Object.keys(config.categories);
  57. categoryNames.forEach((name) => {
  58. const category = config.categories[name];
  59. // add inherited appenders and level to this category
  60. inheritFromParent(config, category, name);
  61. });
  62. }
  63. configuration.addPreProcessingListener((config) =>
  64. addCategoryInheritance(config)
  65. );
  66. configuration.addListener((config) => {
  67. configuration.throwExceptionIf(
  68. config,
  69. configuration.not(configuration.anObject(config.categories)),
  70. 'must have a property "categories" of type object.'
  71. );
  72. const categoryNames = Object.keys(config.categories);
  73. configuration.throwExceptionIf(
  74. config,
  75. configuration.not(categoryNames.length),
  76. 'must define at least one category.'
  77. );
  78. categoryNames.forEach((name) => {
  79. const category = config.categories[name];
  80. configuration.throwExceptionIf(
  81. config,
  82. [
  83. configuration.not(category.appenders),
  84. configuration.not(category.level),
  85. ],
  86. `category "${name}" is not valid (must be an object with properties "appenders" and "level")`
  87. );
  88. configuration.throwExceptionIf(
  89. config,
  90. configuration.not(Array.isArray(category.appenders)),
  91. `category "${name}" is not valid (appenders must be an array of appender names)`
  92. );
  93. configuration.throwExceptionIf(
  94. config,
  95. configuration.not(category.appenders.length),
  96. `category "${name}" is not valid (appenders must contain at least one appender name)`
  97. );
  98. if (Object.prototype.hasOwnProperty.call(category, 'enableCallStack')) {
  99. configuration.throwExceptionIf(
  100. config,
  101. typeof category.enableCallStack !== 'boolean',
  102. `category "${name}" is not valid (enableCallStack must be boolean type)`
  103. );
  104. }
  105. category.appenders.forEach((appender) => {
  106. configuration.throwExceptionIf(
  107. config,
  108. configuration.not(appenders.get(appender)),
  109. `category "${name}" is not valid (appender "${appender}" is not defined)`
  110. );
  111. });
  112. configuration.throwExceptionIf(
  113. config,
  114. configuration.not(levels.getLevel(category.level)),
  115. `category "${name}" is not valid (level "${category.level}" not recognised;` +
  116. ` valid levels are ${levels.levels.join(', ')})`
  117. );
  118. });
  119. configuration.throwExceptionIf(
  120. config,
  121. configuration.not(config.categories.default),
  122. 'must define a "default" category.'
  123. );
  124. });
  125. const setup = (config) => {
  126. categories.clear();
  127. if (!config) {
  128. return;
  129. }
  130. const categoryNames = Object.keys(config.categories);
  131. categoryNames.forEach((name) => {
  132. const category = config.categories[name];
  133. const categoryAppenders = [];
  134. category.appenders.forEach((appender) => {
  135. categoryAppenders.push(appenders.get(appender));
  136. debug(`Creating category ${name}`);
  137. categories.set(name, {
  138. appenders: categoryAppenders,
  139. level: levels.getLevel(category.level),
  140. enableCallStack: category.enableCallStack || false,
  141. });
  142. });
  143. });
  144. };
  145. const init = () => {
  146. setup();
  147. };
  148. init();
  149. configuration.addListener(setup);
  150. const configForCategory = (category) => {
  151. debug(`configForCategory: searching for config for ${category}`);
  152. if (categories.has(category)) {
  153. debug(`configForCategory: ${category} exists in config, returning it`);
  154. return categories.get(category);
  155. }
  156. let sourceCategoryConfig;
  157. if (category.indexOf('.') > 0) {
  158. debug(`configForCategory: ${category} has hierarchy, cloning from parents`);
  159. sourceCategoryConfig = {
  160. ...configForCategory(category.slice(0, category.lastIndexOf('.'))),
  161. };
  162. } else {
  163. if (!categories.has('default')) {
  164. setup({ categories: { default: { appenders: ['out'], level: 'OFF' } } });
  165. }
  166. debug('configForCategory: cloning default category');
  167. sourceCategoryConfig = { ...categories.get('default') };
  168. }
  169. categories.set(category, sourceCategoryConfig);
  170. return sourceCategoryConfig;
  171. };
  172. const appendersForCategory = (category) =>
  173. configForCategory(category).appenders;
  174. const getLevelForCategory = (category) => configForCategory(category).level;
  175. const setLevelForCategory = (category, level) => {
  176. configForCategory(category).level = level;
  177. };
  178. const getEnableCallStackForCategory = (category) =>
  179. configForCategory(category).enableCallStack === true;
  180. const setEnableCallStackForCategory = (category, useCallStack) => {
  181. configForCategory(category).enableCallStack = useCallStack;
  182. };
  183. module.exports = categories;
  184. module.exports = Object.assign(module.exports, {
  185. appendersForCategory,
  186. getLevelForCategory,
  187. setLevelForCategory,
  188. getEnableCallStackForCategory,
  189. setEnableCallStackForCategory,
  190. init,
  191. });