@rhofkens/mcp-quotes-server-claude-code
Version:
Model Context Protocol (MCP) server for managing and serving quotes
155 lines • 4.68 kB
JavaScript
/**
* Logger configuration using Winston
*
* Logs to files only - console output would interfere with MCP STDIO protocol
*/
import path from 'path';
import winston from 'winston';
import { getConfig } from './config.js';
// Ensure logs directory exists
const logsDir = path.join(process.cwd(), 'logs');
// Lazy config getter with defaults
let cachedConfig = null;
function getSafeConfig() {
try {
if (!cachedConfig) {
cachedConfig = getConfig();
}
return cachedConfig;
}
catch {
// Return defaults if config loading fails
return {
logLevel: 'info',
nodeEnv: 'development',
};
}
}
/**
* Create Winston logger instance configured for MCP
* IMPORTANT: No console output - would interfere with MCP STDIO protocol
*/
let loggerInstance = null;
function getLogger() {
if (!loggerInstance) {
const config = getSafeConfig();
loggerInstance = winston.createLogger({
level: config.logLevel || 'info',
format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()),
transports: [
// File transport for all logs
new winston.transports.File({
filename: path.join(logsDir, 'error.log'),
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
new winston.transports.File({
filename: path.join(logsDir, 'combined.log'),
maxsize: 5242880, // 5MB
maxFiles: 5,
}),
],
// Prevent unhandled promise rejection from stopping the process
exitOnError: false,
});
// Add file transport for debug logs in development
if (config.nodeEnv === 'development') {
loggerInstance.add(new winston.transports.File({
filename: path.join(logsDir, 'debug.log'),
level: 'debug',
maxsize: 5242880, // 5MB
maxFiles: 3,
}));
}
}
return loggerInstance;
}
/**
* Create a child logger with additional context
*/
export function createLogger(context) {
return getLogger().child(context);
}
/**
* Performance logger for tracking operation durations
*/
export class PerformanceLogger {
timers = new Map();
start(operation) {
this.timers.set(operation, Date.now());
getLogger().debug('Performance timer started', { operation });
}
end(operation, metadata) {
const startTime = this.timers.get(operation);
if (!startTime) {
getLogger().warn('Performance timer not found', { operation });
return;
}
const duration = Date.now() - startTime;
this.timers.delete(operation);
getLogger().info('Operation completed', {
operation,
duration,
...metadata,
});
}
}
/**
* Request logger for MCP/HTTP requests
*/
export class RequestLogger {
logRequest(method, params, id) {
getLogger().info('Request received', {
type: 'request',
method,
id,
params: this.sanitizeParams(params),
});
}
logResponse(method, result, id, duration) {
getLogger().info('Response sent', {
type: 'response',
method,
id,
duration,
hasResult: result !== undefined,
});
}
logError(method, error, id) {
getLogger().error('Request error', {
type: 'error',
method,
id,
error: error.message || error,
stack: error.stack,
});
}
sanitizeParams(params) {
if (!params) {
return params;
}
// Remove sensitive data like API keys
const sanitized = { ...params };
if (sanitized.apiKey) {
sanitized.apiKey = '***';
}
if (sanitized.token) {
sanitized.token = '***';
}
if (sanitized.serperApiKey) {
sanitized.serperApiKey = '***';
}
return sanitized;
}
}
// Export a proxy object that looks like a logger but initializes lazily
export const logger = new Proxy({}, {
get(_target, prop) {
const actualLogger = getLogger();
return Reflect.get(actualLogger, prop);
},
});
export const performanceLogger = new PerformanceLogger();
export const requestLogger = new RequestLogger();
//# sourceMappingURL=logger.js.map