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