@absolunet/ioc2.1.0

View on GitHub

log/services/Logger/Logger.js

//--------------------------------------------------------
//-- Node IoC - Log - Services - Logger
//--------------------------------------------------------

import __             from '@absolunet/private-registry';
import hasDriver      from '../../../support/mixins/hasDriver';
import DatabaseDriver from './drivers/DatabaseDriver';
import FileDriver     from './drivers/FileDriver';
import StackDriver    from './drivers/StackDriver';


/**
 * Logger service that exposes log methods representing levels.
 * It forwards the log to the configured driver.
 *
 * @memberof log.services
 * @augments support.mixins.HasDriver
 * @hideconstructor
 */
class Logger extends hasDriver() {

	/**
	 * Class dependencies: <code>['app', 'config', log.level']</code>.
	 *
	 * @type {Array<string>}
	 */
	static get dependencies() {
		return (super.dependencies || []).concat(['config', 'log.level']);
	}

	/**
	 * @inheritdoc
	 * @private
	 */
	init() {
		super.init();

		this.addDriver('database', DatabaseDriver);
		this.addDriver('file',     FileDriver);
		this.addDriver('stack',    StackDriver);

		this.setFallbackChannel('single');
	}

	/**
	 * Log emergency message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async emergency(message, context) {
		await this.log(this.LEVEL.EMERGENCY, message, context);
	}

	/**
	 * Log alert message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async alert(message, context) {
		await this.log(this.LEVEL.ALERT, message, context);
	}

	/**
	 * Log critical message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async critical(message, context) {
		await this.log(this.LEVEL.CRITICAL, message, context);
	}

	/**
	 * Log error message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async error(message, context) {
		await this.log(this.LEVEL.ERROR, message, context);
	}

	/**
	 * Log warning message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async warning(message, context) {
		await this.log(this.LEVEL.WARNING, message, context);
	}

	/**
	 * Log notice message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async notice(message, context) {
		await this.log(this.LEVEL.NOTICE, message, context);
	}

	/**
	 * Log info message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async info(message, context) {
		await this.log(this.LEVEL.INFO, message, context);
	}

	/**
	 * Log debug message.
	 *
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async debug(message, context) {
		await this.log(this.LEVEL.DEBUG, message, context);
	}

	/**
	 * Log message with default channel.
	 *
	 * @param {number} level - The log level.
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async log(level, message, context) {
		await this.logWithDefaultChannel(level, message, context);
	}

	/**
	 * Log message with the given channel by name.
	 * Catches errors.
	 *
	 * @param {string} channel - The channel to use.
	 * @param {number} level - The log level.
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async logWithChannel(channel, level, message, context) {
		try {
			await this.unsafeLogWithChannel(channel, level, message, context);
		} catch (error) {
			if (channel !== this.fallbackChannel) {
				await this.logWithFallbackChannel(this.LEVEL.ERROR, `Error thrown while logging: "${error.message}". Switching to [${this.fallbackChannel}] channel.`);
				await this.logWithFallbackChannel(level, message, context);
			}
		}
	}

	/**
	 * Log message with the given channel by name.
	 * Does not catch errors.
	 *
	 * @param {string} channel - The channel to use.
	 * @param {number} level - The log level.
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 * @throws {TypeError} Indicates that the log channel was not found.
	 */
	async unsafeLogWithChannel(channel, level, message, context) {
		const config = this.config.get(`log.channels.${channel}`);

		if (!config) {
			throw new TypeError(`Cannot find log channel [${channel}]`);
		}

		if (!this.isLevelUnderThreshold(level, config.level)) {
			const driver = this.driver(config.driver);
			driver.setConfig(config);

			await driver.log(level, message, context);
		}
	}

	/**
	 * Log with default channel.
	 *
	 * @param {number} level - The log level.
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async logWithDefaultChannel(level, message, context) {
		await this.logWithChannel(this.config.get('log.default'), level, message, context);
	}

	/**
	 * Log with fallback channel.
	 *
	 * @param {number} level - The log level.
	 * @param {string} message - The message.
	 * @param {*} [context] - The context.
	 * @returns {Promise} The async process promise.
	 */
	async logWithFallbackChannel(level, message, context) {
		await this.logWithChannel(this.fallbackChannel, level, message, context);
	}

	/**
	 * Check if the given level is under the given level threshold.
	 *
	 * @param {string|number} level - The level to evaluate.
	 * @param {string|number} [threshold] - The threshold.
	 * @returns {boolean} Indicates that the given level is under the given threshold.
	 */
	isLevelUnderThreshold(level, threshold = 0) {
		return this.getLevelValue(level) < this.getLevelValue(threshold);
	}

	/**
	 * Get parsed level value from either the number value or the verbose string.
	 *
	 * @param {string|number} level - The level.
	 * @returns {number} The level value as integer.
	 */
	getLevelValue(level) {
		return isNaN(level) ? this.LEVEL[level.toUpperCase()] : parseInt(level, 10);
	}

	/**
	 * Set default channel.
	 *
	 * @param {string} channel - The fallback channel name.
	 */
	setFallbackChannel(channel) {
		__(this).set('fallbackChannel', channel);
	}

	/**
	 * Log level enum.
	 *
	 * @type {log.enums.Level}
	 */
	get LEVEL() {
		return __(this).get('log.level');
	}

	/**
	 * The fallback channel.
	 *
	 * @type {string}
	 */
	get fallbackChannel() {
		return __(this).get('fallbackChannel');
	}

}


export default Logger;