@@ -3,6 +3,7 @@ import { mongoLogId, MongoLogId, MongoLogManager, MongoLogWriter } from "mongodb
33import redact from "mongodb-redact" ;
44import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
55import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js" ;
6+ import { EventEmitter } from "events" ;
67
78export type LogLevel = LoggingMessageNotification [ "params" ] [ "level" ] ;
89
@@ -55,12 +56,17 @@ interface LogPayload {
5556 context : string ;
5657 message : string ;
5758 noRedaction ?: boolean | LoggerType | LoggerType [ ] ;
59+ attributes ?: Record < string , string > ;
5860}
5961
6062export type LoggerType = "console" | "disk" | "mcp" ;
6163
62- export abstract class LoggerBase {
63- private defaultUnredactedLogger : LoggerType = "mcp" ;
64+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65+ type EventMap < T > = Record < keyof T , any [ ] > | DefaultEventMap ;
66+ type DefaultEventMap = [ never ] ;
67+
68+ export abstract class LoggerBase < T extends EventMap < T > = DefaultEventMap > extends EventEmitter < T > {
69+ private readonly defaultUnredactedLogger : LoggerType = "mcp" ;
6470
6571 public log ( level : LogLevel , payload : LogPayload ) : void {
6672 // If no explicit value is supplied for unredacted loggers, default to "mcp"
@@ -72,7 +78,7 @@ export abstract class LoggerBase {
7278 } ) ;
7379 }
7480
75- protected abstract type : LoggerType ;
81+ protected abstract readonly type ? : LoggerType ;
7682
7783 protected abstract logCore ( level : LogLevel , payload : LogPayload ) : void ;
7884
@@ -92,7 +98,7 @@ export abstract class LoggerBase {
9298 if (
9399 typeof noRedaction === "object" &&
94100 Array . isArray ( noRedaction ) &&
95- this . type !== null &&
101+ this . type &&
96102 noRedaction . indexOf ( this . type ) !== - 1
97103 ) {
98104 // If the consumer has supplied noRedaction: array, we skip redacting if our logger
@@ -103,78 +109,108 @@ export abstract class LoggerBase {
103109 return redact ( message ) ;
104110 }
105111
106- info ( payload : LogPayload ) : void {
112+ public info ( payload : LogPayload ) : void {
107113 this . log ( "info" , payload ) ;
108114 }
109115
110- error ( payload : LogPayload ) : void {
116+ public error ( payload : LogPayload ) : void {
111117 this . log ( "error" , payload ) ;
112118 }
113- debug ( payload : LogPayload ) : void {
119+ public debug ( payload : LogPayload ) : void {
114120 this . log ( "debug" , payload ) ;
115121 }
116122
117- notice ( payload : LogPayload ) : void {
123+ public notice ( payload : LogPayload ) : void {
118124 this . log ( "notice" , payload ) ;
119125 }
120126
121- warning ( payload : LogPayload ) : void {
127+ public warning ( payload : LogPayload ) : void {
122128 this . log ( "warning" , payload ) ;
123129 }
124130
125- critical ( payload : LogPayload ) : void {
131+ public critical ( payload : LogPayload ) : void {
126132 this . log ( "critical" , payload ) ;
127133 }
128134
129- alert ( payload : LogPayload ) : void {
135+ public alert ( payload : LogPayload ) : void {
130136 this . log ( "alert" , payload ) ;
131137 }
132138
133- emergency ( payload : LogPayload ) : void {
139+ public emergency ( payload : LogPayload ) : void {
134140 this . log ( "emergency" , payload ) ;
135141 }
136142}
137143
138144export class ConsoleLogger extends LoggerBase {
139- protected type : LoggerType = "console" ;
145+ protected readonly type : LoggerType = "console" ;
140146
141147 protected logCore ( level : LogLevel , payload : LogPayload ) : void {
142148 const { id, context, message } = payload ;
143- console . error ( `[${ level . toUpperCase ( ) } ] ${ id . __value } - ${ context } : ${ message } (${ process . pid } )` ) ;
149+ console . error (
150+ `[${ level . toUpperCase ( ) } ]${ id . __value } - ${ context } : ${ message } (${ process . pid } ${ this . serializeAttributes ( payload . attributes ) } )`
151+ ) ;
144152 }
145- }
146153
147- export class DiskLogger extends LoggerBase {
148- private constructor ( private logWriter : MongoLogWriter ) {
149- super ( ) ;
154+ private serializeAttributes ( attributes ?: Record < string , string > ) : string {
155+ if ( ! attributes || Object . keys ( attributes ) . length === 0 ) {
156+ return "" ;
157+ }
158+ return `, ${ Object . entries ( attributes )
159+ . map ( ( [ key , value ] ) => `${ key } =${ value } ` )
160+ . join ( ", " ) } `;
150161 }
162+ }
151163
152- protected type : LoggerType = "disk" ;
153-
154- static async fromPath ( logPath : string ) : Promise < DiskLogger > {
155- await fs . mkdir ( logPath , { recursive : true } ) ;
156-
157- const manager = new MongoLogManager ( {
158- directory : logPath ,
159- retentionDays : 30 ,
160- onwarn : console . warn ,
161- onerror : console . error ,
162- gzip : false ,
163- retentionGB : 1 ,
164- } ) ;
164+ export class DiskLogger extends LoggerBase < { initialized : [ ] } > {
165+ private bufferedMessages : { level : LogLevel ; payload : LogPayload } [ ] = [ ] ;
166+ private logWriter ?: MongoLogWriter ;
165167
166- await manager . cleanupOldLogFiles ( ) ;
168+ public constructor ( logPath : string , onError : ( error : Error ) => void ) {
169+ super ( ) ;
167170
168- const logWriter = await manager . createLogWriter ( ) ;
171+ void this . initialize ( logPath , onError ) ;
172+ }
169173
170- return new DiskLogger ( logWriter ) ;
174+ private async initialize ( logPath : string , onError : ( error : Error ) => void ) : Promise < void > {
175+ try {
176+ await fs . mkdir ( logPath , { recursive : true } ) ;
177+
178+ const manager = new MongoLogManager ( {
179+ directory : logPath ,
180+ retentionDays : 30 ,
181+ onwarn : console . warn ,
182+ onerror : console . error ,
183+ gzip : false ,
184+ retentionGB : 1 ,
185+ } ) ;
186+
187+ await manager . cleanupOldLogFiles ( ) ;
188+
189+ this . logWriter = await manager . createLogWriter ( ) ;
190+
191+ for ( const message of this . bufferedMessages ) {
192+ this . logCore ( message . level , message . payload ) ;
193+ }
194+ this . bufferedMessages = [ ] ;
195+ this . emit ( "initialized" ) ;
196+ } catch ( error : unknown ) {
197+ onError ( error as Error ) ;
198+ }
171199 }
172200
201+ protected type : LoggerType = "disk" ;
202+
173203 protected logCore ( level : LogLevel , payload : LogPayload ) : void {
204+ if ( ! this . logWriter ) {
205+ // If the log writer is not initialized, buffer the message
206+ this . bufferedMessages . push ( { level, payload } ) ;
207+ return ;
208+ }
209+
174210 const { id, context, message } = payload ;
175211 const mongoDBLevel = this . mapToMongoDBLogLevel ( level ) ;
176212
177- this . logWriter [ mongoDBLevel ] ( "MONGODB-MCP" , id , context , message ) ;
213+ this . logWriter [ mongoDBLevel ] ( "MONGODB-MCP" , id , context , message , payload . attributes ) ;
178214 }
179215
180216 private mapToMongoDBLogLevel ( level : LogLevel ) : "info" | "warn" | "error" | "debug" | "fatal" {
@@ -199,11 +235,11 @@ export class DiskLogger extends LoggerBase {
199235}
200236
201237export class McpLogger extends LoggerBase {
202- constructor ( private server : McpServer ) {
238+ public constructor ( private readonly server : McpServer ) {
203239 super ( ) ;
204240 }
205241
206- type : LoggerType = "mcp" ;
242+ protected readonly type : LoggerType = "mcp" ;
207243
208244 protected logCore ( level : LogLevel , payload : LogPayload ) : void {
209245 // Only log if the server is connected
@@ -219,35 +255,41 @@ export class McpLogger extends LoggerBase {
219255}
220256
221257export class CompositeLogger extends LoggerBase {
222- // This is not a real logger type - it should not be used anyway.
223- protected type : LoggerType = "composite" as unknown as LoggerType ;
258+ protected readonly type ?: LoggerType ;
224259
225- private loggers : LoggerBase [ ] = [ ] ;
260+ private readonly loggers : LoggerBase [ ] = [ ] ;
261+ private readonly attributes : Record < string , string > = { } ;
226262
227263 constructor ( ...loggers : LoggerBase [ ] ) {
228264 super ( ) ;
229265
230- this . setLoggers ( ... loggers ) ;
266+ this . loggers = loggers ;
231267 }
232268
233- setLoggers ( ...loggers : LoggerBase [ ] ) : void {
234- if ( loggers . length === 0 ) {
235- throw new Error ( "At least one logger must be provided" ) ;
236- }
237- this . loggers = [ ...loggers ] ;
269+ public addLogger ( logger : LoggerBase ) : void {
270+ this . loggers . push ( logger ) ;
238271 }
239272
240273 public log ( level : LogLevel , payload : LogPayload ) : void {
241274 // Override the public method to avoid the base logger redacting the message payload
242275 for ( const logger of this . loggers ) {
243- logger . log ( level , payload ) ;
276+ logger . log ( level , { ... payload , attributes : { ... this . attributes , ... payload . attributes } } ) ;
244277 }
245278 }
246279
247280 protected logCore ( ) : void {
248281 throw new Error ( "logCore should never be invoked on CompositeLogger" ) ;
249282 }
283+
284+ public setAttribute ( key : string , value : string ) : void {
285+ this . attributes [ key ] = value ;
286+ }
250287}
251288
252- const logger = new CompositeLogger ( new ConsoleLogger ( ) ) ;
253- export default logger ;
289+ export class NullLogger extends LoggerBase {
290+ protected type ?: LoggerType ;
291+
292+ protected logCore ( ) : void {
293+ // No-op logger, does not log anything
294+ }
295+ }
0 commit comments