UNPKG

node-red-contrib-uibuilder

Version:

Easily create data-driven web UI's for Node-RED. Single- & Multi-page. Multiple UI's. Work with existing web development workflows or mix and match with no-code/low-code features.

181 lines (161 loc) 5.98 kB
/* eslint-disable jsdoc/no-defaults */ // @ts-nocheck /** * @module logger * @description A simple logger for the front-end module. * Enhances standard console logging with additional features. * Ensures that the original calling context is preserved so that the correct calling line number is shown. * Exposes a global `log` object for ease of use. In addition to exporting the logger object. * * @example * log.error('This is an error message', { some: 'data' }) * * @version 1.0.0 * @license Apache-2.0 * @author Julian Knight (Totally Information) * @copyright (c) 2025-2025 Julian Knight (Totally Information) */ // TODO: // - Add a `level` property to set the logging level. // - Add a `styles` property to set the logging styles. // - Add a `version` property to show the version. // - Swap uibuilder and ui modules to use this logger. const nativeConsole = { ...window.console } // Clone the original console /** Recursively builds an object with all properties of an object, including getters and setters * @param {object} obj - The object to log * @param {string} [prefix=''] - The prefix for nested properties * @returns {object} - The nested object with all properties */ const buildDeepPropertiesObject = (obj, prefix = '') => { const result = {} const descriptors = Object.getOwnPropertyDescriptors(obj) for (const key in descriptors) { if (Object.prototype.hasOwnProperty.call(descriptors, key)) { const descriptor = descriptors[key] const newPrefix = prefix ? `${prefix}.${key}` : key if (descriptor.get || descriptor.set) { result[newPrefix] = { get: !!descriptor.get, set: !!descriptor.set } } else if (typeof descriptor.value === 'object' && descriptor.value !== null) { result[newPrefix] = buildDeepPropertiesObject(descriptor.value, newPrefix) } else { result[newPrefix] = descriptor.value } } } return result } // Create the new logger object - so that logger can be called without a property function - i.e. log(...) - easy shorthand // const logger = {} const logger = nativeConsole['log'].bind( nativeConsole, '%c[Custom]%c', 'color: green; font-weight: bold;', '' ) // Set the logging level logger.level = 'info' // Set the logging styles logger.styles = { // 0 error: { level: 0, css: 'background: red; color: black;', txtCss: 'color: red; ', pre: '⛔ ', console: 'error', // or trace }, // 1 warn: { level: 1, css: 'background: darkorange; color: black;', txtCss: 'color: darkorange; ', pre: '⚠ ', console: 'warn', }, // 2 info: { level: 2, css: 'background: aqua; color: black;', txtCss: 'color: aqua;', pre: '❗ ', console: 'info', }, // 3 log: { level: 3, css: 'background: grey; color: yellow;', txtCss: 'color: grey;', pre: '', console: 'log', }, // 4 debug: { level: 4, css: 'background: chartreuse; color: black;', txtCss: 'color: chartreuse;', pre: '', console: 'debug', }, // 5 trace: { level: 5, css: 'background: indigo; color: yellow;', txtCss: 'color: hotpink;', pre: '', console: 'trace', }, // General data and default styles '_': { names: ['error', 'warn', 'info', 'log', 'debug', 'trace'], reset: 'color: inherit;', head: 'font-weight:bold; font-style:italic;', level: 'font-weight:bold; border-radius: 3px; padding: 2px 5px; display:inline-block;', }, } // Attach enhanced console methods to the logger object Object.keys(nativeConsole).forEach(method => { if (typeof nativeConsole[method] === 'function') { // By using bind, we ensure that the original calling context is preserved because the stack is preserved. if (method in logger.styles) { // Not all console methods are stylable logger[method] = nativeConsole[method].bind( nativeConsole, // `%c${logger.styles[method].pre}${method}%c [%s]`, // %s is the 1st actual arg `%c${logger.styles[method].pre}${method}%c`, `${logger.styles._.level} ${logger.styles[method].css}`, // 1st %c format `${logger.styles._.head} ${logger.styles[method].txtCss}`, // 2nd %c format ) } else { logger[method] = nativeConsole[method].bind( nativeConsole ) } } }) console.log('LOGGER', buildDeepPropertiesObject(logger)) export default { logger } if (!window.log) { window.log = logger } // // Attach the log level to the callable function - using a getter and setter // Object.defineProperty(this.callable, 'level', { // get: () => this.level, // set: (newLevel) => { // if (LOG_STYLES._.names.includes(newLevel)) { // this.level = newLevel // } else { // this.callable.error(`Invalid logging level: ${newLevel}`) // } // } // }) // this.callable.valueOf = this.callable.toString = () => `Logger v${this.callable.version} - Level: ${this.callable.level}` // Add the ability to this.callable to return some help text to the console - ideally it would not need to be called as a function // this.callable.help = () => { // const helpText = [ // `\tLogger v${this.version} - Level: ${this.callable.level}`, // `\tAvailable logging levels: ${LOG_STYLES._.names.join(', ')}`, // '\tAdditionally, all console methods are available.', // '\tTo change the logging level, use: logger.setLevel(\'level\')', // ] // this.callable.info('UIBUILDER Custom Logger help', helpText.join('\n')) // }