UNPKG

@sap/generator-cap-project

Version:

Creates a new SAP Cloud Application Programming Model project.

202 lines (178 loc) 6.46 kB
const { forEach, findKey } = require("lodash"); const stacktrace = require("stacktrace-js"); const { levelsConfig, isValidLogLevel } = require("./levels"); const ADD_SOURCE_LOCATION_INFO = Symbol("addSourceLocationInfo"); const CHANGE_LEVEL = Symbol("changeLevel"); const CHANGE_SOURCE_LOCATION_TRACKING = Symbol("changeSourceLocationTracking"); const LABEL = Symbol("label"); const LOGGER_IMPEL = Symbol("loggerImpel"); const LEVEL_INT = Symbol("levelInt"); const SOURCE_LOCATION_TRACKING = Symbol("sourceLocationTracking"); const CONSOLE_OUTPUT = Symbol("consoleOutput"); const CHILD_LOGGERS = Symbol("childLoggers"); const OUT_CHANNEL = Symbol("outChannel"); const WARN_IF_LOCATION_TRACKING_IS_ENABLED = Symbol( "warnIfLocationTrackingIsEnabled" ); class BaseLogger { /** * @param {object} opts * @param {string} opts.label * @param {string} opts.level * @param {boolean} [opts.sourceLocationTracking] * @param {boolean} [opts.consoleOutput] * @param {import("winston").Logger} opts.loggerImpel */ constructor(opts) { this[LABEL] = opts.label; this[LOGGER_IMPEL] = opts.loggerImpel; this[LEVEL_INT] = levelsConfig[opts.level]; // disabled by default as this may cause performance regressions this[SOURCE_LOCATION_TRACKING] = opts.sourceLocationTracking || false; this[CONSOLE_OUTPUT] = opts.consoleOutput || false; // Possible memory leak if users forget to release their childLogger Resources // Could have been resolved using WeakMap, however Weak Collections in JavaScript are not iterable (yet) // So they are insufficient for our needs (see `getChildLogger` method). // - https://github.com/tc39/proposal-weakrefs#iterable-weakmaps this[CHILD_LOGGERS] = new Map([]); } getChildLogger(opts) { const newLabel = this[LABEL] + "." + opts.label; if (this[CHILD_LOGGERS].has(newLabel)) { return this[CHILD_LOGGERS].get(newLabel); } const newChildLoggerImpel = new BaseLogger({ label: newLabel, sourceLocationTracking: this[SOURCE_LOCATION_TRACKING], consoleOutput: this[CONSOLE_OUTPUT], level: findKey(levelsConfig, val => val === this[LEVEL_INT]), loggerImpel: this[LOGGER_IMPEL] }); this[CHILD_LOGGERS].set(newLabel, newChildLoggerImpel); return newChildLoggerImpel; } fatal(msg, ...args) { if (this[LEVEL_INT] >= levelsConfig.fatal) { this[ADD_SOURCE_LOCATION_INFO](args); args.push({ label: this[LABEL] }); // @ts-ignore this[LOGGER_IMPEL].fatal(msg, ...args); } } error(msg, ...args) { if (this[LEVEL_INT] >= levelsConfig.error) { this[ADD_SOURCE_LOCATION_INFO](args); args.push({ label: this[LABEL] }); this[LOGGER_IMPEL].error(msg, ...args); } } warn(msg, ...args) { if (this[LEVEL_INT] >= levelsConfig.warn) { this[ADD_SOURCE_LOCATION_INFO](args); args.push({ label: this[LABEL] }); this[LOGGER_IMPEL].warn(msg, ...args); } } info(msg, ...args) { if (this[LEVEL_INT] >= levelsConfig.info) { this[ADD_SOURCE_LOCATION_INFO](args); args.push({ label: this[LABEL] }); this[LOGGER_IMPEL].info(msg, ...args); } } debug(msg, ...args) { if (this[LEVEL_INT] >= levelsConfig.debug) { this[ADD_SOURCE_LOCATION_INFO](args); args.push({ label: this[LABEL] }); this[LOGGER_IMPEL].debug(msg, ...args); } } trace(msg, ...args) { if (this[LEVEL_INT] >= levelsConfig.trace) { this[ADD_SOURCE_LOCATION_INFO](args); args.push({ label: this[LABEL] }); // @ts-ignore this[LOGGER_IMPEL].trace(msg, ...args); } } } // private methods using Symbols to hide it, need to be added directly on the prototype BaseLogger.prototype[CHANGE_LEVEL] = function(newLevel) { this[LEVEL_INT] = levelsConfig[newLevel]; // @ts-ignore forEach([...this[CHILD_LOGGERS].values()], childLogger => { // Recursive Call childLogger[CHANGE_LEVEL](newLevel); }); }; // private methods using Symbols to hide it, need to be added directly on the prototype BaseLogger.prototype[CHANGE_SOURCE_LOCATION_TRACKING] = function( isSourceLocTrack ) { this[SOURCE_LOCATION_TRACKING] = isSourceLocTrack; // @ts-ignore forEach([...this[CHILD_LOGGERS].values()], childLogger => { // Recursive Call childLogger[CHANGE_SOURCE_LOCATION_TRACKING](isSourceLocTrack); }); }; BaseLogger.prototype[ADD_SOURCE_LOCATION_INFO] = function(args) { if (this[SOURCE_LOCATION_TRACKING] === true) { const stack = stacktrace.getSync(); // we need to go 2 levels up the stack to get to the end user's code const userFrame = stack[2]; const sourceLocMsg = `${userFrame.fileName}:${userFrame.lineNumber}:${userFrame.columnNumber}`; // Balance between reducing number of properties and readability args.push({ source: { function: userFrame.functionName, location: sourceLocMsg } }); } }; class VSCodeExtLogger extends BaseLogger { /** * @param {object} opts * @param {string} opts.label * @param {string} opts.level * @param {import("winston").Logger} opts.loggerImpel * @param {import("../api").BasicOutputChannel} [opts.outChannel] * @param {boolean} [opts.sourceLocationTracking] * @param {boolean} [opts.consoleOutput] */ constructor(opts) { super(opts); this[OUT_CHANNEL] = opts.outChannel || undefined; this.changeLevel(opts.level); this[WARN_IF_LOCATION_TRACKING_IS_ENABLED](); } changeLevel(newLevel) { if (isValidLogLevel(newLevel) === false) { this.fatal( `Attempt to use unknown logging level: <${newLevel}> has been ignored.` ); } else { // A strange kind of `super.changeLevel` call this[CHANGE_LEVEL](newLevel); } } changeSourceLocationTracking(newSourceLocation) { this[CHANGE_SOURCE_LOCATION_TRACKING](newSourceLocation); this[WARN_IF_LOCATION_TRACKING_IS_ENABLED](); } } // private method using Symbols to hide it, need to be added directly on the prototype VSCodeExtLogger.prototype[WARN_IF_LOCATION_TRACKING_IS_ENABLED] = function() { if (this[SOURCE_LOCATION_TRACKING] === true) { this.fatal( "SourceLocationTracking is Enabled, This must only be used during debugging flows as it causes performance regressions" ); if (this[OUT_CHANNEL]) { this[OUT_CHANNEL].show(); } } }; module.exports = { VSCodeExtLogger: VSCodeExtLogger };