-
Notifications
You must be signed in to change notification settings - Fork 133
refactor: split loggers #111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
8841667
ed64115
3e5b6d8
92e247e
0c57934
6733c9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,12 @@ import { LoggingMessageNotification } from "@modelcontextprotocol/sdk/types.js"; | |
export type LogLevel = LoggingMessageNotification["params"]["level"]; | ||
|
||
abstract class LoggerBase { | ||
async initialize(): Promise<void> { | ||
return Promise.resolve(); | ||
} | ||
|
||
abstract log(level: LogLevel, id: MongoLogId, context: string, message: string): void; | ||
|
||
info(id: MongoLogId, context: string, message: string): void { | ||
this.log("info", id, context, message); | ||
} | ||
|
@@ -47,22 +52,39 @@ class ConsoleLogger extends LoggerBase { | |
} | ||
} | ||
|
||
class Logger extends LoggerBase { | ||
constructor( | ||
private logWriter: MongoLogWriter, | ||
private server: McpServer | ||
) { | ||
class DiskLogger extends LoggerBase { | ||
private logWriter?: MongoLogWriter; | ||
|
||
constructor(private logPath: string) { | ||
super(); | ||
} | ||
|
||
async initialize(): Promise<void> { | ||
await fs.mkdir(this.logPath, { recursive: true }); | ||
|
||
const manager = new MongoLogManager({ | ||
directory: this.logPath, | ||
retentionDays: 30, | ||
onwarn: console.warn, | ||
onerror: console.error, | ||
gzip: false, | ||
retentionGB: 1, | ||
}); | ||
|
||
await manager.cleanupOldLogFiles(); | ||
|
||
this.logWriter = await manager.createLogWriter(); | ||
} | ||
|
||
log(level: LogLevel, id: MongoLogId, context: string, message: string): void { | ||
message = redact(message); | ||
const mongoDBLevel = this.mapToMongoDBLogLevel(level); | ||
|
||
if (!this.logWriter) { | ||
throw new Error("DiskLogger is not initialized"); | ||
} | ||
|
||
this.logWriter[mongoDBLevel]("MONGODB-MCP", id, context, message); | ||
void this.server.server.sendLoggingMessage({ | ||
level, | ||
data: `[${context}]: ${message}`, | ||
}); | ||
} | ||
|
||
private mapToMongoDBLogLevel(level: LogLevel): "info" | "warn" | "error" | "debug" | "fatal" { | ||
|
@@ -86,31 +108,58 @@ class Logger extends LoggerBase { | |
} | ||
} | ||
|
||
class ProxyingLogger extends LoggerBase { | ||
private internalLogger: LoggerBase = new ConsoleLogger(); | ||
class McpLogger extends LoggerBase { | ||
constructor(private server: McpServer) { | ||
super(); | ||
} | ||
|
||
log(level: LogLevel, id: MongoLogId, context: string, message: string): void { | ||
this.internalLogger.log(level, id, context, message); | ||
log(level: LogLevel, _: MongoLogId, context: string, message: string): void { | ||
void this.server.server.sendLoggingMessage({ | ||
level, | ||
data: `[${context}]: ${message}`, | ||
}); | ||
} | ||
} | ||
|
||
const logger = new ProxyingLogger(); | ||
export default logger; | ||
class CompositeLogger extends LoggerBase { | ||
private loggers: LoggerBase[]; | ||
|
||
export async function initializeLogger(server: McpServer, logPath: string): Promise<void> { | ||
await fs.mkdir(logPath, { recursive: true }); | ||
constructor(...loggers: LoggerBase[]) { | ||
super(); | ||
|
||
const manager = new MongoLogManager({ | ||
directory: logPath, | ||
retentionDays: 30, | ||
onwarn: console.warn, | ||
onerror: console.error, | ||
gzip: false, | ||
retentionGB: 1, | ||
}); | ||
if (loggers.length === 0) { | ||
// default to ConsoleLogger | ||
this.loggers = [new ConsoleLogger()]; | ||
return; | ||
} | ||
|
||
await manager.cleanupOldLogFiles(); | ||
this.loggers = [...loggers]; | ||
} | ||
|
||
const logWriter = await manager.createLogWriter(); | ||
logger["internalLogger"] = new Logger(logWriter, server); | ||
async initialize(): Promise<void> { | ||
for (const logger of this.loggers) { | ||
await logger.initialize(); | ||
} | ||
} | ||
|
||
setLoggers(...loggers: LoggerBase[]): void { | ||
if (loggers.length === 0) { | ||
throw new Error("At least one logger must be provided"); | ||
} | ||
this.loggers = [...loggers]; | ||
} | ||
|
||
log(level: LogLevel, id: MongoLogId, context: string, message: string): void { | ||
for (const logger of this.loggers) { | ||
logger.log(level, id, context, message); | ||
} | ||
} | ||
} | ||
|
||
const logger = new CompositeLogger(); | ||
export default logger; | ||
|
||
export async function initializeLogger(server: McpServer, logPath: string): Promise<void> { | ||
logger.setLoggers(new McpLogger(server), new DiskLogger(logPath)); | ||
|
||
await logger.initialize(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having an async instance method for initializing the logger, wouldn't it be cleaner to turn it into an async factori that calls a private constructor? That way we won't have ambiguous optionality for the
logWriter
.Something like: