Source: logging.js

/*!
 * Module Logging
 */

/**
 * @namespace Logging
 */

const MODULE_NAME = 'Logging';



//###[ DATA ]###########################################################################################################

export const
	LOG_LEVELS = ['log', 'warn', 'err'],
	XLOG_CONTEXT_COUNTS = {}
;

export let
	LOG_LEVEL = 'log',
	TRY_TO_LOG_TO_PARENT = false
;

/*
 * to make log calls chainable, this object is returned for every logging call, this offers extended functionality,
 * going far beyond log/warn/err, while keeping a very narrow interface for everyday stuff
 */
const LOG_CHAINABLE_OBJECT = {
	__documentation_for_chainable_object_of_log_execution__(){
		return 'Use this object to chain logging calls. All standard methods are supported'
			+' (see https://developer.mozilla.org/en-US/docs/Web/API/Console) and are executed'
			+' with silent fails if not supported by the browser. See other methods in this'
			+' object for an overview. Use disable()/enable() to deactivate/activate all debug outputs'
			+' (exceptions are assert, clear, error and warn) to the console centrally'
		;
	},
	setLogLevel(level){
		level = `${level}`;

		if( LOG_LEVELS.includes(level) ){
			LOG_LEVEL = level;
		}

		return log();
	},
	tryToLogToParent(setting){
		setting = (setting === undefined) ? true : !!setting;
		TRY_TO_LOG_TO_PARENT = setting;

		return log();
	},
	assert : genericConsoleMethodWrapperFactory('assert'),
	clear : genericConsoleMethodWrapperFactory('clear'),
	count : genericConsoleMethodWrapperFactory('count'),
	dir : genericConsoleMethodWrapperFactory('dir'),
	dirxml : genericConsoleMethodWrapperFactory('dirxml'),
	dirXml : genericConsoleMethodWrapperFactory('dirxml'),
	error(){
		return err(...Array.from(arguments));
	},
	group : genericConsoleMethodWrapperFactory('group'),
	groupCollapsed : genericConsoleMethodWrapperFactory('groupCollapsed'),
	groupEnd : genericConsoleMethodWrapperFactory('groupEnd'),
	info : genericConsoleMethodWrapperFactory('info'),
	log(){
		return log(...Array.from(arguments));
	},
	profile : genericConsoleMethodWrapperFactory('profile'),
	profileEnd : genericConsoleMethodWrapperFactory('profileEnd'),
	table : genericConsoleMethodWrapperFactory('table'),
	time : genericConsoleMethodWrapperFactory('time'),
	timeEnd : genericConsoleMethodWrapperFactory('timeEnd'),
	timeLog : genericConsoleMethodWrapperFactory('timeLog'),
	timeStamp : genericConsoleMethodWrapperFactory('timeStamp'),
	trace : genericConsoleMethodWrapperFactory('trace'),
	warn(){
		return warn(...Array.from(arguments));
	}
};



//###[ HELPERS ]########################################################################################################

/*
 * generically wraps console functions for chainability even if method is unavailable or fails
 * used in LOG_CHAINABLE_OBJECT below
 *
 * @private
 */
function genericConsoleMethodWrapper(name, logLevel=null, ...args){
	name = `${name}`;
	logLevel = LOG_LEVELS.includes(`${logLevel}`) ? `${logLevel}` : LOG_LEVELS[0];

	if( LOG_LEVELS.indexOf(LOG_LEVEL) <= LOG_LEVELS.indexOf(logLevel) ){
		if( typeof window.console?.[name] === 'function' ){
			try {
				if( TRY_TO_LOG_TO_PARENT ){
					parent.console[name].apply(parent.console, args);
				} else {
					console[name].apply(console, args);
				}
			} catch(ex){
				try {
					warn(`console call to "${name}" failed, implementation seemingly incompatible`);
				} catch(ex){}
			}
		} else {
			try {
				warn(`console call to "${name}" failed, is seemingly not supported`);
			} catch(ex){}
		}
	}

	return log();
}

/*
 * prepare an executable wrapper version based on a specific function name
 * used in LOG_CHAINABLE_OBJECT below
 *
 * @private
 */
function genericConsoleMethodWrapperFactory(name, logLevel){
	return function(){
		return genericConsoleMethodWrapper(name, logLevel, ...Array.from(arguments));
	};
}



//###[ EXPORTS ]########################################################################################################

/**
 * @namespace Logging:log
 */

/**
 * Logs a message to the console. Prevents errors in browsers, that don't support this feature.
 * This method is chainable (always returns a chainable object with all methods) and wraps all
 * advanced logging methods like dir, assert and count (https://developer.mozilla.org/en-US/docs/Web/API/Console).
 *
 * Use setLogLevel() on the chainable object to globally define the current log level.
 * 'log' is the default value and allows for all logs to appear on console. 'warn' reduces logs to warnings and errors.
 * 'err' only shows errors on console. Use this to mute debug stuff in production.
 *
 * You can use the method tryToLogToParent(true/false) to instruct log to try to log to the parent window also,
 * which comes in handy if you are developing inside a same domain iframe.
 *
 * @param {...*} [...] - any number of arguments you wish to log
 * @returns {Object} - chainable logging object
 *
 * @memberof Logging:log
 * @alias log
 * @example
 * log(randomVar, 'string');
 * log(false, true);
 * log().group().log(1).log(2).log(3).groupEnd().error('ouch');
 * log().setLogLevel('warn');
 * log('test', {test : 'test'}).setLogLevel('warn').warn('oh noez, but printed').log('not printed').setLogLevel('log').clear();
 * log().tryToLogToParent().log('hooray times two').tryToLogToParent(false);
 */
export function log(){
	if(
		(typeof window.console?.log === 'function')
		&& (LOG_LEVELS.indexOf(LOG_LEVEL) <= LOG_LEVELS.indexOf('log'))
	){
		Array.from(arguments).forEach(obj => {
			if( (obj  === true) || (obj === false) ){
				obj = obj ? 'true' : 'false';
			}

			if( TRY_TO_LOG_TO_PARENT ){
				parent.console.log(obj);
			} else {
				console.log(obj);
			}
		});
	}

	return LOG_CHAINABLE_OBJECT;
}



/**
 * @namespace Logging:warn
 */

/**
 * Logs a warning to the console. Prevents errors in browsers, that don't support this feature.
 *
 * @param {...*} [...] - add any number of arguments you wish to log
 *
 * @memberof Logging:warn
 * @alias warn
 * @example
 * warn('warning yo!');
 * warn(randomVar, 'string');
 * warn(false);
 * warn(true);
 */
export function warn(){
	if(
		(typeof window.console?.warn === 'function')
		&& (LOG_LEVELS.indexOf(LOG_LEVEL) <= LOG_LEVELS.indexOf('warn'))
	){
		Array.from(arguments).forEach(obj => {
			if( (obj  === true) || (obj === false) ){
				obj = obj ? 'true' : 'false';
			}

			if( TRY_TO_LOG_TO_PARENT ){
				parent.console.warn(obj);
			} else {
				console.warn(obj);
			}
		});
	}

	return LOG_CHAINABLE_OBJECT;
}



/**
 * @namespace Logging:err
 */

/**
 * Logs an error to the console. Prevents errors in browsers, that don't support this feature.
 *
 * This function is not named error because that already might be misleading with the Error object and is not a verb.
 *
 * @param {...*} [...] - add any number of arguments you wish to log
 *
 * @memberof Logging:err
 * @alias err
 * @example
 * err('error yo!');
 * err(randomVar, 'string');
 * err(false);
 * err(true);
 */
export function err(){
	if(
		(typeof window.console?.error === 'function')
		&& (LOG_LEVELS.indexOf(LOG_LEVEL) <= LOG_LEVELS.indexOf('err'))
	){
		Array.from(arguments).forEach(obj => {
			if( (obj  === true) || (obj === false) ){
				obj = obj ? 'true' : 'false';
			}

			if( TRY_TO_LOG_TO_PARENT ){
				parent.console.error(obj);
			} else {
				console.error(obj);
			}
		});
	}

	return LOG_CHAINABLE_OBJECT;
}



/**
 * @namespace Logging:xlog
 */

/**
 * X marks the spot. A very simple method for urgent cases of printf-debugging.
 * Simply logs the context of the call to the console, also providing a counter,
 * counting the executions from that context.
 *
 * For real detailed debugging, you have to instantiate/throw an error and work with that information,
 * this is just for quick checking.
 *
 * This method uses Error.stack, which is a rather wonky and unstable feature feature. Use this for debugging and better
 * remove it again afterwards. For more info on Error.stack, read:
 * https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack
 *
 * @param {?String} [message=null] - an optional message to be printed besides the context information
 *
 * @memberof Logging:xlog
 * @alias xlog
 * @example
 * for( let i = 0; i < 10; i++ ){
 *   xlog('purely optional message');
 * }
 */
export function xlog(message=null){
	let context;

	try {
		context = (new Error()).stack.split('\n')[2].trim();
	} catch(ex){
		context = 'anonymous';
	}

	if( XLOG_CONTEXT_COUNTS[context] === undefined ){
		XLOG_CONTEXT_COUNTS[context] = 0;
	}
	XLOG_CONTEXT_COUNTS[context]++;

	log(`<<XLOG>> ${context} [${XLOG_CONTEXT_COUNTS[context]}]${!!message ? ' | '+message : ''}`);
}