/** * dxLogger module. * A simple, static logger that replaces `console.log`. * It provides multi-level logging and can be viewed in the corresponding VSCode plugin during debugging. * * Features: * - Three log levels: DEBUG, INFO, ERROR. All levels are always enabled. * - Supports logging various JavaScript data types, including objects and errors. * - Non-blocking log output to avoid performance impact. * * Usage: * - Import the logger: `import log from './dxLogger.js'` * - Use the logging methods: `log.info('Application started');`, `log.error('An error occurred:', new Error('test'));` * * Doc/Demo: https://github.com/DejaOS/DejaOS */ const logger = {} // The maximum length of the log message. In special cases, too long content can cause application crashes. logger.max_length = 1024 import * as std from "std" import dxMap from './dxMap.js' let loggerMap = dxMap.get('__logger__') /** * Enables or disables the debug mode for the logger. * When debug mode is enabled, output is flushed immediately after each log. * This is essential for seeing logs in real-time when the output is redirected * to a pipe (e.g., during VS Code debugging), but can impact performance and stability * on physical serial ports. * * @param {boolean} [isdebug=true] - Whether to enable or disable debug mode. * @example * // Enable debug mode for real-time logging * logger.setDebug(true); * * // Disable debug mode for production/stability testing * logger.setDebug(false); */ logger.setDebug = function (isdebug = true) { if (isdebug) { loggerMap.put('isdebug', true) } else { loggerMap.put('isdebug', false) } } /** * Logs a message at the DEBUG level. * @param {...*} data - The data to log. Can be multiple arguments of any type. * @example * logger.debug('User logged in:', { userId: 123 }); */ logger.debug = function (...data) { log("DEBUG", data) } /** * Logs a message at the INFO level. * @param {...*} data - The data to log. Can be multiple arguments of any type. * @example * logger.info('Server started on port', 8080); */ logger.info = function (...data) { log("INFO", data) } /** * Logs a message at the ERROR level. * @param {...*} data - The data to log. Can be multiple arguments of any type. * @example * try { * // ... some code that might fail * } catch (e) { * logger.error('Operation failed:', e); * } */ logger.error = function (...data) { log("ERROR", data) } //-----------------------------------private---------------------- // Formats and prints the log message to standard output. function log(level, messages) { let message = messages.map(msg => getContent(msg)).join(' '); //multi \n will cause vscode to not see the subsequent logs if (message.includes('\n\n')) { message = message.replace(/\n{2,}/g, '\n'); } const content = `[${level} ${getTime()}]: ${message}`.trimEnd(); try { if (content.length > logger.max_length) { std.puts(content.slice(0, logger.max_length - 3) + '...\n'); } else { std.puts(content + '\n'); } // The default behavior is to flush, which is suitable for real-time debugging (VSCode). // To disable flushing for stability testing on serial ports (MobaXterm), // explicitly call logger.setDebug(false). if (loggerMap.get('isdebug') !== false) { std.out.flush(); } } catch (e) { // If even the fallback fails, there's nothing more we can do. } } // Converts any JavaScript value to a string for logging. function getContent(message) { if (message === undefined) { return 'undefined' } else if (message === null) { return 'null' } if (typeof message === 'object') { if (Object.prototype.toString.call(message) === '[object Error]') { let errorString = message.message || 'Error'; if (message.stack) { errorString += '\n' + message.stack; } return errorString; } return JSON.stringify(message) } return String(message); } // Generates a timestamp string in 'YYYY-MM-DD HH:mm:ss.ms' format. function getTime() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); const milliseconds = String(now.getMilliseconds()).padStart(3, '0'); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`; } if (globalThis && globalThis.console) { globalThis.console.log = logger.info globalThis.console.debug = logger.debug globalThis.console.error = logger.error } export default logger