| | import fs from 'fs' |
| | import path from 'path' |
| |
|
| | export interface LogEntry { |
| | timestamp: string |
| | source: 'Server' | 'Browser' |
| | level: string |
| | message: string |
| | } |
| |
|
| | |
| | export class FileLogger { |
| | private logFilePath: string = '' |
| | private isInitialized: boolean = false |
| | private logQueue: string[] = [] |
| | private flushTimer: NodeJS.Timeout | null = null |
| | private mcpServerEnabled: boolean = false |
| |
|
| | public initialize(distDir: string, mcpServerEnabled: boolean): void { |
| | this.logFilePath = path.join(distDir, 'logs', `next-development.log`) |
| | this.mcpServerEnabled = mcpServerEnabled |
| |
|
| | if (this.isInitialized) { |
| | return |
| | } |
| |
|
| | |
| | if (!this.mcpServerEnabled) { |
| | return |
| | } |
| |
|
| | try { |
| | |
| | |
| | fs.mkdirSync(path.dirname(this.logFilePath), { recursive: true }) |
| | fs.writeFileSync(this.logFilePath, '') |
| | this.isInitialized = true |
| | } catch (error) { |
| | console.error(error) |
| | } |
| | } |
| |
|
| | private formatTimestamp(): string { |
| | |
| | const now = performance.now() |
| | const hours = Math.floor(now / 3600000) |
| | .toString() |
| | .padStart(2, '0') |
| | const minutes = Math.floor((now % 3600000) / 60000) |
| | .toString() |
| | .padStart(2, '0') |
| | const seconds = Math.floor((now % 60000) / 1000) |
| | .toString() |
| | .padStart(2, '0') |
| | const milliseconds = Math.floor(now % 1000) |
| | .toString() |
| | .padStart(3, '0') |
| | return `${hours}:${minutes}:${seconds}.${milliseconds}` |
| | } |
| |
|
| | private formatLogEntry(entry: LogEntry): string { |
| | const { timestamp, source, level, message } = entry |
| | const levelPadded = level.toUpperCase().padEnd(7, ' ') |
| | const sourcePadded = source === 'Browser' ? source : 'Server ' |
| | return `[${timestamp}] ${sourcePadded} ${levelPadded} ${message}\n` |
| | } |
| |
|
| | private scheduleFlush(): void { |
| | |
| | if (this.flushTimer) { |
| | clearTimeout(this.flushTimer) |
| | this.flushTimer = null |
| | } |
| |
|
| | |
| | this.flushTimer = setTimeout(() => { |
| | this.flush() |
| | }, 100) |
| | } |
| |
|
| | public getLogQueue(): string[] { |
| | return this.logQueue |
| | } |
| |
|
| | private flush(): void { |
| | if (this.logQueue.length === 0) { |
| | return |
| | } |
| |
|
| | |
| | if (!this.mcpServerEnabled) { |
| | this.logQueue = [] |
| | this.flushTimer = null |
| | return |
| | } |
| |
|
| | try { |
| | |
| | const logDir = path.dirname(this.logFilePath) |
| | if (!fs.existsSync(logDir)) { |
| | fs.mkdirSync(logDir, { recursive: true }) |
| | } |
| |
|
| | const logsToWrite = this.logQueue.join('') |
| | |
| | fs.appendFileSync(this.logFilePath, logsToWrite) |
| | this.logQueue = [] |
| | } catch (error) { |
| | console.error('Failed to flush logs to file:', error) |
| | } finally { |
| | this.flushTimer = null |
| | } |
| | } |
| |
|
| | private enqueueLog(formattedEntry: string): void { |
| | this.logQueue.push(formattedEntry) |
| |
|
| | |
| | if (this.flushTimer) { |
| | clearTimeout(this.flushTimer) |
| | this.flushTimer = null |
| | } |
| |
|
| | this.scheduleFlush() |
| | } |
| |
|
| | log(source: 'Server' | 'Browser', level: string, message: string): void { |
| | |
| | if (!this.mcpServerEnabled) { |
| | return |
| | } |
| |
|
| | if (!this.isInitialized) { |
| | return |
| | } |
| |
|
| | const logEntry: LogEntry = { |
| | timestamp: this.formatTimestamp(), |
| | source, |
| | level, |
| | message, |
| | } |
| |
|
| | const formattedEntry = this.formatLogEntry(logEntry) |
| | this.enqueueLog(formattedEntry) |
| | } |
| |
|
| | logServer(level: string, message: string): void { |
| | this.log('Server', level, message) |
| | } |
| |
|
| | logBrowser(level: string, message: string): void { |
| | this.log('Browser', level, message) |
| | } |
| |
|
| | |
| | forceFlush(): void { |
| | if (this.flushTimer) { |
| | clearTimeout(this.flushTimer) |
| | this.flushTimer = null |
| | } |
| | this.flush() |
| | } |
| |
|
| | |
| | destroy(): void { |
| | this.forceFlush() |
| | } |
| | } |
| |
|
| | |
| | let fileLogger: FileLogger | null = null |
| |
|
| | export function getFileLogger(): FileLogger { |
| | if (!fileLogger || process.env.NODE_ENV === 'test') { |
| | fileLogger = new FileLogger() |
| | } |
| | return fileLogger |
| | } |
| |
|
| | |
| | export function test__resetFileLogger(): void { |
| | if (fileLogger) { |
| | fileLogger.destroy() |
| | } |
| | fileLogger = null |
| | } |
| |
|