Skip to content

Commit 54cc219

Browse files
committed
📊 Add comprehensive logging system for MCP server
- Implemented structured logging with multiple levels (ERROR, WARN, INFO, DEBUG) - Added production JSON format and development human-readable format - Created specialized logging methods for chess analysis and game simulation - Added security event logging and rate limit monitoring - Implemented data sanitization to remove sensitive information - Added performance timing and metrics logging capabilities
1 parent 263ba3b commit 54cc219

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed

mcp-server/src/logger.ts

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/**
2+
* Logger for MCP Chess Server
3+
*
4+
* Provides structured logging with different levels and contexts
5+
* for monitoring and debugging the agentic chess API.
6+
*/
7+
8+
export enum LogLevel {
9+
ERROR = 0,
10+
WARN = 1,
11+
INFO = 2,
12+
DEBUG = 3,
13+
}
14+
15+
interface LogEntry {
16+
timestamp: string;
17+
level: string;
18+
context: string;
19+
message: string;
20+
data?: any;
21+
requestId?: string;
22+
}
23+
24+
export class Logger {
25+
private context: string;
26+
private level: LogLevel;
27+
private requestId?: string;
28+
29+
constructor(context: string = 'ChessMCP', level: LogLevel = LogLevel.INFO) {
30+
this.context = context;
31+
this.level = level;
32+
}
33+
34+
setRequestId(requestId: string): void {
35+
this.requestId = requestId;
36+
}
37+
38+
error(message: string, data?: any): void {
39+
if (this.level >= LogLevel.ERROR) {
40+
this.log(LogLevel.ERROR, message, data);
41+
}
42+
}
43+
44+
warn(message: string, data?: any): void {
45+
if (this.level >= LogLevel.WARN) {
46+
this.log(LogLevel.WARN, message, data);
47+
}
48+
}
49+
50+
info(message: string, data?: any): void {
51+
if (this.level >= LogLevel.INFO) {
52+
this.log(LogLevel.INFO, message, data);
53+
}
54+
}
55+
56+
debug(message: string, data?: any): void {
57+
if (this.level >= LogLevel.DEBUG) {
58+
this.log(LogLevel.DEBUG, message, data);
59+
}
60+
}
61+
62+
private log(level: LogLevel, message: string, data?: any): void {
63+
const entry: LogEntry = {
64+
timestamp: new Date().toISOString(),
65+
level: LogLevel[level],
66+
context: this.context,
67+
message,
68+
requestId: this.requestId,
69+
};
70+
71+
if (data !== undefined) {
72+
entry.data = this.sanitizeData(data);
73+
}
74+
75+
// Format output based on environment
76+
if (process.env.NODE_ENV === 'production') {
77+
// JSON format for production logging
78+
console.log(JSON.stringify(entry));
79+
} else {
80+
// Human-readable format for development
81+
const timestamp = entry.timestamp.substring(11, 19); // HH:MM:SS
82+
const levelStr = `[${entry.level}]`.padEnd(7);
83+
const contextStr = `[${entry.context}]`.padEnd(12);
84+
const requestStr = entry.requestId ? `[${entry.requestId}]` : '';
85+
86+
let output = `${timestamp} ${levelStr} ${contextStr} ${requestStr} ${message}`;
87+
88+
if (data !== undefined) {
89+
output += '\n' + JSON.stringify(entry.data, null, 2);
90+
}
91+
92+
// Color coding for different log levels
93+
switch (level) {
94+
case LogLevel.ERROR:
95+
console.error('\x1b[31m%s\x1b[0m', output); // Red
96+
break;
97+
case LogLevel.WARN:
98+
console.warn('\x1b[33m%s\x1b[0m', output); // Yellow
99+
break;
100+
case LogLevel.INFO:
101+
console.info('\x1b[36m%s\x1b[0m', output); // Cyan
102+
break;
103+
case LogLevel.DEBUG:
104+
console.debug('\x1b[90m%s\x1b[0m', output); // Gray
105+
break;
106+
}
107+
}
108+
}
109+
110+
private sanitizeData(data: any): any {
111+
if (data === null || data === undefined) {
112+
return data;
113+
}
114+
115+
if (typeof data === 'string') {
116+
// Truncate very long strings
117+
return data.length > 1000 ? data.substring(0, 1000) + '...' : data;
118+
}
119+
120+
if (typeof data === 'object') {
121+
try {
122+
const sanitized = { ...data };
123+
124+
// Remove sensitive fields
125+
const sensitiveFields = ['password', 'token', 'apiKey', 'secret', 'key'];
126+
for (const field of sensitiveFields) {
127+
if (field in sanitized) {
128+
sanitized[field] = '[REDACTED]';
129+
}
130+
}
131+
132+
// Truncate large objects
133+
const jsonStr = JSON.stringify(sanitized);
134+
if (jsonStr.length > 2000) {
135+
return { ...sanitized, _truncated: true, _originalSize: jsonStr.length };
136+
}
137+
138+
return sanitized;
139+
} catch (error) {
140+
return '[Circular or non-serializable object]';
141+
}
142+
}
143+
144+
return data;
145+
}
146+
147+
// Create child logger with additional context
148+
child(additionalContext: string): Logger {
149+
const childLogger = new Logger(`${this.context}:${additionalContext}`, this.level);
150+
childLogger.requestId = this.requestId;
151+
return childLogger;
152+
}
153+
154+
// Performance logging helpers
155+
time(label: string): void {
156+
console.time(`${this.context}:${label}`);
157+
}
158+
159+
timeEnd(label: string): void {
160+
console.timeEnd(`${this.context}:${label}`);
161+
}
162+
163+
// Metrics logging
164+
metric(name: string, value: number, unit: string = '', tags?: Record<string, string>): void {
165+
this.info(`Metric: ${name}`, {
166+
metric: name,
167+
value,
168+
unit,
169+
tags,
170+
timestamp: Date.now(),
171+
});
172+
}
173+
174+
// Request/response logging
175+
request(method: string, tool: string, args: any): void {
176+
this.info(`Request: ${method} ${tool}`, {
177+
method,
178+
tool,
179+
args: this.sanitizeData(args),
180+
});
181+
}
182+
183+
response(tool: string, success: boolean, duration: number, error?: string): void {
184+
const level = success ? LogLevel.INFO : LogLevel.ERROR;
185+
const message = `Response: ${tool} ${success ? 'SUCCESS' : 'ERROR'} (${duration}ms)`;
186+
187+
this.log(level, message, {
188+
tool,
189+
success,
190+
duration,
191+
error,
192+
});
193+
}
194+
195+
// Chess-specific logging
196+
analysis(fen: string, depth: number, evaluation: number, nodes: number, timeMs: number): void {
197+
this.info('Chess Analysis', {
198+
fen: fen.substring(0, 50) + (fen.length > 50 ? '...' : ''),
199+
depth,
200+
evaluation,
201+
nodes,
202+
timeMs,
203+
nps: Math.round(nodes / (timeMs / 1000)),
204+
});
205+
}
206+
207+
gameSimulation(moves: number, result: string, termination: string): void {
208+
this.info('Game Simulation Complete', {
209+
moves,
210+
result,
211+
termination,
212+
});
213+
}
214+
215+
// Security logging
216+
securityEvent(event: string, clientId: string, details?: any): void {
217+
this.warn(`Security Event: ${event}`, {
218+
event,
219+
clientId,
220+
details: this.sanitizeData(details),
221+
});
222+
}
223+
224+
rateLimitHit(clientId: string, tool: string, resetTime: Date): void {
225+
this.warn('Rate Limit Hit', {
226+
clientId,
227+
tool,
228+
resetTime: resetTime.toISOString(),
229+
});
230+
}
231+
}

0 commit comments

Comments
 (0)