summaryrefslogtreecommitdiff
path: root/packages/server/src/core/logger.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/server/src/core/logger.ts')
-rw-r--r--packages/server/src/core/logger.ts239
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;
- }
-}