UNPKG

modbus-connect

Version:

Modbus RTU over Web Serial and Node.js SerialPort

278 lines (242 loc) 8.19 kB
// logger.js const LEVELS = ['debug', 'info', 'warn', 'error']; let currentLevel = 'info'; let enabled = true; let useColors = true; let buffering = true; const COLORS = { debug: '\x1b[36m', // cyan info: '\x1b[32m', // green warn: '\x1b[33m', // yellow error: '\x1b[31m', // red reset: '\x1b[0m' }; let groupLevel = 0; let globalContext = {}; let buffer = []; let flushTimeout = null; const FLUSH_INTERVAL = 300; const categoryLevels = {}; function getIndent() { return ' '.repeat(groupLevel); } function getTimestamp() { const d = new Date(); return d.toISOString().replace('T', ' ').replace('Z', ''); } /** * Formats a log message according to the specified level and context. * @param {string} level - Log level (debug, info, warn, error) * @param {Array<any>} args - Arguments to be logged * @param {Object} [context={}] - Context object with additional information * @returns {Array<string>} Formatted log message */ function format(level, args, context = {}) { const timestamp = getTimestamp(); const color = useColors ? COLORS[level] : ''; const reset = useColors ? COLORS.reset : ''; const indent = getIndent(); const transport = globalContext.transport?.toUpperCase?.() || 'UNKNOWN'; let responseTimeStr = ''; if (context.responseTime != null) { responseTimeStr = ` [responseTime: ${context.responseTime} ms]`; } return [`${color}[${timestamp}] [modbus] [${transport}] [${level.toUpperCase()}]${responseTimeStr}`, indent, ...args, reset]; } /** * Determines whether a log message should be logged based on the specified level and context. * @param {string} level - Log level (debug, info, warn, error) * @param {Object} [context={}] - Context object with additional information * @returns {boolean} Whether the log message should be logged */ function shouldLog(level, context = {}) { if (!enabled) return false; if (context.logger && categoryLevels[context.logger]) { return LEVELS.indexOf(level) >= LEVELS.indexOf(categoryLevels[context.logger]); } return LEVELS.indexOf(level) >= LEVELS.indexOf(currentLevel); } /** * Flushes the log buffer and outputs all log messages to the console. */ function flushBuffer() { for (const item of buffer) { const formatted = format(item.level, item.args, item.context); if (useColors) { const [head, indent, ...rest] = formatted; console[item.level](head + indent, ...rest); } else { console[item.level](...formatted); } } buffer = []; flushTimeout = null; } /** * Outputs a log message to the console. * @param {string} level - Log level (debug, info, warn, error) * @param {Array<any>} args - Arguments to be logged * @param {Object} [context={}] - Context object with additional information * @param {boolean} [immediate=false] - Whether to output the log message immediately */ const LOG_RATE_LIMIT = 100; // ms let lastLogTime = 0; async function output(level, args, context, immediate = false) { if (!shouldLog(level, context)) return; const now = Date.now(); if (!immediate && now - lastLogTime < LOG_RATE_LIMIT) return; lastLogTime = now; if (immediate || !buffering) { const formatted = format(level, args, context); if (useColors) { const [head, indent, ...rest] = formatted; console[level](head + indent, ...rest); } else { console[level](...formatted); } return; } buffer.push({ level, args, context }); if (!flushTimeout) { flushTimeout = setTimeout(flushBuffer, FLUSH_INTERVAL); } } /** * Splits the arguments into the main arguments and the context object. * @param {Array<any>} args - Arguments to be logged * @returns {{ args: Array<any>, context: Object }} Object containing the main arguments and the context object */ function splitArgsAndContext(args) { if (args.length > 1 && typeof args[args.length - 1] === 'object' && !Array.isArray(args[args.length - 1])) { const context = args.pop(); return { args, context }; } return { args, context: {} }; } /** * Logger is a logging utility that supports different log levels and categories. * * To log a message, use one of the following methods: * - `logger.debug()`: Logs a debug message * - `logger.info()`: Logs an informational message * - `logger.warn()`: Logs a warning message * - `logger.error()`: Logs an error message * * To create a new logger category, use the `logger.createLogger()` method. * * To set the global log level, use the `logger.setLevel()` method. * * To set the log level for a specific category, use the `logger.setLevelFor()` method. * * To enable or disable logging, use the `logger.enable()` and `logger.disable()` methods. * * To get the current log level, use the `logger.getLevel()` method. * * To check if logging is enabled, use the `logger.isEnabled()` method. * * To disable colored output, use the `logger.disableColors()` method. * * To set the global context, use the `logger.setGlobalContext()` method. * * To add to the global context, use the `logger.addGlobalContext()` method. * * To set the transport type, use the `logger.setTransportType()` method. * * To set buffering, use the `logger.setBuffering()` method. * * To flush the log buffer, use the `logger.flush()` method. */ const logger = { debug: async (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); await output('debug', newArgs, context); }, info: async (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); await output('info', newArgs, context); }, warn: async (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); await output('warn', newArgs, context); }, error: async (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); await output('error', newArgs, context, true); }, group() { groupLevel++; }, groupCollapsed() { groupLevel++; }, groupEnd() { if (groupLevel > 0) groupLevel--; }, setLevel(level) { if (LEVELS.includes(level)) { currentLevel = level; } else { throw new Error(`Unknown log level: ${level}`); } }, setLevelFor(category, level) { if (!LEVELS.includes(level)) throw new Error(`Unknown log level: ${level}`); categoryLevels[category] = level; }, enable() { enabled = true; }, disable() { enabled = false; }, getLevel() { return currentLevel; }, isEnabled() { return enabled; }, disableColors() { useColors = false; }, setGlobalContext(ctx) { globalContext = { ...ctx }; }, addGlobalContext(ctx) { globalContext = { ...globalContext, ...ctx }; }, setTransportType(type) { globalContext.transport = type; }, setBuffering(value) { buffering = !!value; }, flush() { flushBuffer(); }, createLogger(name) { if (!name) throw new Error('Logger name required'); return { debug: (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); output('debug', newArgs, { ...context, logger: name }); }, info: (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); output('info', newArgs, { ...context, logger: name }); }, warn: (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); output('warn', newArgs, { ...context, logger: name }); }, error: (...args) => { const { args: newArgs, context } = splitArgsAndContext([...args]); output('error', newArgs, { ...context, logger: name }, true); }, group: () => logger.group(), groupCollapsed: () => logger.groupCollapsed(), groupEnd: () => logger.groupEnd(), setLevel: (lvl) => logger.setLevelFor(name, lvl), }; } }; module.exports = logger