mcode-log
Version:
A stand-alone package of core code for logging and debugging.
1,048 lines (947 loc) • 96.5 kB
JavaScript
// #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