UNPKG

mcode-log

Version:

A stand-alone package of core code for logging and debugging.

1,048 lines (947 loc) 96.5 kB
// #region F I L E // <copyright file="mcode-log/index.js" company="MicroCODE Incorporated">Copyright © 2022-2024 MicroCODE, Inc. Troy, MI</copyright><author>Timothy J. McGuire</author> // #region M O D U L E // #region D O C U M E N T A T I O N /** * Project: MicroCODE MERN Applications * Customer: Internal + MIT xPRO Course * @module 'mcode-log.js' * @memberof mcode * @created January 2022-2024 * @author Timothy McGuire, MicroCODE, Inc. * @description > * MicroCODE Shared App Logging Library * * LICENSE: * -------- * MIT License: MicroCODE.mcode-log * * Copyright (c) 2022-2024 Timothy McGuire, MicroCODE, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * * DESCRIPTION: * ------------ * This module implements the MicroCODE's Common JavaScript functions for logging and debugging. * * * REFERENCES: * ----------- * 1. MIT xPRO Course: Professional Certificate in Coding: Full Stack Development with MERN * * 2. List of ANSI Color Escape Sequences * https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences * * 3. Showing Line Numbers in console.log from Node.js * https://stackoverflow.com/questions/45395369/how-to-get-console-log-line-numbers-shown-in-nodejs * * * * * MODIFICATIONS: * -------------- * Date: By-Group: Rev: Description: * * 27-Jan-2022 TJM-MCODE {0001} New module for common reusable Javascript logging functions. * 05-Mar-2022 TJM-MCODE {0002} Documentation updates. * 04-May-0222 TJM-MCODE {0003} Corrected 'month' in timeStamp. * 03-Oct-2022 TJM-MCODE {0004} Added 'log()' to simplify console logging of app events. * 03-Oct-2022 TJM-MCODE {0005} Added use of 'vt' for colorizing Console Log entries. * 16-Oct-2022 TJM-MCODE {0006} Added 'success' as a severity. * 30-Oct-2023 TJM-MCODE {0007} Updated to TypeScript, reversed to pure JavaScript in Jan 2024. * 03-Dec-2023 TJM-MCODE {0008} Don't log 'debug' messages in staging or production mode. * 21-Jan-2024 TJM-MCODE {0009} Converted to a single ES6 Module (ESM) for use in both * Frontend/Client and Backend/Server as a NodeJS package. * 01-Feb-2024 TJM-MCODE {0010} Changed to the Universal Module Definition (UMD) pattern to support AMD, * CommonJS/Node.js, and browser global in our exported module. * 02-Mar-2024 TJM-MCODE {0011} Added 'logobj()', 'expobj()', 'isFunction()', 'hexify()', 'octify()', and 'colorizeLines()' * all in the pursuit of a more complete and consistent logging and debugging experience, * in both the Console, NPM, and the Browser's DevTools. * 06-Jul-2024 TJM-MCODE {0012} 0.4.00 - moved all 'data' functions into sub-package 'mcode-data'. * 22-Aug-2024 TJM-MCODE {0013} 0.4.04 - corrected 'colorizeLines()' to carry on embedded colors to following lines. * 22-Aug-2024 TJM-MCODE {0014} 0.4.05 - corrected 'logify()' to accept all legal JSON Key names. * 19-Feb-2025 TJM-MCODE {0015} 0.5.08 - updated 'resx()' to support returning non-db entity results, * to carry this common response code into our HTMX UI responses. * 21-Feb-2025 TJM-MCODE {0016} 0.5.09 - optimized many functions, standardized on '' strings instead of a mix * of "" and '', now "" only used when embedded ' are needed. * - fixed an issues in 'logify*()' with string arrays where element had embedded ". * 08-Mar-2025 TJM-MCODE {0017} 0.5.10 - updated resx() handle HTTP Status 204 properly with '.end()'. * 16-Mar-2025 TJM-MCODE {0018} 0.6.07 - Passing MODULE_NAME is now optional and the logging functions * all log complete source path and line # of the caller automatically. * 17-Apr-2025 TJM-MCODE {0019} 0.6.08 - added 'logifyObject()' to handle all objects, including arrays and JSON. * 18-Apr-2025 TJM-MCODE {0020} 0.6.09 - add support for passing 'data' thru 'resx()' to support HTML responses for * HTMX UI swaps, specifically to support an 'app-banner', * added 'fatal' severity to align with app-banner. * 24-Apr-2025 TJM-MCODE {0021} 0.6.09 - added support for UUID 'Event Id' as optional data to be logged for traceability. * 03-Oct-2025 TJM-MCODE {0022} 0.7.00 - added support for HTML output thru the use of a new 'ht' table used in * combination with the existing 'vt' table. * 16-Oct-2025 TJM-MCODE {0023} 0.7.04 - fixed logify() to stop inserting blank line before and between JSON objects. * Also fixed logObj() to always use logifyObject(), even for simple arrays to * correct a unique indentation issue. * 19-Oct-2025 TJM-MCODE {0024} 0.7.05 - fixed 'getFrom()' to work properly in non-NodeJS environments by checking for 'process.versions.node'. * * * * * NOTE: This module follow's MicroCODE's JavaScript Style Guide and Template JS file, see: * * o https://github.com/MicroCODEIncorporated/JavaScriptSG * o https://github.com/MicroCODEIncorporated/TemplatesJS * * ...be sure to check out the CTRL-SHIFT+K, +L, +J keybaord shortcuts in Visual Studio Code * for taking advance of the #regions in this file and our templates. * * */ // #endregion // #region I M P O R T S const _data = require('mcode-data'); const isNode = typeof process !== 'undefined' && process.versions?.node; const path = isNode ? require('path') : null; const packageJson = require('./package.json'); // #endregion // #region T Y P E S // #endregion // #region I N T E R F A C E S // #endregion // #region C O N S T A N T S, F U N C T I O N S – P U B L I C // MicroCODE: define this module's name for our 'mcode-log' package const MODULE_NAME = 'mcode-log.js'; const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const theme = process.env.THEME || 'dark'; // default to dark mode const mode = process.env.NODE_ENV || 'development'; // default to development mode /** * @namespace mcode * @desc mcode namespace containing functions and constants. */ const mcode = { /** * @const vt * @memberof mcode * @desc Colors constants for changing Console appearance ala DEC's VT52 + VT100 + VT220. * @example ANSI Color Escape Sequence \x1b[***m -- where '***' is a series of command codes separated by semi-colons (;). Code Effect -- notes ------------------------------------------------------------------------------ 0 Reset / Normal -- all attributes off 1 Bold -- increased intensity 2 Faint -- decreased intensity - not widely supported 3 Italic -- not widely supported, sometimes treated as inverse 4 Underline 5 Slow Blink -- less than 150 per minute 6 Rapid Blink -- MS-DOS ANSI.SYS; 150+ per minute; not widely supported 7 Reverse video -- swap foreground and background colors 8 Conceal -- not widely supported. 9 Crossed-out -- characters legible, but marked for deletion. Not widely supported 10 Primary font (default) 11–19 Alternate font -- select alternate font n-10 20 Fraktur -- hardly ever supported 21 Bold off or Double Underline -- bold off not widely supported; double underline hardly ever supported 22 Normal color or intensity -- neither bold nor faint 23 Not italic, not Fraktur 24 Underline roundOff -- not singly or doubly underlined 25 Blink off 27 Inverse off 28 Reveal conceal off 29 Not crossed out 30–37 Set foreground color -- see color table below 38 Set foreground color -- next arguments are 5;<n> or 2;<r>;<g>;<b>, see below 39 Default foreground color -- implementation defined (according to standard) 40–47 Set background color -- see color table below 48 Set background color -- next arguments are 5;<n> or 2;<r>;<g>;<b>, see below 49 Default background color -- implementation defined (according to standard) 51 Framed 52 Encircled 53 Overlined 54 Not framed or encircled 55 Not overlined 60 ideogram underline -- hardly ever supported 61 ideogram double underline -- hardly ever supported 62 ideogram overline -- hardly ever supported 63 ideogram double overline -- hardly ever supported 64 ideogram stress marking hardly ever supported 65 ideogram attributes off reset the effects of all of 60-64 90–97 Set bright foreground color aixterm (not in standard) 100–107 Set bright background color aixterm (not in standard) * */ vt: { notice: "This is a test string for logifying 'mcode' as an object during testing.", // common effects, predefined ANSI escape sequences reset: '\x1b[0m', bold: '\x1b[1m', bright: '\x1b[1m', dim: '\x1b[2m', faint: '\x1b[2m', italic: '\x1b[3m', underscore: '\x1b[4m', underline: '\x1b[4m', blink: '\x1b[5m', blink_slow: '\x1b[5m', blink_fast: '\x1b[6m', reverse: '\x1b[7m', hidden: '\x1b[8m', conceal: '\x1b[8m', strikethru: '\x1b[9m', crossed_out: '\x1b[9m', // foreground colors fg: { black: '\x1b[30m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', }, // background colors bg: { black: '\x1b[40m', red: '\x1b[41m', green: '\x1b[42m', yellow: '\x1b[43m', blue: '\x1b[44m', magenta: '\x1b[45m', cyan: '\x1b[46m', white: '\x1b[47m', }, // colors for event severity: dark light gray: (theme === 'dark') ? '\x1b[90m' : '\x1b[30m', // gray errr: (theme === 'dark') ? '\x1b[91m' : '\x1b[31m', // red good: (theme === 'dark') ? '\x1b[92m' : '\x1b[32m', // green warn: (theme === 'dark') ? '\x1b[93m' : '\x1b[33m', // yellow cold: (theme === 'dark') ? '\x1b[94m' : '\x1b[34m', // blue dead: (theme === 'dark') ? '\x1b[95m' : '\x1b[35m', // magenta code: (theme === 'dark') ? '\x1b[96m' : '\x1b[36m', // cyan info: (theme === 'dark') ? '\x1b[97m' : '\x1b[37m', // white dbug: (theme === 'dark') ? '\x1b[38;2;255;140;0m' : '\x1b[38;5;208m', // orange // custom JSON colors -- see 'logify()' for use punc: '\x1b[96m\x1b[1m', // string value - CYAN, BOLD key: '\x1b[97m', // key name - WHITE string: '\x1b[96m', // string value - CYAN integer: '\x1b[94m', // integer value - BLUE real: '\x1b[92m', // floating point value - GREEN bigint: '\x1b[95m', // bigint value - MAGENTA true: '\x1b[92m', // boolean true - BRIGHT GREEN (LIME) false: '\x1b[91m', // boolean false - RED null: '\x1b[90m', // null value - GRAY value: '\x1b[93m', // fallback for other values - YELLOW nl: '\n' // newline }, /** * @const ht * @memberof mcode * @desc HTML inline style constants for generating HTML with colorized appearance similar to the VT ANSI colors. * @example Supported HTML equivalents for ANSI Color Escape Sequence Code Effect -- notes ------------------------------------------------------------------------------ reset end span bold font-weight: 600 bright font-weight: 600 dim opacity: 0.6 faint opacity: 0.6 italic font-style: italic underscore text-decoration: underline underline text-decoration: underline blink animation: blink 1s infinite blink_slow animation: blink 2s infinite blink_fast animation: blink 0.5s infinite reverse filter: invert(1) hidden visibility: hidden conceal visibility: hidden strikethru text-decoration: line-through crossed_out text-decoration: line-through */ ht: { notice: "This is a test string for HTML-ifying 'mcode' as an object during testing.", // common effects, HTML inline styles reset: '</span>', bold: '<span style="font-weight: 600;">', bright: '<span style="font-weight: 600;">', dim: '<span style="opacity: 0.6;">', faint: '<span style="opacity: 0.6;">', italic: '<span style="font-style: italic;">', underscore: '<span style="text-decoration: underline;">', underline: '<span style="text-decoration: underline;">', blink: '<span style="animation: blink 1s infinite;">', blink_slow: '<span style="animation: blink 2s infinite;">', blink_fast: '<span style="animation: blink 0.5s infinite;">', reverse: '<span style="filter: invert(1);">', hidden: '<span style="visibility: hidden;">', conceal: '<span style="visibility: hidden;">', strikethru: '<span style="text-decoration: line-through;">', crossed_out: '<span style="text-decoration: line-through;">', // foreground colors fg: { black: '<span style="color: black;">', red: '<span style="color: red;">', green: '<span style="color: green;">', yellow: '<span style="color: yellow;">', blue: '<span style="color: blue;">', magenta: '<span style="color: magenta;">', cyan: '<span style="color: cyan;">', white: '<span style="color: white;">', }, // background colors bg: { black: '<span style="background-color: black;">', red: '<span style="background-color: red;">', green: '<span style="background-color: green;">', yellow: '<span style="background-color: yellow;">', blue: '<span style="background-color: blue;">', magenta: '<span style="background-color: magenta;">', cyan: '<span style="background-color: cyan;">', white: '<span style="background-color: white;">', }, // colors for event severity: dark light themes gray: (theme === 'dark') ? '<span style="color: #999999;">' : '<span style="color: #333333;">', // gray errr: (theme === 'dark') ? '<span style="color: #ff6b6b;">' : '<span style="color: #d32f2f;">', // red good: (theme === 'dark') ? '<span style="color: #51cf66;">' : '<span style="color: #388e3c;">', // green warn: (theme === 'dark') ? '<span style="color: #efff3b;">' : '<span style="color: #cbc800;">', // yellow cold: (theme === 'dark') ? '<span style="color: #339af0;">' : '<span style="color: #1976d2;">', // blue dead: (theme === 'dark') ? '<span style="color: #e599f7;">' : '<span style="color: #7b1fa2;">', // magenta code: (theme === 'dark') ? '<span style="color: #3bc9db;">' : '<span style="color: #0097a7;">', // cyan info: (theme === 'dark') ? '<span style="color: #f8f9fa;">' : '<span style="color: #212529;">', // white/black dbug: (theme === 'dark') ? '<span style="color: #f2c200;">' : '<span style="color: #f57c00;">', // orange // custom JSON colors -- see 'logifyHtml()' for use punc: '<span style="font-weight: 500; color: #00ffff;">', // punctuation - CYAN, BOLD key: '<span style="font-weight: 300; color: #f8f9fa;">', // key name - WHITE string: '<span style="font-weight: 500; color: #00cccc;">', // string value - CYAN, DARKER integer: '<span style="font-weight: 600; color: #8bd6ffff;">', // integer value - BLUE real: '<span style="font-weight: 600; color: #a0ff86;">', // floating point value - GREEN bigint: '<span style="font-weight: 600; color: #cf86ff;">', // bigint value - MAGENTA true: '<span style="font-weight: 600; color: #00ff00;">', // boolean true - LIME false: '<span style="font-weight: 600; color: #ff0000;">', // boolean false - RED null: '<span style="font-weight: 600; color: #7c7c7c;">', // null value - GRAY value: '<span style="font-weight: 600; color: #ffbf00;">', // fallback for other values - ORANGE nl: '<br/>' // newline }, /** * @func resolveSeverity * @memberof mcode * @desc Resolves a severity level to its corresponding text, icon, color, and prefix. * This is used throughout to ensure consistency in the logged output. * @api public * @param {string} severity The severity level to resolve. * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt'. * @returns {object} An object containing the resolved text, icon, color, and prefix. */ resolveSeverity: function (severity, vx = mcode.vt) { const normalized = (severity ?? '').toString().toLowerCase(); const severityMap = [ {keys: ['i', 'inf', 'info'], text: 'info', icon: '📣', colorKey: 'info', prefix: 'i'}, {keys: ['w', 'wrn', 'warn', 'warning'], text: 'warn', icon: '⚠️', colorKey: 'warn', prefix: '!'}, {keys: ['e', 'err', 'error'], text: 'error', icon: '⛔', colorKey: 'errr', prefix: 'x'}, {keys: ['x', 'exp', 'crash', 'fatal', 'exception'], text: 'exception', icon: '💀', colorKey: 'dead', prefix: '*'}, {keys: ['s', 'ack', 'done', 'success'], text: 'success', icon: '✅', colorKey: 'good', prefix: '✓'}, {keys: ['d', 'dbg', 'dbug', 'debug'], text: 'debug', icon: '🎃', colorKey: 'dbug', prefix: 'µ'} ]; const entry = severityMap.find((item) => item.keys.includes(normalized)) || {text: 'undefined', icon: '❓', colorKey: 'punc', prefix: '?'}; const colorValue = `${vx.reset}${vx[entry.colorKey] ?? vx.punc}`; return { text: entry.text, icon: entry.icon, color: colorValue, prefix: entry.prefix }; }, /** * @func extractEntity * @memberof mcode * @api private * @desc Extracts, capitalizes, and returns the ENTITY name of a source module, * by MicroCODE's convention this is APP of app.controller.js, USER of user.view.js, etc. * @param {string} relativePath location of the source code module. * @returns Just the first part--before 1st '.'--of the source file. */ extractEntity: function (relativePath) { // Extract the file name (last part of the path) const parts = relativePath.split(/[\\/]/); // Split on both forward and backslashes const fileName = parts.pop(); // Get last element (file name) // Extract the part before the first dot (.), return ready for message header as [ENTITY] return fileName.split('.')[0].toUpperCase(); }, /** * @func getFrom * @memberof mcode * @api private * @desc Helper function for log() to determine module and line of caller. * @param {string} source the module name from the mcode.log() call as a default (optional). * @returns (2) strings: 'appModule' and 'module:line' of mcode.log() caller. */ getFrom: function (source) { let appModule = 'APP'; let moduleLine = 'UNKNOWN'; if (typeof source !== 'string' || source.trim() === '') return [appModule, moduleLine]; // Get <app-base-dir> (3 levels up from <baseDir>/node_modules/mcode-log) const baseDir = isNode ? path.resolve(__dirname, '../../..') : ''; appModule = source.split(/[\.,:;!?\s]+/)[0].toUpperCase(); // ƒ Convert absolute file path to relative to baseDir for NODE environment const toRelative = (filePath) => { if (!filePath) return source; if (!isNode || filePath.startsWith('node:')) return filePath; return path.relative(baseDir, filePath); }; if (typeof Error.prepareStackTrace !== 'function') { // Fallback for non-V8 engines -- crawl a string stack trace const stack = new Error().stack.split('\n'); for (let i = 2; i < stack.length; i++) { if (!stack[i].includes('index.js')) { const match = stack[i].match(/\(([^)]+):(\d+):\d+\)/); if (match) { // convert to relative path to keep short but informative const filePath = match[1]; const lineNumber = match[2]; let relativePath = (filePath.includes('node:')) ? filePath : toRelative(filePath); moduleLine = `${relativePath}:${lineNumber}`; appModule = this.extractEntity(relativePath); } } } } else { // V8 Engine, use structured stack trace for speed, save the original handler (string generator) const prepareStackTrace = Error.prepareStackTrace; try { // Override to return structured stack trace Error.prepareStackTrace = (_, stack) => stack; const stack = new Error().stack; if (stack && stack.length > 2) { const caller = stack.find((s) => !s.getFileName().includes('index.js')); if (caller) { // convert to relative path to keep short but informative const filePath = caller.getFileName(); const lineNumber = caller.getLineNumber(); let relativePath = (filePath.includes('node:')) ? filePath : toRelative(filePath); moduleLine = `${relativePath}:${lineNumber}`; appModule = this.extractEntity(relativePath); } } } finally { // ensure restoration no matter what Error.prepareStackTrace = prepareStackTrace; } } return [appModule, moduleLine]; }, /** * @func log/logHtml * @memberof mcode * @desc Logs App Events to the Console in a standardized format. * @api public * @param {object} message pre-formatted message to be logged. * @param {string} source where the message orginated. * @param {string} severity Event.Severity: 'info', 'warn', 'error', 'exception', and 'success'. * @param {string} error [Optional] error message from another source. * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI. * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML. * @returns {string} '{severity}: {message}' for display in UI. * * @example * mcode.log('This is a test message.', 'myModule', 'info'); * mcode.log('object', object, 'myModule); */ logHtml: function (message = '<no message>', source = '<unknown>.js', severity = 'debug', error = null, event_id = null) { return mcode.log(message, source, severity, error, event_id, mcode.ht); }, log: function (message = '<no message>', source = '<unknown>.js', severity = 'debug', error = null, event_id = null, vx = mcode.vt) { let logText = []; // build the response as an array for speed let status = `${severity}: ${message}`; let logifiedMessage = ''; // support mcode.log(object) directly -- {0023} if (_data.isObject(message)) return mcode.logobj('', message, source, severity, event_id, vx); // support mcode.log('This object is bad', object) directly -- {0023} if (_data.isObject(source)) { // If event_id looks like a source string (not a UUID), use it as source const evtIsSrc = (typeof event_id === 'string' && !event_id.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)); const src = evtIsSrc ? event_id : '<unknown>.js'; const evt = !evtIsSrc ? event_id : null; return mcode.logobj(message, source, src, severity, evt, vx); } // do not log 'debug' messages in production mode - {0008} if ((severity === 'debug') && (mode === 'production')) return status; // flatten the message object to strings for logging... if (_data.isArray(message)) { logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message, vx), vx); } else if (_data.isJson(message)) { logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message, vx), vx); } else if (_data.isFunction(message)) { logifiedMessage = `${vx.nl}` + `${message}`; } else { logifiedMessage = message; } const [appModule, moduleLine] = this.getFrom(source); const {text: sevText, icon: sevIcon, color: sevColor, prefix: sevPrefix} = mcode.resolveSeverity(severity, vx); logText.push(`${vx.reset}${vx.dim}++${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim} ${sevPrefix} 「mcode」: ${sevColor}${sevIcon} [${appModule}] '${mcode.colorizeLines(logifiedMessage, sevColor, vx)}'`); logText.push(`${vx.reset}${vx.nl}`); let logifiedError = false; if (error) { if (_data.isObject(error)) { logifiedError = mcode.logify(mcode.logifyObject(error, vx), vx); } else if (_data.isJson(error)) { logifiedError = mcode.logify(mcode.logifyObject(error, vx), vx); } else { logifiedError = error; } status += ` ERROR: ${mcode.simplify(logifiedError)}`; } if (logifiedError) { logifiedError = mcode.colorizeLines(logifiedError, sevColor, vx); logText.push(`${vx.reset}${vx.dim} error: ${vx.reset}${sevColor}${logifiedError}${vx.reset}${vx.nl}`); } if (event_id) { const uuidInfo = _data.uuidDecode(event_id); // see mcode.data package logText.push(`${vx.reset}${vx.dim} event: ${vx.reset}${sevColor}${event_id}${vx.reset}`); logText.push(`${vx.reset}${vx.dim} node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`); // aligned to 'time:' } logText.push(`${vx.reset}${vx.dim} time: ${vx.reset}${mcode.timeStamp()}`); logText.push(`${vx.reset}${vx.dim} from: ${vx.reset}${moduleLine}`); logText.push(`${vx.reset}${vx.dim} severity: ${vx.reset}${sevColor}${sevText}${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}--${vx.reset}`); // Output to console or return HTML based on vx parameter const output = logText.join(''); if (vx === mcode.ht) { // Wrap HTML output in div with severity color const codeColor = sevColor.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db'; return `<div style="color: ${codeColor};">${output}</div>`; } else { // Set severity color for VT output console.log(`${sevColor}${output}${vx.reset}`); return status; // for caller to use as needed } }, /** * @func logobj/logobjHtml * @memberof mcode * @desc Logs a labeled Object to the Console in a standardized format. * @api public * @param {string} objName the name of the Object and/or a message to precede it in the log. * @param {object} obj javaScript Object to log. * @param {string} source where the Object orginated. * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI. * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML. * @returns {string} '{objName}: {obj}' for display in UI. * * @example * mcode.logobj('myObject', myObject, 'myModule'); * mcode.obj('myObject', myObject, 'myModule'); */ logobjHtml: function (objName, obj, source = '<undefined>.js', severity = 'info', event_id = null) { return mcode.logobj(objName, obj, source, severity, event_id, mcode.ht); }, logobj: function (objName, obj, source = '<undefined>.js', severity = 'info', event_id = null, vx = mcode.vt) { let logText = []; // build the response as an array for speed let logifiedMessage = ''; const [appModule, moduleLine] = this.getFrom(source); const {text: sevText, icon: sevIcon, color: sevColor, prefix: sevPrefix} = mcode.resolveSeverity(severity, vx); const logObjName = (typeof objName === 'string' && objName.trim() !== '') ? `${sevColor}${objName}:${vx.reset}${vx.nl}` : ''; // 'Logifiy' all objects passed to this function... if (_data.isArray(obj)) { logifiedMessage = `${sevColor}{array}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + mcode.logify(mcode.logifyObject(obj, vx), vx); } else if (_data.isObject(obj)) { logifiedMessage = `${sevColor}{${(typeof obj)}}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + mcode.logify(mcode.logifyObject(obj, vx), vx); } else if (_data.isJson(obj)) { logifiedMessage = `${sevColor}{json}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + mcode.logify(mcode.logifyObject(obj, vx), vx); } else { logifiedMessage = `${sevColor}{${(typeof obj)}}${vx.reset}${vx.nl}${vx.nl}${logObjName}` + obj; } // Apply severity color to the entire message logifiedMessage = mcode.colorizeLines(logifiedMessage, sevColor, vx); logText.push(`${vx.reset}${vx.dim}++${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim} ${sevPrefix} 「mcode」: ${sevColor}${sevIcon} [${appModule}] '${logifiedMessage}'${vx.reset}${vx.nl}`); if (event_id) { const uuidInfo = _data.uuidDecode(event_id); // see mcode.data package logText.push(`${vx.reset}${vx.dim} event: ${vx.reset}${sevColor}${event_id}${vx.reset}`); logText.push(`${vx.reset}${vx.dim} node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`); // aligned to 'time:' } logText.push(`${vx.reset}${vx.dim} time: ${vx.reset}${mcode.timeStamp()}`); logText.push(`${vx.reset}${vx.dim} from: ${vx.reset}${moduleLine}`); logText.push(`${vx.reset}${vx.dim} severity: ${vx.reset}${sevColor}${sevText}${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}--${vx.reset}`); // Output to console or return HTML based on vx parameter const output = logText.join(''); if (vx === mcode.ht) { // Wrap HTML output in div with severity color using !important to override nested spans const codeColor = sevColor.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db'; return `<div style="color: ${codeColor} !important;">${output}</div>`; } else { // Set severity color for VT output console.log(`${sevColor}${output}${vx.reset}`); } }, // convenient abbreviations of all the logged severities... info: function (message, source, event_id) {mcode.log(message, source, 'info', null, event_id);}, warn: function (message, source, event_id) {mcode.log(message, source, 'warn', null, event_id);}, crash: function (message, source, event_id) {mcode.log(message, source, 'exception', null, event_id);}, fatal: function (message, source, event_id) {mcode.log(message, source, 'exception', null, event_id);}, done: function (message, source, event_id) {mcode.log(message, source, 'success', null, event_id);}, debug: function (message, source, event_id) {mcode.log(message, source, 'debug', null, event_id);}, success: function (message, source, event_id) {mcode.log(message, source, 'success', null, event_id);}, // error() takes an optional 'error' parameter for logging underlying error information error: function (message, source, error, event_id) {mcode.log(message, source, 'error', error, event_id);}, /** * @func ready * @memberof mcode * @desc Logs a message to the Console when the module is loaded to show version. */ ready: function () { this.log(`MicroCODE ${MODULE_NAME} v${packageJson.version} is loaded, mode: ${mode}, theme: ${theme}.`, MODULE_NAME, 'success'); }, /** * @func exp/expHtml * @memberof mcode * @desc logs an exception to the Console in a standardized format and a stack dump. * @api public * @param {object} message pre-formatted message to be logged. * @param {string} source where the message orginated. * @param {string} exception the underlying exception object/trace that was caught. * @param {string} exptrace the underlying exception object/trace that was caught... if 'source' is an object to log. * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI. * @returns {string} 'message: {message} - exception: {exception}' for display in UI. */ expHtml: function (message = '<no message>', source = '<unknown>.js', exception = {}, exptrace = {}, event_id = null) { return mcode.exp(message, source, exception, exptrace, event_id, mcode.ht); }, exp: function (message = '<no message>', source = '<unknown>.js', exception = {}, exptrace = {}, event_id = null, vx = mcode.vt) { let logText = []; // build the response as an array for speed let logifiedMessage = ''; let logifiedException = ''; let isExpObject = false; // support mcode.log(object) directly -- {0023} if (_data.isObject(message)) return mcode.expobj('', message, source, exception, event_id, vx); // if an exception object has been passed without source, shift parameters... if (!_data.isString(source)) { exception = source; source = '<unknown>.js'; } if (_data.isJson(message)) { logifiedMessage = `${vx.nl}` + mcode.logify(mcode.logifyObject(message)); } else { logifiedMessage = message; } // flatten the exception object to strings for logging... if (_data.isObject(exception)) { isExpObject = true; if (exception.stack) { // colorized the passed the stack trace... logifiedException = mcode.colorizeLines(exception.stack, vx.gray); } else { // treat as an Object, not a stack trace and show in default colors... logifiedException = `${vx.reset}` + mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc); } } else if (_data.isJson(exception)) { // treat as JSON, not a stack trace and show in default colors... logifiedException = `${vx.reset}` + mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc); } else { // treat as a string, not a stack trace or object and show in gray... logifiedException = `${vx.reset}${vx.gray}` + exception; logifiedException = mcode.colorizeLines(logifiedException, vx.gray); } const [appModule, moduleLine] = this.getFrom(source); let sevColor = vx.reset; sevColor += vx.dead; // created a simplified exception message for the log entry... const loggedException = ' exception: ' + mcode.colorizeLines(mcode.simplify(logifiedException), vx.dead); // if 'loggedException' contains a stack trace, log it as an 'exception w/stack' if (loggedException.includes('Error:') && loggedException.includes('at ')) { isExpObject = true; } if (isExpObject) { logText.push(`${vx.reset}${vx.dim}++${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}${sevColor} exception:${vx.reset}${vx.nl}`); logText.push(logifiedException + `${vx.reset}${vx.nl}`); if (event_id) { const uuidInfo = _data.uuidDecode(event_id); // see mcode.data package logText.push(`${vx.reset}${vx.dim} event: ${vx.reset}${sevColor}${event_id}${vx.reset}`); logText.push(`${vx.reset}${vx.dim} node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`); // aligned to 'time:' } logText.push(`${vx.reset}${vx.dim} time: ${vx.reset}${mcode.timeStamp()}`); logText.push(`${vx.reset}${vx.dim} from: ${vx.reset}${moduleLine}`); logText.push(`${vx.reset}${vx.dim} severity: ${sevColor}exception w/stack${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}--${vx.reset}`); const output = logText.join(''); if (vx === mcode.ht) { const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db'; return `<div style="color: ${codeColor};">${output}</div>`; } else { console.log(output); return `${message} ${exception}`; // for caller to return } } else { logText.push(`${vx.reset}${vx.dim}++${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}${sevColor}${loggedException}${vx.gray}${vx.reset}${vx.nl}`); logText.push(mcode.colorizeLines(`call stack: ${new Error().stack}${vx.reset}${vx.nl}`, vx.gray)); if (event_id) { const uuidInfo = _data.uuidDecode(event_id); // see mcode.data package logText.push(`${vx.reset}${vx.dim} event: ${vx.reset}${sevColor}${event_id}${vx.reset}`); logText.push(`${vx.reset}${vx.dim} node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`); // aligned to 'time:' } logText.push(`${vx.reset}${vx.dim} time: ${vx.reset}${mcode.timeStamp()}`); logText.push(`${vx.reset}${vx.dim} from: ${vx.reset}${moduleLine}`); logText.push(`${vx.reset}${vx.dim} severity: ${sevColor}exception w/trace${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}--${vx.reset}`); const output = logText.join(''); if (vx === mcode.ht) { const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db'; return `<div style="color: ${codeColor};">${output}</div>`; } else { console.log(output); return `${message} ${exception}`; // for caller to return } } }, /** * @func expobj/expobjHtml * @memberof mcode * @desc Logs a labeled Object to the Console in a standardized format, with an associated exception and stack dump. * @api public * @param {string} objName the name of the Object and/or a message to precede it in the log. * @param {object} obj javaScript Object to log. * @param {string} source where the Object orginated. * @param {string} exception the underlying exception message that was caught. * @param {string} event_id [Optional] a UUID to uniquely tie this logged event back into an error display in the UI. * @param {object} vx [Optional] the color video effects to use, defaults to 'mcode.vt' for Console, 'mcode.ht' for HTML. * @returns {string} 'message: {message} - exception: {exception}' for display in UI. * * @example * mcode.expobj('myObject', myObject, 'myModule', err); // from within a 'catch (err)' block */ expobjHtml: function (objName = '<no name>', obj = {}, source = '<unknown>.js', exception = {}, event_id = null) { return mcode.expobj(objName, obj, source, exception, event_id, mcode.ht); }, expobj: function (objName = '<no name>', obj = {}, source = '<unknown>.js', exception = {}, event_id = null, vx = mcode.vt) { let logText = []; // build the response as an array for speed let logifiedMessage = ''; let logifiedException = ''; const logObjName = (typeof objName === 'string' && objName.trim() !== '') ? `${objName}:${vx.nl}` : ''; let isExpObject = false; // flatten the message object to strings for logging... if (_data.isArray(obj)) { logifiedMessage = `{array}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + mcode.logify(mcode.logifyObject(obj), vx); } else if (_data.isObject(obj)) { logifiedMessage = `{${(typeof obj)}}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + mcode.logify(mcode.logifyObject(obj), vx); } else if (_data.isJson(obj)) { logifiedMessage = `{json}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + mcode.logify(mcode.logifyObject(obj), vx); } else { logifiedMessage = `{${(typeof obj)}}${vx.nl}${vx.nl}${vx.punc}${logObjName}` + obj; } // flatten the exception object to strings for logging... if (_data.isObject(exception)) { isExpObject = true; if (exception.stack) { // colorized the passed the stack trace... logifiedException = mcode.colorizeLines(exception.stack, vx.gray); } else { logifiedException = `${vx.reset}` + mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc); } } else if (_data.isJson(exception)) { logifiedException = mcode.colorizeLines(mcode.logify(mcode.logifyObject(exception), vx), vx.punc); } else { logifiedException = mcode.colorizeLines(exception, vx.gray); } const [appModule, moduleLine] = this.getFrom(source); let sevColor = vx.reset; sevColor += vx.dead; // created a simplified exception message for the log entry... const loggedException = 'exception: ' + mcode.simplify(logifiedException); // if 'loggedException' contains a stack trace, log it as an 'exception w/stack' if (loggedException.includes('Error:') && loggedException.includes('at ')) { isExpObject = true; source = `${sevColor}exception${vx.reset}`; } if (isExpObject) { logText.push(`${vx.reset}${vx.dim}++${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}${sevColor}exception:${vx.reset}${vx.nl}`); logText.push(`${vx.reset}` + logifiedException + `${vx.reset}${vx.nl}`); if (event_id) { const uuidInfo = _data.uuidDecode(event_id); // see mcode.data package logText.push(`${vx.reset}${vx.dim} event: ${vx.reset}${sevColor}${event_id}${vx.reset}`); logText.push(`${vx.reset}${vx.dim} node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`); // aligned to 'time:' } logText.push(`${vx.reset}${vx.dim} time: ${vx.reset}${mcode.timeStamp()}`); logText.push(`${vx.reset}${vx.dim} from: ${vx.reset}${moduleLine}`); logText.push(`${vx.reset}${vx.dim} severity: ${sevColor}exception w/stack${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}--${vx.reset}`); const output = logText.join(''); if (vx === mcode.ht) { const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db'; return `<div style="color: ${codeColor};">${output}</div>`; } else { console.log(output); return `Object: ${objName} ${exception}`; // for caller to return } } else { logText.push(`${vx.reset}${vx.dim}++${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim} * 「mcode」: ${sevColor}💀 [${appModule}] '${logifiedMessage}'${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}${sevColor}${loggedException}${vx.gray}${vx.reset}${vx.nl}`); logText.push(mcode.colorizeLines(`call stack: ${new Error().stack}${vx.reset}${vx.nl}`, vx.gray)); if (event_id) { const uuidInfo = _data.uuidDecode(event_id); // see mcode.data package logText.push(`${vx.reset}${vx.dim} event: ${vx.reset}${sevColor}${event_id}${vx.reset}`); logText.push(`${vx.reset}${vx.dim} node: ${vx.reset}${sevColor}${uuidInfo?.macid}${vx.reset}${vx.nl}`); // aligned to 'time:' } logText.push(`${vx.reset}${vx.dim} time: ${vx.reset}${mcode.timeStamp()}`); logText.push(`${vx.reset}${vx.dim} from: ${vx.reset}${moduleLine}`); logText.push(`${vx.reset}${vx.dim} severity: ${sevColor}exception w/trace${vx.reset}${vx.nl}`); logText.push(`${vx.reset}${vx.dim}--${vx.reset}`); const output = logText.join(''); if (vx === mcode.ht) { const codeColor = vx.punc.match(/color:\s*([^;"]*)/)?.[1] || '#3bc9db'; return `<div style="color: ${codeColor};">${output}</div>`; } else { console.log(output); return `Object: ${objName} ${exception}`; // for caller to return } } }, /** * @typedef {object} resxData * @property {number} status - HTTP Status Code * @property {string} message - Message to log to console / file * @property {string} data - Response data to be send to Frontend/Client/Browser - optionally a HTML component * @property {string} entity - [optional] The DB Entity name associated with this Response/Event * @property {string} endpoint - [optional] The API Endpoint name associated with this Response/Event * @property {string} error - [optional] Any error message associated with this response/event * @property {UUID} _id - [optional] The DB Record UUID associated with this Response/Event * @property {UUID} event_id - [optional] A unique Event UUID for traceability from UI to LOG. */ /** * @func resx * @memberof mcode * @desc 'res' extension - logs an http respo