summaryrefslogtreecommitdiff
path: root/packages/server/src/core/logger.ts
diff options
context:
space:
mode:
authorSeth Troisi <[email protected]>2025-05-21 07:36:22 +0000
committerGitHub <[email protected]>2025-05-21 00:36:22 -0700
commitcd13c5881b7cb60c0fca9e674c0344815ca0070d (patch)
tree661f72841f8005f07b510be5d0d987224ade31cd /packages/server/src/core/logger.ts
parentbda472f1476476a1bcd4a2b7050ab2823a6d3bb5 (diff)
Add Logger for command history (#435)
Diffstat (limited to 'packages/server/src/core/logger.ts')
-rw-r--r--packages/server/src/core/logger.ts131
1 files changed, 131 insertions, 0 deletions
diff --git a/packages/server/src/core/logger.ts b/packages/server/src/core/logger.ts
new file mode 100644
index 00000000..d12d4240
--- /dev/null
+++ b/packages/server/src/core/logger.ts
@@ -0,0 +1,131 @@
+/**
+ * @license
+ * Copyright 2025 Google LLC
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import path from 'node:path';
+import sqlite3 from 'sqlite3';
+import { promises as fs } from 'node:fs';
+
+const GEMINI_DIR = '.gemini';
+const DB_NAME = 'logs.db';
+const CREATE_TABLE_SQL = `
+CREATE TABLE IF NOT EXISTS messages (
+ session_id INTEGER,
+ message_id INTEGER,
+ timestamp TEXT,
+ type TEXT,
+ message TEXT
+);`;
+
+export enum MessageSenderType {
+ USER = 'user',
+}
+
+export class Logger {
+ private db: sqlite3.Database | undefined;
+ private sessionId: number | undefined;
+ private messageId: number | undefined;
+
+ constructor() {}
+
+ async initialize(): Promise<void> {
+ if (this.db) {
+ return;
+ }
+
+ this.sessionId = Math.floor(Date.now() / 1000);
+ this.messageId = 0;
+
+ // Could be cleaner if our sqlite package supported promises.
+ return new Promise((resolve, reject) => {
+ const DB_DIR = path.resolve(process.cwd(), GEMINI_DIR);
+ const DB_PATH = path.join(DB_DIR, DB_NAME);
+ fs.mkdir(DB_DIR, { recursive: true })
+ .then(() => {
+ this.db = new sqlite3.Database(
+ DB_PATH,
+ sqlite3.OPEN_READWRITE |
+ sqlite3.OPEN_CREATE |
+ sqlite3.OPEN_FULLMUTEX,
+ (err: Error | null) => {
+ if (err) {
+ reject(err);
+ }
+
+ // Read and execute the SQL script in create_tables.sql
+ this.db?.exec(CREATE_TABLE_SQL, (err: Error | null) => {
+ if (err) {
+ this.db?.close();
+ reject(err);
+ }
+ resolve();
+ });
+ },
+ );
+ })
+ .catch(reject);
+ });
+ }
+
+ /**
+ * Get list of previous user inputs sorted most recent first.
+ * @returns list of messages.
+ */
+ async getPreviousUserMessages(): Promise<string[]> {
+ if (!this.db) {
+ console.error('Database not initialized.');
+ return [];
+ }
+
+ return new Promise((resolve, reject) => {
+ // Most recent messages first
+ const query = `SELECT message FROM messages
+ WHERE type = '${MessageSenderType.USER}'
+ ORDER BY session_id DESC, message_id DESC`;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ this.db!.all(query, [], (err: Error | null, rows: any[]) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(rows.map((row) => row.message));
+ }
+ });
+ });
+ }
+
+ async logMessage(type: MessageSenderType, message: string): Promise<void> {
+ if (!this.db) {
+ console.error('Database not initialized.');
+ return;
+ }
+
+ return new Promise((resolve, reject) => {
+ const query = `INSERT INTO messages (session_id, message_id, type, message, timestamp) VALUES (?, ?, ?, ?, datetime('now'))`;
+ this.messageId = this.messageId! + 1;
+ this.db!.run(
+ query,
+ [this.sessionId || 0, this.messageId - 1, type, message],
+ (err: Error | null) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve();
+ }
+ },
+ );
+ });
+ }
+
+ close(): void {
+ if (this.db) {
+ this.db.close((err: Error | null) => {
+ if (err) {
+ console.error('Error closing database:', err.message);
+ }
+ });
+ this.db = undefined;
+ }
+ }
+}