llm-checker
Version:
Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system
272 lines (228 loc) • 7.55 kB
JavaScript
const fs = require('fs');
const path = require('path');
const os = require('os');
class Logger {
constructor(options = {}) {
this.level = options.level || process.env.LLM_CHECKER_LOG_LEVEL || 'info';
this.enableColors = options.colors !== false && !process.env.NO_COLOR;
this.logFile = options.logFile || null;
this.enableConsole = options.console !== false;
this.enableDebug = options.debug || process.env.DEBUG === '1';
this.levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
trace: 4
};
this.colors = {
error: '\x1b[31m', // Red
warn: '\x1b[33m', // Yellow
info: '\x1b[36m', // Cyan
debug: '\x1b[35m', // Magenta
trace: '\x1b[37m', // White
reset: '\x1b[0m'
};
this.setupLogFile();
}
setupLogFile() {
if (this.logFile) {
const logDir = path.dirname(this.logFile);
if (!fs.existsSync(logDir)) {
try {
fs.mkdirSync(logDir, { recursive: true });
} catch (error) {
console.warn(`Warning: Could not create log directory: ${error.message}`);
this.logFile = null;
}
}
}
}
shouldLog(level) {
return this.levels[level] <= this.levels[this.level];
}
formatMessage(level, message, meta = {}) {
const timestamp = new Date().toISOString();
const levelUpper = level.toUpperCase();
let formatted = `${timestamp} [${levelUpper}]`;
if (meta.component) {
formatted += ` [${meta.component}]`;
}
formatted += ` ${message}`;
if (meta.data && typeof meta.data === 'object') {
formatted += '\n' + JSON.stringify(meta.data, null, 2);
}
if (meta.error && meta.error instanceof Error) {
formatted += '\n' + meta.error.stack;
}
return formatted;
}
colorize(level, message) {
if (!this.enableColors) return message;
return this.colors[level] + message + this.colors.reset;
}
writeToFile(message) {
if (!this.logFile) return;
try {
fs.appendFileSync(this.logFile, message + '\n', 'utf8');
} catch (error) {
// Fallback to console if file write fails
if (this.enableConsole) {
console.error('Failed to write to log file:', error.message);
}
}
}
log(level, message, meta = {}) {
if (!this.shouldLog(level)) return;
const formatted = this.formatMessage(level, message, meta);
// Write to console
if (this.enableConsole) {
const colored = this.colorize(level, formatted);
if (level === 'error') {
console.error(colored);
} else if (level === 'warn') {
console.warn(colored);
} else {
console.log(colored);
}
}
// Write to file
this.writeToFile(formatted);
}
error(message, meta = {}) {
this.log('error', message, meta);
}
warn(message, meta = {}) {
this.log('warn', message, meta);
}
info(message, meta = {}) {
this.log('info', message, meta);
}
debug(message, meta = {}) {
this.log('debug', message, meta);
}
trace(message, meta = {}) {
this.log('trace', message, meta);
}
// Specialized logging methods
logHardwareDetection(hardware) {
this.debug('Hardware detection completed', {
component: 'HardwareDetector',
data: {
cpu: hardware.cpu.brand,
ram: `${hardware.memory.total}GB`,
gpu: hardware.gpu.model,
architecture: hardware.cpu.architecture
}
});
}
logModelAnalysis(model, score) {
this.debug('Model compatibility analyzed', {
component: 'CompatibilityAnalyzer',
data: {
model: model.name,
score: score,
category: model.category
}
});
}
logOllamaOperation(operation, model, result) {
this.info(`Ollama ${operation} ${result ? 'succeeded' : 'failed'}`, {
component: 'OllamaClient',
data: {
operation,
model,
success: result
}
});
}
logPerformanceTest(model, results) {
this.info('Performance test completed', {
component: 'PerformanceAnalyzer',
data: {
model,
tokensPerSecond: results.tokensPerSecond,
responseTime: results.responseTime
}
});
}
logError(error, context = {}) {
this.error(error.message, {
component: context.component || 'Unknown',
error: error,
data: context.data
});
}
// Utility methods
time(label) {
const start = process.hrtime.bigint();
return {
end: () => {
const end = process.hrtime.bigint();
const duration = Number(end - start) / 1000000; // Convert to milliseconds
this.debug(`Timer: ${label} took ${duration.toFixed(2)}ms`);
return duration;
}
};
}
createChild(component) {
return {
error: (message, meta = {}) => this.error(message, { ...meta, component }),
warn: (message, meta = {}) => this.warn(message, { ...meta, component }),
info: (message, meta = {}) => this.info(message, { ...meta, component }),
debug: (message, meta = {}) => this.debug(message, { ...meta, component }),
trace: (message, meta = {}) => this.trace(message, { ...meta, component })
};
}
getLogFile() {
return this.logFile;
}
setLogFile(filePath) {
this.logFile = filePath;
this.setupLogFile();
}
setLevel(level) {
if (this.levels.hasOwnProperty(level)) {
this.level = level;
} else {
this.warn(`Invalid log level: ${level}. Valid levels: ${Object.keys(this.levels).join(', ')}`);
}
}
clearLogFile() {
if (this.logFile && fs.existsSync(this.logFile)) {
try {
fs.writeFileSync(this.logFile, '', 'utf8');
this.info('Log file cleared');
} catch (error) {
this.error('Failed to clear log file', { error });
}
}
}
rotateLogFile() {
if (!this.logFile || !fs.existsSync(this.logFile)) return;
try {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const rotatedFile = this.logFile + '.' + timestamp;
fs.renameSync(this.logFile, rotatedFile);
this.info(`Log file rotated to: ${rotatedFile}`);
} catch (error) {
this.error('Failed to rotate log file', { error });
}
}
}
// Global logger instance
let globalLogger = null;
function getLogger(options = {}) {
if (!globalLogger) {
globalLogger = new Logger(options);
}
return globalLogger;
}
function setLogger(logger) {
globalLogger = logger;
}
module.exports = {
Logger,
getLogger,
setLogger
};