Skip to main content
Glama
logger.ts9.18 kB
import winston from 'winston'; import fs from 'fs'; import path from 'path'; import { getAppPaths } from './app-paths.js'; // Unified logging system - starts with session logging, upgradeable to contextual let currentLogger: winston.Logger | null = null; let currentLogFile: string = 'uninitialized'; let consoleMode: boolean = false; // Create custom format for better readability const customFormat = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS', }), winston.format.errors({ stack: true }), winston.format.json() //winston.format.printf(({ timestamp, level, message, stack, ...meta }) => { // const metaStr = // Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : ''; // const stackStr = stack && typeof stack === 'string' ? `\n${stack}` : ''; // return `${String(timestamp)} [${String(level).toUpperCase().padEnd(5)}] ${String(message)}${metaStr}${stackStr}`; //}) ); /** * Create a logger for the given log file path */ function createLogger( logFilePath: string, enableConsole: boolean = false ): winston.Logger { // Ensure directory exists const logDir = path.dirname(logFilePath); try { fs.mkdirSync(logDir, { recursive: true }); } catch (error) { process.stderr.write( `[INIT] Failed to create logs directory: ${error instanceof Error ? error.message : String(error)}\n` ); throw error; } try { const transports: winston.transport[] = [ new winston.transports.File({ filename: logFilePath, handleExceptions: true, handleRejections: true, }), ]; // Add console transport in console mode if (enableConsole) { transports.push( new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp({ format: 'HH:mm:ss.SSS', }), winston.format.printf(({ timestamp, level, message, ...meta }) => { const metaStr = Object.keys(meta).length > 0 && meta.timestamp !== timestamp ? ` ${JSON.stringify(meta)}` : ''; return `[${String(timestamp)}] ${String(level).toUpperCase().padEnd(5)} ${String(message)}${metaStr}`; }) ), handleExceptions: true, handleRejections: true, }) ); } const logger = winston.createLogger({ level: process.env.SYMBOLS_LOGLEVEL || 'info', format: customFormat, transports, }); return logger; } catch (error) { process.stderr.write( `[INIT] Failed to create winston logger: ${error instanceof Error ? error.message : String(error)}\n` ); throw error; } } /** * Initialize with basic session logging */ function initializeSessionLogger(enableConsole: boolean = false): void { consoleMode = enableConsole; try { // Use env-paths for cross-platform log directory const logDir = getAppPaths().log; // Generate timestamped filename for this session const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); currentLogFile = path.join(logDir, `session-${timestamp}.log`); currentLogger = createLogger(currentLogFile, consoleMode); // Log session start currentLogger.debug('='.repeat(80)); currentLogger.debug(`Session started - Log file: ${currentLogFile}`); currentLogger.debug(`Log level: ${currentLogger.level}`); currentLogger.debug(`Console mode: ${consoleMode ? 'enabled' : 'disabled'}`); currentLogger.debug( `Environment: ${process.env.NODE_ENV || 'development'}` ); currentLogger.debug(`PID: ${process.pid}`); currentLogger.debug('='.repeat(80)); } catch (error) { process.stderr.write( `[INIT] Failed to initialize session logger: ${error instanceof Error ? error.message : String(error)}\n` ); } } /** * Safe wrapper that handles uninitialized logger */ function ensureInitialized(): winston.Logger { if (!currentLogger) { process.stderr.write( `[WARN] Logger used before initialization, auto-initializing...\n` ); initializeSessionLogger(); } return currentLogger!; } /** * Upgrade to contextual logging with workspace + LSP info * Copies existing logs to the new file and switches logging target */ function upgradeToContextualLogger( workspacePath: string, lspName?: string ): void { try { // Generate contextual log filename const logDir = getAppPaths().log; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); // Find workspace name from path const workspaceName = path .basename(workspacePath) .replace(/[<>:"/\\|?*\s]/g, '-') .substring(0, 50); const parts = [workspaceName]; if (lspName) { parts.push(lspName.replace(/[<>:"/\\|?*\s]/g, '-').substring(0, 50)); } parts.push(timestamp); const contextualLogFile = path.join(logDir, `${parts.join('_')}.log`); // Log the transition in the current logger (ensure it's initialized) const logger = ensureInitialized(); logger.info(`Moving to LSP-specific logging: ${contextualLogFile}`); logger.info( `Workspace: ${workspacePath}, LSP: ${lspName || 'auto-detect'}` ); // Ensure destination directory exists before any file operations const logDirExists = fs.existsSync(logDir); if (!logDirExists) { fs.mkdirSync(logDir, { recursive: true }); } // Copy existing session log without loading it into memory if ( currentLogFile !== contextualLogFile && currentLogFile !== 'uninitialized' && fs.existsSync(currentLogFile) ) { try { fs.copyFileSync(currentLogFile, contextualLogFile); } catch (copyError) { logger.warn('Failed to copy existing session log to contextual log', { error: copyError instanceof Error ? copyError.message : String(copyError), }); // Ensure file exists even if copy failed if (!fs.existsSync(contextualLogFile)) { fs.writeFileSync(contextualLogFile, ''); } } } // Create new contextual logger (preserve console mode) const newLogger = createLogger(contextualLogFile, consoleMode); // Add transition marker in new file newLogger.debug('='.repeat(80)); newLogger.debug('TRANSITIONED TO CONTEXTUAL LOGGING'); newLogger.debug(`Workspace: ${workspacePath}`); newLogger.debug(`LSP: ${lspName || 'auto-detect'}`); newLogger.debug(`Console mode: ${consoleMode ? 'enabled' : 'disabled'}`); newLogger.debug('='.repeat(80)); // Save old session file path before switching const oldLogFile = currentLogFile; // Switch to new logger currentLogger = newLogger; currentLogFile = contextualLogFile; // Clean up old session file if it's different if ( fs.existsSync(oldLogFile) && oldLogFile.includes('session-') && oldLogFile !== contextualLogFile ) { try { fs.unlinkSync(oldLogFile); } catch (unlinkError) { logger.debug('Failed to remove old session log', { error: unlinkError instanceof Error ? unlinkError.message : String(unlinkError), }); } } } catch (error) { // If upgrade fails, continue with session logger ensureInitialized().error('Failed to upgrade to contextual logger', { error, }); } } /** * Initialize the logger system - must be called before using logger * @param enableDebug - Enable console output for debugging */ export function initLogger(enableDebug: boolean = false): void { initializeSessionLogger(enableDebug); } // No automatic initialization - must call initLogger() explicitly // Create a logger interface that maintains winston compatibility interface UnifiedLogger { info(message: string): winston.Logger; info(message: string, meta: object): winston.Logger; warn(message: string): winston.Logger; warn(message: string, meta: object): winston.Logger; error(message: string): winston.Logger; error(message: string, meta: object): winston.Logger; debug(message: string): winston.Logger; debug(message: string, meta: object): winston.Logger; level: string; } // Export unified logger interface that proxies to the current logger const logger: UnifiedLogger = { info: (message: string, meta?: object) => ensureInitialized().info(message, meta), warn: (message: string, meta?: object) => ensureInitialized().warn(message, meta), error: (message: string, meta?: object) => { // Always output errors to stderr as well for visibility const metaStr = meta ? ` ${JSON.stringify(meta)}` : ''; process.stderr.write(`[ERROR] ${message}${metaStr}\n`); return ensureInitialized().error(message, meta); }, debug: (message: string, meta?: object) => ensureInitialized().debug(message, meta), get level() { return ensureInitialized().level; }, set level(newLevel: string) { ensureInitialized().level = newLevel; }, }; // Export upgrade function for use in main export { upgradeToContextualLogger }; export default logger;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/p1va/symbols-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server