modbus-connect
Version:
Modbus RTU over Web Serial and Node.js SerialPort
210 lines (176 loc) • 5.42 kB
JavaScript
// 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', '');
}
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];
}
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);
}
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;
}
async function output(level, args, context, immediate = false) {
if (!shouldLog(level, context)) return;
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);
}
}
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: {} };
}
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