import winston from 'winston';
import { v4 as uuidv4 } from 'uuid';
export interface LogContext {
requestId?: string;
operation?: string;
uri?: string;
toolName?: string;
[key: string]: unknown;
}
class MCPLogger {
private logger: winston.Logger;
private sessionId: string;
constructor() {
this.sessionId = uuidv4();
// 構造化ロギング設定
this.logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
sessionId: this.sessionId,
serverType: 'mcp-universal'
},
transports: [
// ファイルベースのロギング
new winston.transports.File({
filename: 'logs/error.log',
level: 'error',
maxsize: 5242880, // 5MB
maxFiles: 5
}),
new winston.transports.File({
filename: 'logs/combined.log',
maxsize: 5242880,
maxFiles: 5
}),
// 標準エラー出力(MCPプロトコルに準拠)
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
const metaStr = Object.keys(meta).length ?
`\n${JSON.stringify(meta, null, 2)}` : '';
return `[${timestamp}] ${level}: ${message}${metaStr}`;
})
),
stderrLevels: ['error', 'warn', 'info', 'debug']
})
]
});
}
info(message: string, context?: LogContext) {
this.logger.info(message, { ...context, requestId: context?.requestId || uuidv4() });
}
warn(message: string, context?: LogContext) {
this.logger.warn(message, { ...context, requestId: context?.requestId || uuidv4() });
}
error(message: string, error?: Error, context?: LogContext) {
this.logger.error(message, {
...context,
requestId: context?.requestId || uuidv4(),
error: error ? {
name: error.name,
message: error.message,
stack: error.stack
} : undefined
});
}
debug(message: string, context?: LogContext) {
this.logger.debug(message, { ...context, requestId: context?.requestId || uuidv4() });
}
getSessionId(): string {
return this.sessionId;
}
}
export const logger = new MCPLogger();