logger.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /* eslint no-underscore-dangle: ["error", { "allow": ["_log"] }] */
  2. const debug = require('debug')('log4js:logger');
  3. const LoggingEvent = require('./LoggingEvent');
  4. const levels = require('./levels');
  5. const clustering = require('./clustering');
  6. const categories = require('./categories');
  7. const configuration = require('./configuration');
  8. const stackReg = /^(?:\s*)at (?:(.+) \()?(?:([^(]+?):(\d+):(\d+))\)?$/;
  9. /**
  10. * The top entry is the Error
  11. */
  12. const baseCallStackSkip = 1;
  13. /**
  14. * The _log function is 3 levels deep, we need to skip those to make it to the callSite
  15. */
  16. const defaultErrorCallStackSkip = 3;
  17. /**
  18. *
  19. * @param {Error} data
  20. * @param {number} skipIdx
  21. * @returns {import('../types/log4js').CallStack | null}
  22. */
  23. function defaultParseCallStack(
  24. data,
  25. skipIdx = defaultErrorCallStackSkip + baseCallStackSkip
  26. ) {
  27. try {
  28. const stacklines = data.stack.split('\n').slice(skipIdx);
  29. if (!stacklines.length) {
  30. // There's no stack in this stack
  31. // Should we try a previous index if skipIdx was set?
  32. return null;
  33. }
  34. const lineMatch = stackReg.exec(stacklines[0]);
  35. /* istanbul ignore else: failsafe */
  36. if (lineMatch && lineMatch.length === 5) {
  37. // extract class, function and alias names
  38. let className = '';
  39. let functionName = '';
  40. let functionAlias = '';
  41. if (lineMatch[1] && lineMatch[1] !== '') {
  42. // WARN: this will unset alias if alias is not present.
  43. [functionName, functionAlias] = lineMatch[1]
  44. .replace(/[[\]]/g, '')
  45. .split(' as ');
  46. functionAlias = functionAlias || '';
  47. if (functionName.includes('.'))
  48. [className, functionName] = functionName.split('.');
  49. }
  50. return {
  51. fileName: lineMatch[2],
  52. lineNumber: parseInt(lineMatch[3], 10),
  53. columnNumber: parseInt(lineMatch[4], 10),
  54. callStack: stacklines.join('\n'),
  55. className,
  56. functionName,
  57. functionAlias,
  58. callerName: lineMatch[1] || '',
  59. };
  60. // eslint-disable-next-line no-else-return
  61. } else {
  62. // will never get here unless nodejs has changes to Error
  63. console.error('log4js.logger - defaultParseCallStack error'); // eslint-disable-line no-console
  64. }
  65. } catch (err) {
  66. // will never get error unless nodejs has breaking changes to Error
  67. console.error('log4js.logger - defaultParseCallStack error', err); // eslint-disable-line no-console
  68. }
  69. return null;
  70. }
  71. /**
  72. * Logger to log messages.
  73. * use {@see log4js#getLogger(String)} to get an instance.
  74. *
  75. * @name Logger
  76. * @namespace Log4js
  77. * @param name name of category to log to
  78. * @param level - the loglevel for the category
  79. * @param dispatch - the function which will receive the logevents
  80. *
  81. * @author Stephan Strittmatter
  82. */
  83. class Logger {
  84. constructor(name) {
  85. if (!name) {
  86. throw new Error('No category provided.');
  87. }
  88. this.category = name;
  89. this.context = {};
  90. /** @private */
  91. this.callStackSkipIndex = 0;
  92. /** @private */
  93. this.parseCallStack = defaultParseCallStack;
  94. debug(`Logger created (${this.category}, ${this.level})`);
  95. }
  96. get level() {
  97. return levels.getLevel(
  98. categories.getLevelForCategory(this.category),
  99. levels.OFF
  100. );
  101. }
  102. set level(level) {
  103. categories.setLevelForCategory(
  104. this.category,
  105. levels.getLevel(level, this.level)
  106. );
  107. }
  108. get useCallStack() {
  109. return categories.getEnableCallStackForCategory(this.category);
  110. }
  111. set useCallStack(bool) {
  112. categories.setEnableCallStackForCategory(this.category, bool === true);
  113. }
  114. get callStackLinesToSkip() {
  115. return this.callStackSkipIndex;
  116. }
  117. set callStackLinesToSkip(number) {
  118. if (typeof number !== 'number') {
  119. throw new TypeError('Must be a number');
  120. }
  121. if (number < 0) {
  122. throw new RangeError('Must be >= 0');
  123. }
  124. this.callStackSkipIndex = number;
  125. }
  126. log(level, ...args) {
  127. const logLevel = levels.getLevel(level);
  128. if (!logLevel) {
  129. if (configuration.validIdentifier(level) && args.length > 0) {
  130. // logLevel not found but of valid signature, WARN before fallback to INFO
  131. this.log(
  132. levels.WARN,
  133. 'log4js:logger.log: valid log-level not found as first parameter given:',
  134. level
  135. );
  136. this.log(levels.INFO, `[${level}]`, ...args);
  137. } else {
  138. // apart from fallback, allow .log(...args) to be synonym with .log("INFO", ...args)
  139. this.log(levels.INFO, level, ...args);
  140. }
  141. } else if (this.isLevelEnabled(logLevel)) {
  142. this._log(logLevel, args);
  143. }
  144. }
  145. isLevelEnabled(otherLevel) {
  146. return this.level.isLessThanOrEqualTo(otherLevel);
  147. }
  148. _log(level, data) {
  149. debug(`sending log data (${level}) to appenders`);
  150. const error = data.find((item) => item instanceof Error);
  151. let callStack;
  152. if (this.useCallStack) {
  153. try {
  154. if (error) {
  155. callStack = this.parseCallStack(
  156. error,
  157. this.callStackSkipIndex + baseCallStackSkip
  158. );
  159. }
  160. } catch (_err) {
  161. // Ignore Error and use the original method of creating a new Error.
  162. }
  163. callStack =
  164. callStack ||
  165. this.parseCallStack(
  166. new Error(),
  167. this.callStackSkipIndex +
  168. defaultErrorCallStackSkip +
  169. baseCallStackSkip
  170. );
  171. }
  172. const loggingEvent = new LoggingEvent(
  173. this.category,
  174. level,
  175. data,
  176. this.context,
  177. callStack,
  178. error
  179. );
  180. clustering.send(loggingEvent);
  181. }
  182. addContext(key, value) {
  183. this.context[key] = value;
  184. }
  185. removeContext(key) {
  186. delete this.context[key];
  187. }
  188. clearContext() {
  189. this.context = {};
  190. }
  191. setParseCallStackFunction(parseFunction) {
  192. if (typeof parseFunction === 'function') {
  193. this.parseCallStack = parseFunction;
  194. } else if (typeof parseFunction === 'undefined') {
  195. this.parseCallStack = defaultParseCallStack;
  196. } else {
  197. throw new TypeError('Invalid type passed to setParseCallStackFunction');
  198. }
  199. }
  200. }
  201. function addLevelMethods(target) {
  202. const level = levels.getLevel(target);
  203. const levelStrLower = level.toString().toLowerCase();
  204. const levelMethod = levelStrLower.replace(/_([a-z])/g, (g) =>
  205. g[1].toUpperCase()
  206. );
  207. const isLevelMethod = levelMethod[0].toUpperCase() + levelMethod.slice(1);
  208. Logger.prototype[`is${isLevelMethod}Enabled`] = function () {
  209. return this.isLevelEnabled(level);
  210. };
  211. Logger.prototype[levelMethod] = function (...args) {
  212. this.log(level, ...args);
  213. };
  214. }
  215. levels.levels.forEach(addLevelMethods);
  216. configuration.addListener(() => {
  217. levels.levels.forEach(addLevelMethods);
  218. });
  219. module.exports = Logger;