219 lines
6.9 KiB
JavaScript
219 lines
6.9 KiB
JavaScript
const debug = require('debug')('log4js:categories');
|
|
const configuration = require('./configuration');
|
|
const levels = require('./levels');
|
|
const appenders = require('./appenders');
|
|
|
|
const categories = new Map();
|
|
|
|
/**
|
|
* Add inherited config to this category. That includes extra appenders from parent,
|
|
* and level, if none is set on this category.
|
|
* This is recursive, so each parent also gets loaded with inherited appenders.
|
|
* Inheritance is blocked if a category has inherit=false
|
|
* @param {*} config
|
|
* @param {*} category the child category
|
|
* @param {string} categoryName dotted path to category
|
|
* @return {void}
|
|
*/
|
|
function inheritFromParent(config, category, categoryName) {
|
|
if (category.inherit === false) return;
|
|
const lastDotIndex = categoryName.lastIndexOf('.');
|
|
if (lastDotIndex < 0) return; // category is not a child
|
|
const parentCategoryName = categoryName.slice(0, lastDotIndex);
|
|
let parentCategory = config.categories[parentCategoryName];
|
|
|
|
if (!parentCategory) {
|
|
// parent is missing, so implicitly create it, so that it can inherit from its parents
|
|
parentCategory = { inherit: true, appenders: [] };
|
|
}
|
|
|
|
// make sure parent has had its inheritance taken care of before pulling its properties to this child
|
|
inheritFromParent(config, parentCategory, parentCategoryName);
|
|
|
|
// if the parent is not in the config (because we just created it above),
|
|
// and it inherited a valid configuration, add it to config.categories
|
|
if (
|
|
!config.categories[parentCategoryName] &&
|
|
parentCategory.appenders &&
|
|
parentCategory.appenders.length &&
|
|
parentCategory.level
|
|
) {
|
|
config.categories[parentCategoryName] = parentCategory;
|
|
}
|
|
|
|
category.appenders = category.appenders || [];
|
|
category.level = category.level || parentCategory.level;
|
|
|
|
// merge in appenders from parent (parent is already holding its inherited appenders)
|
|
parentCategory.appenders.forEach((ap) => {
|
|
if (!category.appenders.includes(ap)) {
|
|
category.appenders.push(ap);
|
|
}
|
|
});
|
|
category.parent = parentCategory;
|
|
}
|
|
|
|
/**
|
|
* Walk all categories in the config, and pull down any configuration from parent to child.
|
|
* This includes inherited appenders, and level, where level is not set.
|
|
* Inheritance is skipped where a category has inherit=false.
|
|
* @param {*} config
|
|
*/
|
|
function addCategoryInheritance(config) {
|
|
if (!config.categories) return;
|
|
const categoryNames = Object.keys(config.categories);
|
|
categoryNames.forEach((name) => {
|
|
const category = config.categories[name];
|
|
// add inherited appenders and level to this category
|
|
inheritFromParent(config, category, name);
|
|
});
|
|
}
|
|
|
|
configuration.addPreProcessingListener((config) =>
|
|
addCategoryInheritance(config)
|
|
);
|
|
|
|
configuration.addListener((config) => {
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(configuration.anObject(config.categories)),
|
|
'must have a property "categories" of type object.'
|
|
);
|
|
|
|
const categoryNames = Object.keys(config.categories);
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(categoryNames.length),
|
|
'must define at least one category.'
|
|
);
|
|
|
|
categoryNames.forEach((name) => {
|
|
const category = config.categories[name];
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
[
|
|
configuration.not(category.appenders),
|
|
configuration.not(category.level),
|
|
],
|
|
`category "${name}" is not valid (must be an object with properties "appenders" and "level")`
|
|
);
|
|
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(Array.isArray(category.appenders)),
|
|
`category "${name}" is not valid (appenders must be an array of appender names)`
|
|
);
|
|
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(category.appenders.length),
|
|
`category "${name}" is not valid (appenders must contain at least one appender name)`
|
|
);
|
|
|
|
if (Object.prototype.hasOwnProperty.call(category, 'enableCallStack')) {
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
typeof category.enableCallStack !== 'boolean',
|
|
`category "${name}" is not valid (enableCallStack must be boolean type)`
|
|
);
|
|
}
|
|
|
|
category.appenders.forEach((appender) => {
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(appenders.get(appender)),
|
|
`category "${name}" is not valid (appender "${appender}" is not defined)`
|
|
);
|
|
});
|
|
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(levels.getLevel(category.level)),
|
|
`category "${name}" is not valid (level "${category.level}" not recognised;` +
|
|
` valid levels are ${levels.levels.join(', ')})`
|
|
);
|
|
});
|
|
|
|
configuration.throwExceptionIf(
|
|
config,
|
|
configuration.not(config.categories.default),
|
|
'must define a "default" category.'
|
|
);
|
|
});
|
|
|
|
const setup = (config) => {
|
|
categories.clear();
|
|
if (!config) {
|
|
return;
|
|
}
|
|
|
|
const categoryNames = Object.keys(config.categories);
|
|
categoryNames.forEach((name) => {
|
|
const category = config.categories[name];
|
|
const categoryAppenders = [];
|
|
category.appenders.forEach((appender) => {
|
|
categoryAppenders.push(appenders.get(appender));
|
|
debug(`Creating category ${name}`);
|
|
categories.set(name, {
|
|
appenders: categoryAppenders,
|
|
level: levels.getLevel(category.level),
|
|
enableCallStack: category.enableCallStack || false,
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
const init = () => {
|
|
setup();
|
|
};
|
|
init();
|
|
|
|
configuration.addListener(setup);
|
|
|
|
const configForCategory = (category) => {
|
|
debug(`configForCategory: searching for config for ${category}`);
|
|
if (categories.has(category)) {
|
|
debug(`configForCategory: ${category} exists in config, returning it`);
|
|
return categories.get(category);
|
|
}
|
|
|
|
let sourceCategoryConfig;
|
|
if (category.indexOf('.') > 0) {
|
|
debug(`configForCategory: ${category} has hierarchy, cloning from parents`);
|
|
sourceCategoryConfig = {
|
|
...configForCategory(category.slice(0, category.lastIndexOf('.'))),
|
|
};
|
|
} else {
|
|
if (!categories.has('default')) {
|
|
setup({ categories: { default: { appenders: ['out'], level: 'OFF' } } });
|
|
}
|
|
debug('configForCategory: cloning default category');
|
|
sourceCategoryConfig = { ...categories.get('default') };
|
|
}
|
|
categories.set(category, sourceCategoryConfig);
|
|
return sourceCategoryConfig;
|
|
};
|
|
|
|
const appendersForCategory = (category) =>
|
|
configForCategory(category).appenders;
|
|
|
|
const getLevelForCategory = (category) => configForCategory(category).level;
|
|
const setLevelForCategory = (category, level) => {
|
|
configForCategory(category).level = level;
|
|
};
|
|
|
|
const getEnableCallStackForCategory = (category) =>
|
|
configForCategory(category).enableCallStack === true;
|
|
const setEnableCallStackForCategory = (category, useCallStack) => {
|
|
configForCategory(category).enableCallStack = useCallStack;
|
|
};
|
|
|
|
module.exports = categories;
|
|
module.exports = Object.assign(module.exports, {
|
|
appendersForCategory,
|
|
getLevelForCategory,
|
|
setLevelForCategory,
|
|
getEnableCallStackForCategory,
|
|
setEnableCallStackForCategory,
|
|
init,
|
|
});
|