Skip to main content
Glama
logger.ts6.17 kB
import winston from 'winston'; import path from 'path'; import fs from 'fs-extra'; import { config } from '../config/index.js'; /** * Structured logging with context and error tracking * Following WCGW: Always log enough context to debug issues */ class Logger { private logger: winston.Logger; private requestId?: string; private userId?: string; private metadata: Record<string, any> = {}; constructor() { // Ensure log directory exists const logDir = config.getString('LOG_DIR', 'logs'); fs.ensureDirSync(logDir); // Create winston logger with multiple transports this.logger = winston.createLogger({ level: config.getString('LOG_LEVEL', 'info'), format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'mcp-software-engineer', version: '1.0.0', environment: config.getString('NODE_ENV', 'production') }, transports: [ // Error log file new winston.transports.File({ filename: path.join(logDir, 'error.log'), level: 'error', maxsize: 10 * 1024 * 1024, // 10MB maxFiles: 5 }), // Combined log file new winston.transports.File({ filename: path.join(logDir, 'combined.log'), maxsize: 50 * 1024 * 1024, // 50MB maxFiles: 10 }), // Audit log for security events new winston.transports.File({ filename: path.join(logDir, 'audit.log'), level: 'info', maxsize: 10 * 1024 * 1024, maxFiles: 30, format: winston.format.combine( winston.format.timestamp(), winston.format.json(), winston.format.prettyPrint() ) }) ] }); // Console output for development if (config.getString('NODE_ENV') !== 'production') { this.logger.add(new winston.transports.Console({ format: winston.format.combine( winston.format.colorize(), winston.format.simple() ) })); } } /** * Set request context for all subsequent logs */ setContext(context: { requestId?: string; userId?: string; [key: string]: any }): void { if (context.requestId) this.requestId = context.requestId; if (context.userId) this.userId = context.userId; // Store other metadata const { requestId, userId, ...rest } = context; this.metadata = { ...this.metadata, ...rest }; } /** * Clear context (e.g., at end of request) */ clearContext(): void { this.requestId = undefined; this.userId = undefined; this.metadata = {}; } /** * Format log entry with context */ private formatEntry(level: string, message: string, meta?: any): any { const entry: any = { level, message, timestamp: new Date().toISOString(), ...this.metadata }; if (this.requestId) entry.requestId = this.requestId; if (this.userId) entry.userId = this.userId; if (meta) { if (meta instanceof Error) { entry.error = { name: meta.name, message: meta.message, stack: meta.stack }; } else { entry.metadata = meta; } } return entry; } debug(message: string, meta?: any): void { this.logger.debug(this.formatEntry('debug', message, meta)); } info(message: string, meta?: any): void { this.logger.info(this.formatEntry('info', message, meta)); } warn(message: string, meta?: any): void { this.logger.warn(this.formatEntry('warn', message, meta)); } error(message: string, error?: Error | any, meta?: any): void { const entry = this.formatEntry('error', message, meta); if (error) { entry.error = error instanceof Error ? { name: error.name, message: error.message, stack: error.stack } : error; } this.logger.error(entry); } /** * Log security-related events */ security(event: string, details: any): void { this.logger.info(this.formatEntry('security', event, { securityEvent: true, ...details })); } /** * Log performance metrics */ metric(name: string, value: number, unit: string = 'ms', tags?: Record<string, any>): void { this.logger.info(this.formatEntry('metric', `Performance metric: ${name}`, { metric: true, metricName: name, value, unit, tags })); } /** * Create child logger with additional context */ child(context: Record<string, any>): Logger { const child = new Logger(); child.setContext({ ...this.metadata, ...context }); return child; } /** * Measure and log execution time */ async measureTime<T>( operation: string, fn: () => Promise<T>, meta?: any ): Promise<T> { const startTime = Date.now(); try { const result = await fn(); const duration = Date.now() - startTime; this.metric(operation, duration, 'ms', meta); if (duration > 1000) { this.warn(`Slow operation detected: ${operation}`, { duration, ...meta }); } return result; } catch (error) { const duration = Date.now() - startTime; this.error(`Operation failed: ${operation}`, error, { duration, ...meta }); throw error; } } } // Export singleton instance export const logger = new Logger(); // Log uncaught exceptions and unhandled rejections process.on('uncaughtException', (error: Error) => { logger.error('Uncaught Exception', error); process.exit(1); }); process.on('unhandledRejection', (reason: any, promise: Promise<any>) => { logger.error('Unhandled Rejection', reason, { promise }); }); // Log process lifecycle events process.on('SIGTERM', () => { logger.info('SIGTERM received, shutting down gracefully'); }); process.on('SIGINT', () => { logger.info('SIGINT received, shutting down gracefully'); });

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/Rajawatrajat/mcp-software-engineer'

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