@toreda/log
Version:
Lightweight TypeScript logger with flexible custom transports.
457 lines (456 loc) • 15 kB
JavaScript
"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;