UNPKG

@toreda/log

Version:

Lightweight TypeScript logger with flexible custom transports.

457 lines (456 loc) 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Log = void 0; const global_1 = require("./log/options/global"); const group_1 = require("./log/options/group"); const levels_1 = require("./levels"); const global_2 = require("./log/state/global"); const group_2 = require("./log/state/group"); const transport_1 = require("./transport"); const level_1 = require("./check/level"); const console_1 = require("./console"); /** * Main log class holding attached transports and internal state * data, and logging configuration. */ class Log { constructor(options = {}) { var _a, _b, _c, _d; let enabled; let level; let parent; let path; if ((0, global_1.isLogOptionsGlobal)(options)) { path = (options === null || options === void 0 ? void 0 : options.id) ? [options.id] : []; this.globalState = new global_2.LogStateGlobal(options); this.globalState.groups.set((_a = options === null || options === void 0 ? void 0 : options.id) !== null && _a !== void 0 ? _a : 'default', this); level = this.globalState.globalLevel.get(); this.globalState.globalLevel.set(level); enabled = this.globalState.groupsStartEnabled; for (const groupOptions of (_b = options.startingGroups) !== null && _b !== void 0 ? _b : []) { this.makeLog(groupOptions.id, groupOptions); } } else if ((0, group_1.isLogOptionsGroup)(options)) { this.globalState = options.state; parent = options.parent; path = options.id.split('.'); level = (_c = options.level) !== null && _c !== void 0 ? _c : this.globalState.globalLevel.get(); enabled = (_d = options.enabled) !== null && _d !== void 0 ? _d : this.globalState.groupsStartEnabled; } else { throw Error(`Bad Log init - 'state' was not an instance of LogStateGlobal.`); } this.groupState = new group_2.LogStateGroup({ id: path.join('.'), parent, path, level, enabled }); // Activate console logging if allowed by start options. if (this.globalState.consoleEnabled) { this.activateDefaultConsole(); } for (const transport of this.globalState.transports) { this.addTransport(transport); } } /** * Enable global console logging for development and debugging. */ activateDefaultConsole(level = levels_1.Levels.ALL_EXTENDED) { this.addTransport({ id: 'console', level, action: console_1.logToConsole }); } deactivateDefaultConsole() { this.removeTransportById('console'); } /** * Sets the level of the default console to the level * of the log. */ resetLevelDefaultConsole() { this.setLevelDefaultConsole(this.groupState.level.get()); } setLevelDefaultConsole(level) { const console = this.getTransport('console'); if (!console) { return; } console.level.set(level); } enableLevelDefaultConsole(level) { const console = this.getTransport('console'); if (!console) { return; } console.level.enableLevel(level); } disableLevelDefaultConsole(level) { const console = this.getTransport('console'); if (!console) { return; } console.level.disableLevel(level); } makeLog(id, options) { var _a; if (!id || typeof id !== 'string') { return null; } const path = this.groupState.path.concat(id); const groupId = path.join('.'); const preexistingGroup = this.globalState.groups.get(groupId); if (preexistingGroup != null) { return preexistingGroup; } const level = options && (0, level_1.checkLevel)(options.level) ? options.level : this.globalState.globalLevel.get(); const enabled = (_a = options === null || options === void 0 ? void 0 : options.enabled) !== null && _a !== void 0 ? _a : this.globalState.groupsStartEnabled; const group = new Log({ state: this.globalState, id: groupId, parent: this, path, level, enabled }); this.globalState.groups.set(groupId, group); return group; } /** * Add transport to log. * @param transportData Transport to add to log. */ addTransport(transportData) { if (transportData == null) { return false; } const transport = (() => { if (transportData instanceof transport_1.Transport) { return transportData; } else { const transport = this.getTransport(transportData.id); if (transport != null) { return transport; } return new transport_1.Transport(transportData); } })(); if (this.groupState.transports.has(transport)) { return false; } this.groupState.transports.add(transport); return true; } getTransport(transportId) { for (const transport of this.groupState.transports) { // Remove matching transport and exit. Only one // of each transport can be added to a group. if (transport.id === transportId) { return transport; } } return null; } /** * Remove transport from target group, or from the 'all' group if * id is null. * @param transport */ removeTransport(transport) { if (!transport) { return false; } if (!this.groupState.transports.has(transport)) { return false; } this.groupState.transports.delete(transport); return true; } /** * Remove transport matching target id from target group if * both the group exists and the transport is in the group. * @param transportId */ removeTransportById(transportId) { const transport = this.getTransport(transportId); return this.removeTransport(transport); } /** * Remove multiple transports in one call by providing an array * of log transports to remove from this group. * @param transports */ removeTransports(transports) { if (!Array.isArray(transports)) { return false; } let success = false; for (const transport of transports) { const result = this.removeTransport(transport); if (result) { success = true; } } return success; } /** * Remove matching transports from all groups. Expensive call not suitable to * use generally, but available for specific cases where transports must be removed * and may exist in multiple unknown groups. Prefer to use use `removeTransport` or * `removeGroupTransport` when possible. * @param transport */ removeTransportEverywhere(transport) { if (!transport || !(transport instanceof transport_1.Transport)) { return false; } let removeCount = 0; this.globalState.groups.forEach((group) => { if (group.removeTransport(transport)) { removeCount++; } }); return removeCount > 0; } /** * All Logs become disabled without changing * the state of each log individually. */ enforceGlobalDisable() { this.globalState.forceDisabled = true; this.globalState.forceEnabled = false; } /** * All Logs become enabled without changing the * state of each log individually. */ enforceGlobalEnable() { this.globalState.forceEnabled = true; this.globalState.forceDisabled = false; } /** * Both global enable and disable are turned * off. Logs rely on their own setting. */ useGroupEnable() { this.globalState.forceDisabled = false; this.globalState.forceEnabled = false; } /** * Enable log. */ enable() { this.groupState.enabled = true; } /** * Disable log. */ disable() { this.groupState.enabled = false; } /** * Change global log level. Individual group levels * are used instead of global level when they are set. * @param level */ setGlobalLevel(level) { this.globalState.globalLevel.set(level); } /** * Add a level flag to the global log level without * affecting other global level flags. Has no effect * if target level flag is already enabled. * @param level */ enableGlobalLevel(level) { this.globalState.globalLevel.enableLevel(level); } /** * Add multiple flags to global log level. Performs * sanity checks on each provided level and discards * invalid values. * @param levels */ enableGlobalLevels(levels) { this.globalState.globalLevel.enableLevels(levels); } disableGlobalLevel(level) { this.globalState.globalLevel.disableLevel(level); } disableGlobalLevels(levels) { this.globalState.globalLevel.disableLevels(levels); } /** * Set log level for target group. * @param level * @param id */ setGroupLevel(level) { this.groupState.level.set(level); } enableGroupLevel(level) { this.groupState.level.enableLevel(level); } enableGroupLevels(levels) { this.groupState.level.enableLevels(levels); } disableGroupLevel(level) { this.groupState.level.disableLevel(level); } disableGroupLevels(levels) { this.groupState.level.disableLevels(levels); } /** * Create structured log message. Provided as a call argument * during transport execution. * @param ts UTC timestamp when msg was created. * @param level Level bitmask msg was logged with. * @param msg Msg that was logged. */ createMessage(level, path, ...message) { const date = Date.now(); return { date, level, message, path }; } stringifyMessage(msg) { if (typeof msg === 'string') { return msg; } if (msg instanceof Error) { return `\n${msg.stack}`; } const mightBeToStringableMsg = msg; if (typeof mightBeToStringableMsg.toString === 'function') { return mightBeToStringableMsg.toString(); } return JSON.stringify(msg); } /** * Determine whether group transport can execute target log * message. Checks msg log level against global log level, * group log level, and transport log level. * @param transport * @param globalLevel * @param msgLevel */ canExecute(group, transportLevel, msgLevel) { if (this.globalState.forceDisabled) { return false; } if (!this.globalState.forceEnabled && !group.groupState.enabled) { return false; } if (!(0, level_1.checkLevel)(transportLevel)) { return false; } if (!(0, level_1.checkLevel)(msgLevel)) { return false; } const activeMask = this.globalState.globalLevel.get() | group.groupState.level.get(); return (activeMask & transportLevel & msgLevel) > 0; } /** * Log message to default group. * @param msgLevel * @param msg */ log(msgLevel, ...msg) { if (this.globalState.forceDisabled) { return Promise.resolve(false); } if (!(0, level_1.checkLevel)(msgLevel)) { return Promise.resolve(false); } const message = this.createMessage(msgLevel, this.groupState.path.slice(), ...msg); const actions = []; const transports = new Map(); let group = this; while (group) { if (group.groupState.enabled || group.globalState.forceEnabled) { for (const transport of group.groupState.transports) { if (!transports.has(transport.id)) { transports.set(transport.id, { group, transport }); } } } group = group.groupState.parent; } for (const [id, { group, transport }] of transports) { if (this.canExecute(group, transport.level.get(), msgLevel)) { const result = transport.execute(message).then((res) => { return [id, res]; }); actions.push(result); } else { actions.push(Promise.resolve([id, false])); } } return Promise.all(actions).then((res) => { const result = {}; let failed = false; res.forEach((action) => { if (action[1] !== true) { failed = true; result[action[0]] = action[1]; } }); if (!failed) { return true; } return result; }); } /** * Trigger an error-level log message for no specific group (global). * @param msg */ error(...msg) { return this.log(levels_1.Levels.ERROR, ...msg); } /** * Trigger a warn-level log message for no specific group (global). * @param msg */ warn(...msg) { return this.log(levels_1.Levels.WARN, ...msg); } /** * Trigger an info-level log message for no specific group (global). * @param args */ info(...msg) { return this.log(levels_1.Levels.INFO, ...msg); } /** * Trigger a -level log message for no specific group (global). * @param msg */ debug(...msg) { return this.log(levels_1.Levels.DEBUG, ...msg); } /** * Trigger a trace-level log message for no specific group (global). * @param args */ trace(...msg) { return this.log(levels_1.Levels.TRACE, ...msg); } /** * Clear all transports from this group. */ clear() { this.groupState.transports.clear(); } /** * Clear all transports from all groups. */ clearAll() { this.globalState.groups.forEach((group) => { group.clear(); }); } /** * Remove all groups except the initial group. * Clear all transport from the initial group. */ reset() { const [initialGroup] = this.globalState.groups.values(); for (const [key, group] of this.globalState.groups) { if (group !== initialGroup) { group.globalState.groups.delete(key); } } initialGroup.clear(); return this.globalState.groups[0]; } } exports.Log = Log;