@typecad/kicad-symbols
Version:
Intelligent fuzzy search for KiCad symbols with CLI interface
256 lines • 9.22 kB
JavaScript
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