UNPKG

@typecad/kicad-symbols

Version:

Intelligent fuzzy search for KiCad symbols with CLI interface

256 lines 9.22 kB
import chalk from 'chalk'; import { promises as fs } from 'fs'; import { join } from 'path'; import { createHash } from 'crypto'; /** * Log levels for the Logger */ export var LogLevel; (function (LogLevel) { LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG"; LogLevel[LogLevel["INFO"] = 1] = "INFO"; LogLevel[LogLevel["WARN"] = 2] = "WARN"; LogLevel[LogLevel["ERROR"] = 3] = "ERROR"; LogLevel[LogLevel["NONE"] = 4] = "NONE"; })(LogLevel || (LogLevel = {})); /** * Logger class for debugging and monitoring */ export class Logger { static instance; options; logFilePath; logFileSize = 0; /** * Creates a new Logger instance * @param options - Configuration options for the Logger */ constructor(options = {}) { // Default options this.options = { consoleLevel: LogLevel.INFO, fileLevel: LogLevel.DEBUG, includeTimestamps: true, logDir: '.logs', maxLogSize: 5 * 1024 * 1024, // 5 MB maxLogFiles: 5, ...options }; // Create log file path const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const hash = createHash('md5').update(timestamp).digest('hex').substring(0, 6); this.logFilePath = join(this.options.logDir, `kicad-search-${timestamp}-${hash}.log`); // Ensure log directory exists this.ensureLogDir(); } /** * Gets the Logger instance (singleton) * @param options - Configuration options for the Logger * @returns The Logger instance */ static getInstance(options = {}) { if (!Logger.instance) { Logger.instance = new Logger(options); } return Logger.instance; } /** * Ensures the log directory exists */ async ensureLogDir() { try { await fs.mkdir(this.options.logDir, { recursive: true }); } catch (error) { // If we can't create the log directory, just log to console console.error(`Failed to create log directory: ${error instanceof Error ? error.message : String(error)}`); } } /** * Logs a debug message * @param message - Message to log * @param data - Additional data to log */ async debug(message, data) { await this.log(LogLevel.DEBUG, message, data); } /** * Logs an info message * @param message - Message to log * @param data - Additional data to log */ async info(message, data) { await this.log(LogLevel.INFO, message, data); } /** * Logs a warning message * @param message - Message to log * @param data - Additional data to log */ async warn(message, data) { await this.log(LogLevel.WARN, message, data); } /** * Logs an error message * @param message - Message to log * @param error - Error to log */ async error(message, error) { let errorData; if (error instanceof Error) { errorData = { name: error.name, message: error.message, stack: error.stack }; } else if (error !== undefined) { errorData = error; } await this.log(LogLevel.ERROR, message, errorData); } /** * Logs a message with the specified level * @param level - Log level * @param message - Message to log * @param data - Additional data to log */ async log(level, message, data) { // Skip if log level is too low if (level < this.options.consoleLevel && level < this.options.fileLevel) { return; } // Format the log message const timestamp = this.options.includeTimestamps ? new Date().toISOString() : ''; const levelString = LogLevel[level]; // Format the log message for console let consoleMessage = ''; if (this.options.includeTimestamps) { consoleMessage += `[${timestamp}] `; } // Add level with color switch (level) { case LogLevel.DEBUG: consoleMessage += chalk.gray(`[${levelString}] `); break; case LogLevel.INFO: consoleMessage += chalk.blue(`[${levelString}] `); break; case LogLevel.WARN: consoleMessage += chalk.yellow(`[${levelString}] `); break; case LogLevel.ERROR: consoleMessage += chalk.red(`[${levelString}] `); break; } // Add message consoleMessage += message; // Add data if provided if (data !== undefined) { if (typeof data === 'object') { consoleMessage += '\n' + JSON.stringify(data, null, 2); } else { consoleMessage += ' ' + String(data); } } // Log to console if level is high enough if (level >= this.options.consoleLevel) { switch (level) { case LogLevel.DEBUG: console.debug(consoleMessage); break; case LogLevel.INFO: console.info(consoleMessage); break; case LogLevel.WARN: console.warn(consoleMessage); break; case LogLevel.ERROR: console.error(consoleMessage); break; } } // Log to file if level is high enough if (level >= this.options.fileLevel) { try { // Format the log message for file (without colors) let fileMessage = ''; if (this.options.includeTimestamps) { fileMessage += `[${timestamp}] `; } fileMessage += `[${levelString}] ${message}`; // Add data if provided if (data !== undefined) { if (typeof data === 'object') { fileMessage += '\n' + JSON.stringify(data, null, 2); } else { fileMessage += ' ' + String(data); } } // Add newline fileMessage += '\n'; // Write to log file await this.writeToLogFile(fileMessage); } catch (error) { // If we can't write to the log file, just log to console console.error(`Failed to write to log file: ${error instanceof Error ? error.message : String(error)}`); } } } /** * Writes a message to the log file * @param message - Message to write */ async writeToLogFile(message) { try { // Check if log file is too large if (this.logFileSize >= this.options.maxLogSize) { await this.rotateLogFiles(); } // Write to log file await fs.appendFile(this.logFilePath, message); // Update log file size this.logFileSize += Buffer.byteLength(message); } catch (error) { // If we can't write to the log file, just log to console console.error(`Failed to write to log file: ${error instanceof Error ? error.message : String(error)}`); } } /** * Rotates log files when the current log file gets too large */ async rotateLogFiles() { try { // Create a new log file const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const hash = createHash('md5').update(timestamp).digest('hex').substring(0, 6); this.logFilePath = join(this.options.logDir, `kicad-search-${timestamp}-${hash}.log`); this.logFileSize = 0; // Delete old log files if there are too many const logFiles = await fs.readdir(this.options.logDir); const logFilePaths = logFiles .filter(file => file.startsWith('kicad-search-') && file.endsWith('.log')) .map(file => join(this.options.logDir, file)); // Sort by modification time (oldest first) const fileStats = await Promise.all(logFilePaths.map(async (filePath) => { const stats = await fs.stat(filePath); return { filePath, mtime: stats.mtime }; })); fileStats.sort((a, b) => a.mtime.getTime() - b.mtime.getTime()); // Delete oldest files if there are too many const filesToDelete = fileStats.slice(0, Math.max(0, fileStats.length - this.options.maxLogFiles + 1)); for (const file of filesToDelete) { await fs.unlink(file.filePath); } } catch (error) { // If we can't rotate log files, just log to console console.error(`Failed to rotate log files: ${error instanceof Error ? error.message : String(error)}`); } } } //# sourceMappingURL=Logger.js.map