diff options
Diffstat (limited to 'packages/server/src/core/logger.ts')
| -rw-r--r-- | packages/server/src/core/logger.ts | 239 |
1 files changed, 0 insertions, 239 deletions
diff --git a/packages/server/src/core/logger.ts b/packages/server/src/core/logger.ts deleted file mode 100644 index feb16944..00000000 --- a/packages/server/src/core/logger.ts +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import path from 'node:path'; -import { promises as fs } from 'node:fs'; - -const GEMINI_DIR = '.gemini'; -const LOG_FILE_NAME = 'logs.json'; - -export enum MessageSenderType { - USER = 'user', -} - -export interface LogEntry { - sessionId: number; - messageId: number; - timestamp: string; - type: MessageSenderType; - message: string; -} - -export class Logger { - private logFilePath: string | undefined; - private sessionId: number | undefined; - private messageId = 0; // Instance-specific counter for the next messageId - private initialized = false; - private logs: LogEntry[] = []; // In-memory cache, ideally reflects the last known state of the file - - constructor() {} - - private async _readLogFile(): Promise<LogEntry[]> { - if (!this.logFilePath) { - throw new Error('Log file path not set during read attempt.'); - } - try { - const fileContent = await fs.readFile(this.logFilePath, 'utf-8'); - const parsedLogs = JSON.parse(fileContent); - if (!Array.isArray(parsedLogs)) { - console.debug( - `Log file at ${this.logFilePath} is not a valid JSON array. Starting with empty logs.`, - ); - await this._backupCorruptedLogFile('malformed_array'); - return []; - } - return parsedLogs.filter( - (entry) => - typeof entry.sessionId === 'number' && - typeof entry.messageId === 'number' && - typeof entry.timestamp === 'string' && - typeof entry.type === 'string' && - typeof entry.message === 'string', - ) as LogEntry[]; - } catch (error) { - const nodeError = error as NodeJS.ErrnoException; - if (nodeError.code === 'ENOENT') { - return []; - } - if (error instanceof SyntaxError) { - console.debug( - `Invalid JSON in log file ${this.logFilePath}. Backing up and starting fresh.`, - error, - ); - await this._backupCorruptedLogFile('invalid_json'); - return []; - } - console.debug( - `Failed to read or parse log file ${this.logFilePath}:`, - error, - ); - throw error; - } - } - - private async _backupCorruptedLogFile(reason: string): Promise<void> { - if (!this.logFilePath) return; - const backupPath = `${this.logFilePath}.${reason}.${Date.now()}.bak`; - try { - await fs.rename(this.logFilePath, backupPath); - console.debug(`Backed up corrupted log file to ${backupPath}`); - } catch (_backupError) { - // If rename fails (e.g. file doesn't exist), no need to log an error here as the primary error (e.g. invalid JSON) is already handled. - } - } - - async initialize(): Promise<void> { - if (this.initialized) { - return; - } - this.sessionId = Math.floor(Date.now() / 1000); - const geminiDir = path.resolve(process.cwd(), GEMINI_DIR); - this.logFilePath = path.join(geminiDir, LOG_FILE_NAME); - - try { - await fs.mkdir(geminiDir, { recursive: true }); - let fileExisted = true; - try { - await fs.access(this.logFilePath); - } catch (_e) { - fileExisted = false; - } - this.logs = await this._readLogFile(); - if (!fileExisted && this.logs.length === 0) { - await fs.writeFile(this.logFilePath, '[]', 'utf-8'); - } - const sessionLogs = this.logs.filter( - (entry) => entry.sessionId === this.sessionId, - ); - this.messageId = - sessionLogs.length > 0 - ? Math.max(...sessionLogs.map((entry) => entry.messageId)) + 1 - : 0; - this.initialized = true; - } catch (err) { - console.error('Failed to initialize logger:', err); - this.initialized = false; - } - } - - private async _updateLogFile( - entryToAppend: LogEntry, - ): Promise<LogEntry | null> { - if (!this.logFilePath) { - console.debug('Log file path not set. Cannot persist log entry.'); - throw new Error('Log file path not set during update attempt.'); - } - - let currentLogsOnDisk: LogEntry[]; - try { - currentLogsOnDisk = await this._readLogFile(); - } catch (readError) { - console.debug( - 'Critical error reading log file before append:', - readError, - ); - throw readError; - } - - // Determine the correct messageId for the new entry based on current disk state for its session - const sessionLogsOnDisk = currentLogsOnDisk.filter( - (e) => e.sessionId === entryToAppend.sessionId, - ); - const nextMessageIdForSession = - sessionLogsOnDisk.length > 0 - ? Math.max(...sessionLogsOnDisk.map((e) => e.messageId)) + 1 - : 0; - - // Update the messageId of the entry we are about to append - entryToAppend.messageId = nextMessageIdForSession; - - // Check if this entry (same session, same *recalculated* messageId, same content) might already exist - // This is a stricter check for true duplicates if multiple instances try to log the exact same thing - // at the exact same calculated messageId slot. - const entryExists = currentLogsOnDisk.some( - (e) => - e.sessionId === entryToAppend.sessionId && - e.messageId === entryToAppend.messageId && - e.timestamp === entryToAppend.timestamp && // Timestamps are good for distinguishing - e.message === entryToAppend.message, - ); - - if (entryExists) { - console.debug( - `Duplicate log entry detected and skipped: session ${entryToAppend.sessionId}, messageId ${entryToAppend.messageId}`, - ); - this.logs = currentLogsOnDisk; // Ensure in-memory is synced with disk - return null; // Indicate that no new entry was actually added - } - - currentLogsOnDisk.push(entryToAppend); - - try { - await fs.writeFile( - this.logFilePath, - JSON.stringify(currentLogsOnDisk, null, 2), - 'utf-8', - ); - this.logs = currentLogsOnDisk; - return entryToAppend; // Return the successfully appended entry - } catch (error) { - console.debug('Error writing to log file:', error); - throw error; - } - } - - async getPreviousUserMessages(): Promise<string[]> { - if (!this.initialized) return []; - return this.logs - .filter((entry) => entry.type === MessageSenderType.USER) - .sort((a, b) => { - if (b.sessionId !== a.sessionId) return b.sessionId - a.sessionId; - const dateA = new Date(a.timestamp).getTime(); - const dateB = new Date(b.timestamp).getTime(); - if (dateB !== dateA) return dateB - dateA; - return b.messageId - a.messageId; - }) - .map((entry) => entry.message); - } - - async logMessage(type: MessageSenderType, message: string): Promise<void> { - if (!this.initialized || this.sessionId === undefined) { - console.debug( - 'Logger not initialized or session ID missing. Cannot log message.', - ); - return; - } - - // The messageId used here is the instance's idea of the next ID. - // _updateLogFile will verify and potentially recalculate based on the file's actual state. - const newEntryObject: LogEntry = { - sessionId: this.sessionId, - messageId: this.messageId, // This will be recalculated in _updateLogFile - type, - message, - timestamp: new Date().toISOString(), - }; - - try { - const writtenEntry = await this._updateLogFile(newEntryObject); - if (writtenEntry) { - // If an entry was actually written (not a duplicate skip), - // then this instance can increment its idea of the next messageId for this session. - this.messageId = writtenEntry.messageId + 1; - } - } catch (_error) { - // Error already logged by _updateLogFile or _readLogFile - } - } - - close(): void { - this.initialized = false; - this.logFilePath = undefined; - this.logs = []; - this.sessionId = undefined; - this.messageId = 0; - } -} |
