import winston from 'winston';
import path from 'path';
import fs from 'fs';
// Ensure logs directory exists
const logsDir = './logs';
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Custom format for production logging
const productionFormat = winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss.SSS'
}),
winston.format.errors({ stack: true }),
winston.format.metadata({ fillExcept: ['message', 'level', 'timestamp'] }),
winston.format.json()
);
// Development format for console readability
const developmentFormat = winston.format.combine(
winston.format.timestamp({
format: 'HH:mm:ss.SSS'
}),
winston.format.errors({ stack: true }),
winston.format.colorize(),
winston.format.printf(({ timestamp, level, message, metadata, ...meta }) => {
let metaStr = '';
if (metadata && Object.keys(metadata).length > 0) {
metaStr = ` ${JSON.stringify(metadata)}`;
}
if (Object.keys(meta).length > 0) {
metaStr += ` ${JSON.stringify(meta)}`;
}
return `${timestamp} [${level}]: ${message}${metaStr}`;
})
);
// Create logger instance
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
defaultMeta: {
service: 'mcp-vibe-coding-kg',
version: process.env.npm_package_version || '1.0.0',
environment: process.env.NODE_ENV || 'development'
},
transports: [
// Error log file (errors only)
new winston.transports.File({
filename: path.join(logsDir, 'error.log'),
level: 'error',
format: productionFormat,
maxsize: 10 * 1024 * 1024, // 10MB
maxFiles: 5,
tailable: true
}),
// Combined log file (all levels)
new winston.transports.File({
filename: path.join(logsDir, 'combined.log'),
format: productionFormat,
maxsize: 50 * 1024 * 1024, // 50MB
maxFiles: 10,
tailable: true
}),
// Query log file (debug level, separate for performance analysis)
new winston.transports.File({
filename: path.join(logsDir, 'queries.log'),
level: 'debug',
format: productionFormat,
maxsize: 25 * 1024 * 1024, // 25MB
maxFiles: 3
})
],
// Handle uncaught exceptions
exceptionHandlers: [
new winston.transports.File({
filename: path.join(logsDir, 'exceptions.log'),
format: productionFormat
})
],
// Handle unhandled promise rejections
rejectionHandlers: [
new winston.transports.File({
filename: path.join(logsDir, 'rejections.log'),
format: productionFormat
})
]
});
// Add console transport for development
if (process.env.NODE_ENV !== 'production') {
logger.add(
new winston.transports.Console({
format: developmentFormat,
level: process.env.LOG_LEVEL || 'debug'
})
);
}
// Add request logging method
logger.logRequest = (req, res, duration) => {
logger.info('HTTP Request', {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
userAgent: req.get('User-Agent'),
ip: req.ip || req.connection.remoteAddress
});
};
// Add query logging method
logger.logQuery = (query, params, duration, success = true) => {
logger.debug('Database Query', {
query: query.substring(0, 200) + (query.length > 200 ? '...' : ''),
params,
duration: `${duration}ms`,
success,
timestamp: new Date().toISOString()
});
};
// Add performance logging method
logger.logPerformance = (operation, duration, metadata = {}) => {
const level = duration > 5000 ? 'warn' : duration > 1000 ? 'info' : 'debug';
logger.log(level, `Performance: ${operation}`, {
duration: `${duration}ms`,
...metadata
});
};
// Add security logging method
logger.logSecurity = (event, details = {}) => {
logger.warn('Security Event', {
event,
timestamp: new Date().toISOString(),
...details
});
};
// Utility function to create child logger with additional context
logger.child = (metadata) => {
return logger.child(metadata);
};
export default logger;