diff --git a/package-lock.json b/package-lock.json index a0b81ab..5366860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,8 @@ "undici": "^7.16.0", "update-electron-app": "^3.1.1", "uuid": "^13.0.0", + "winston": "^3.19.0", + "winston-daily-rotate-file": "^5.0.0", "yaml": "^2.8.2", "zod": "^4.1.12" }, @@ -1064,6 +1066,17 @@ "node": ">=18" } }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmmirror.com/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "license": "MIT", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@develar/schema-utils": { "version": "2.6.5", "resolved": "https://registry.npmmirror.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz", @@ -9664,6 +9677,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "license": "MIT", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "license": "MIT", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@so-ric/colorspace/node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@standard-schema/spec": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", @@ -11178,6 +11247,12 @@ "@types/node": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-11.0.0.tgz", @@ -12750,7 +12825,6 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, "license": "MIT" }, "node_modules/async-exit-hook": { @@ -16088,6 +16162,12 @@ "dev": true, "license": "MIT" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encode-utf8": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/encode-utf8/-/encode-utf8-1.0.3.tgz", @@ -17466,6 +17546,12 @@ "pend": "~1.2.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/figures": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", @@ -17508,6 +17594,15 @@ "node": ">=16.0.0" } }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/file-type": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", @@ -17757,6 +17852,12 @@ "imul": "^1.0.0" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -20686,6 +20787,12 @@ "node": ">=14.14.0" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/launch-ide": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/launch-ide/-/launch-ide-1.3.0.tgz", @@ -21483,6 +21590,32 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/loglevel": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", @@ -22122,6 +22255,15 @@ "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -24582,6 +24724,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -24720,6 +24871,15 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -28561,6 +28721,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -29372,6 +29541,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -29746,6 +29921,15 @@ "node": ">=0.8.0" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -31518,6 +31702,81 @@ "node": ">=8" } }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmmirror.com/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "license": "MIT", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmmirror.com/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/winston/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index d485d64..0b5dd05 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,11 @@ "@electron-forge/plugin-vite": "^7.10.2", "@electron-forge/publisher-github": "^7.10.2", "@electron-forge/shared-types": "^7.8.1", - "@pengx17/electron-forge-maker-appimage": "^1.2.1", "@electron/fuses": "~1.8.0", "@electron/rebuild": "^3.7.1", "@eslint/compat": "^1.4.1", "@eslint/js": "^9.39.0", + "@pengx17/electron-forge-maker-appimage": "^1.2.1", "@playwright/test": "^1.56.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", @@ -63,8 +63,8 @@ "conventional-changelog-conventionalcommits": "^9.1.0", "electron": "^37.3.1", "electron-devtools-installer": "^4.0.0", - "electron-playwright-helpers": "^1.8.2", "electron-packager-languages": "^0.6.0", + "electron-playwright-helpers": "^1.8.2", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4", @@ -147,6 +147,8 @@ "undici": "^7.16.0", "update-electron-app": "^3.1.1", "uuid": "^13.0.0", + "winston": "^3.19.0", + "winston-daily-rotate-file": "^5.0.0", "yaml": "^2.8.2", "zod": "^4.1.12" } diff --git a/src/instrument.ts b/src/instrument.ts index 3252aa1..5581150 100644 --- a/src/instrument.ts +++ b/src/instrument.ts @@ -15,7 +15,7 @@ function getQuickConfig() { return config.error_reporting_enabled; } } catch (e) { - console.error('Failed to read config for Sentry init:', e); + logger.error('Failed to read config for Sentry init:', e); } return false; } diff --git a/src/ipc/cloud/router.ts b/src/ipc/cloud/router.ts index 8418e54..e39e0cd 100644 --- a/src/ipc/cloud/router.ts +++ b/src/ipc/cloud/router.ts @@ -13,6 +13,7 @@ import { } from './handler'; import { CloudAccountSchema } from '../../types/cloudAccount'; import { CloudAccountRepo } from '../database/cloudHandler'; +import { logger } from '../../utils/logger'; export const cloudRouter = os.router({ addGoogleAccount: os @@ -72,7 +73,7 @@ export const cloudRouter = os.router({ return result; } catch (error: any) { - console.error('[ORPC] syncLocalAccount error:', error.message, error.stack); + logger.error('[ORPC] syncLocalAccount error:', error.message, error.stack); throw error; } }), diff --git a/src/ipc/gateway/handlers.ts b/src/ipc/gateway/handlers.ts index bbed3ef..ad73100 100644 --- a/src/ipc/gateway/handlers.ts +++ b/src/ipc/gateway/handlers.ts @@ -5,6 +5,7 @@ import { bootstrapNestServer, stopNestServer, getNestServerStatus } from '../../server/main'; import { ConfigManager } from '../config/manager'; import { v4 as uuidv4 } from 'uuid'; +import { logger } from '../../utils/logger'; /** * Start the gateway server (NestJS) @@ -20,7 +21,7 @@ export const startGateway = async (port: number): Promise => { return await bootstrapNestServer(proxyConfig); } catch (e) { - console.error('Failed to start gateway:', e); + logger.error('Failed to start gateway:', e); return false; } }; @@ -32,7 +33,7 @@ export const stopGateway = async (): Promise => { try { return await stopNestServer(); } catch (e) { - console.error('Failed to stop gateway:', e); + logger.error('Failed to stop gateway:', e); return false; } }; diff --git a/src/ipc/handler.ts b/src/ipc/handler.ts index 19f81f5..a795a22 100644 --- a/src/ipc/handler.ts +++ b/src/ipc/handler.ts @@ -28,7 +28,7 @@ try { logger.info(`Database router check: ${!!db}`); } } catch (e) { - console.error('Error logging router:', e); + logger.error('Error logging router:', e); } export const rpcHandler: RPCHandler> = new RPCHandler(router); diff --git a/src/ipc/router.ts b/src/ipc/router.ts index 5f7607e..527c3f3 100644 --- a/src/ipc/router.ts +++ b/src/ipc/router.ts @@ -11,6 +11,7 @@ import { os } from '@orpc/server'; import { z } from 'zod'; import { isProcessRunning, closeAntigravity, startAntigravity } from './process/handler'; import { systemHandler } from './system/handler'; +import { logger } from '../utils/logger'; // Log middleware setup const logMiddleware = os.middleware(async (opts: any) => { @@ -21,7 +22,7 @@ const logMiddleware = os.middleware(async (opts: any) => { const result = await next({}); return result; } catch (err) { - console.error(`[ORPC] Error in handler for ${JSON.stringify(requestPath)}:`, err); + logger.error(`[ORPC] Error in handler for ${JSON.stringify(requestPath)}:`, err); throw err; } }); diff --git a/src/lib/antigravity/ClaudeRequestMapper.ts b/src/lib/antigravity/ClaudeRequestMapper.ts index 249ef10..e101c86 100644 --- a/src/lib/antigravity/ClaudeRequestMapper.ts +++ b/src/lib/antigravity/ClaudeRequestMapper.ts @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import { mapClaudeModelToGemini } from './ModelMapping'; import { cleanJsonSchema } from './JsonSchemaUtils'; import { SignatureStore } from './SignatureStore'; +import { logger } from '../../utils/logger'; import { ClaudeRequest, Message, @@ -378,7 +379,7 @@ function injectGoogleSearchTool(body: { tools?: GeminiToolDeclaration[] }) { const hasFunctions = toolsArr.some((t) => t.functionDeclarations); if (hasFunctions) { - console.info('Skipping googleSearch injection due to existing functionDeclarations'); + logger.info('Skipping googleSearch injection due to existing functionDeclarations'); return; } diff --git a/src/lib/antigravity/ClaudeStreamingMapper.ts b/src/lib/antigravity/ClaudeStreamingMapper.ts index 71e049a..ec6b876 100644 --- a/src/lib/antigravity/ClaudeStreamingMapper.ts +++ b/src/lib/antigravity/ClaudeStreamingMapper.ts @@ -1,6 +1,7 @@ import { GeminiPart, Usage, UsageMetadata } from './types'; import { SignatureStore } from './SignatureStore'; import { decodeSignature } from './signature-utils'; +import { logger } from '../../utils/logger'; type BlockType = 'None' | 'Text' | 'Thinking' | 'Function'; @@ -249,7 +250,7 @@ export class StreamingState { const chunks: string[] = []; this.parseErrorCount++; - console.warn( + logger.warn( `[SSE-Parser] Parse error #${this.parseErrorCount}. Raw data length: ${rawData.length}`, ); @@ -260,7 +261,7 @@ export class StreamingState { // Emit error event if too many errors if (this.parseErrorCount > 3) { - console.error( + logger.error( `[SSE-Parser] High error rate (${this.parseErrorCount} errors). Stream may be corrupted.`, ); chunks.push( diff --git a/src/lib/antigravity/ModelMapping.ts b/src/lib/antigravity/ModelMapping.ts index 196f9bc..5ce8146 100644 --- a/src/lib/antigravity/ModelMapping.ts +++ b/src/lib/antigravity/ModelMapping.ts @@ -1,7 +1,4 @@ -const logger = { - log: (msg: string) => console.log(msg), - warn: (msg: string) => console.warn(msg), -}; +import { logger } from '../../utils/logger'; const CLAUDE_TO_GEMINI: Record = { // Directly supported models @@ -78,7 +75,7 @@ export function resolveModelRoute( ): string { // 1. Check custom exact mapping (Highest priority) if (customMapping[originalModel]) { - logger.log( + logger.info( `[Router] Using custom exact mapping: ${originalModel} -> ${customMapping[originalModel]}`, ); return customMapping[originalModel]; @@ -98,7 +95,7 @@ export function resolveModelRoute( lowerModel === 'gpt-4' ) { if (openaiMapping['gpt-4-series']) { - logger.log( + logger.info( `[Router] Using GPT-4 series mapping: ${originalModel} -> ${openaiMapping['gpt-4-series']}`, ); return openaiMapping['gpt-4-series']; @@ -113,7 +110,7 @@ export function resolveModelRoute( lowerModel.includes('turbo') ) { if (openaiMapping['gpt-4o-series']) { - logger.log( + logger.info( `[Router] Using GPT-4o/3.5 series mapping: ${originalModel} -> ${openaiMapping['gpt-4o-series']}`, ); return openaiMapping['gpt-4o-series']; @@ -124,13 +121,13 @@ export function resolveModelRoute( if (lowerModel.startsWith('gpt-5')) { // Prefer gpt-5-series mapping, fallback to gpt-4-series if missing if (openaiMapping['gpt-5-series']) { - logger.log( + logger.info( `[Router] Using GPT-5 series mapping: ${originalModel} -> ${openaiMapping['gpt-5-series']}`, ); return openaiMapping['gpt-5-series']; } if (openaiMapping['gpt-4-series']) { - logger.log( + logger.info( `[Router] Using GPT-4 series mapping (GPT-5 fallback): ${originalModel} -> ${openaiMapping['gpt-4-series']}`, ); return openaiMapping['gpt-4-series']; diff --git a/src/lib/antigravity/QuotaService.ts b/src/lib/antigravity/QuotaService.ts index e40dbfd..23d18b4 100644 --- a/src/lib/antigravity/QuotaService.ts +++ b/src/lib/antigravity/QuotaService.ts @@ -1,5 +1,6 @@ import axios, { AxiosError, AxiosInstance } from 'axios'; import { QuotaData, Tier, LoadProjectResponse, QuotaApiResponse } from './types'; +import { logger } from '../../utils/logger'; // Constants const QUOTA_API_URL = 'https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels'; @@ -48,15 +49,15 @@ export class QuotaService { const subscriptionTier = data.paidTier?.id || data.currentTier?.id; if (subscriptionTier) { - console.log(`📊 [${email}] Subscription Identified: ${subscriptionTier}`); + logger.info(`📊 [${email}] Subscription Identified: ${subscriptionTier}`); } return [projectId, subscriptionTier]; } else { - console.warn(`⚠️ [${email}] loadCodeAssist failed: Status: ${res.status}`); + logger.warn(`⚠️ [${email}] loadCodeAssist failed: Status: ${res.status}`); } } catch (error: any) { - console.error(`❌ [${email}] loadCodeAssist Network Error: ${error.message}`); + logger.error(`❌ [${email}] loadCodeAssist Network Error: ${error.message}`); } return [undefined, undefined]; @@ -86,7 +87,7 @@ export class QuotaService { const url = QUOTA_API_URL; const maxRetries = 3; - console.log(`Sending quota request to ${url}`); + logger.info(`Sending quota request to ${url}`); for (let attempt = 1; attempt <= maxRetries; attempt++) { try { @@ -104,11 +105,11 @@ export class QuotaService { subscriptionTier: subscriptionTier, }; - console.log(`Quota API returned ${Object.keys(quotaResponse.models || {}).length} models:`); + logger.info(`Quota API returned ${Object.keys(quotaResponse.models || {}).length} models:`); if (quotaResponse.models) { for (const [name, info] of Object.entries(quotaResponse.models)) { - console.log(` - ${name}`); + logger.info(` - ${name}`); if (info.quotaInfo) { const fraction = info.quotaInfo.remainingFraction ?? 0; const percentage = Math.floor(fraction * 100); @@ -135,7 +136,7 @@ export class QuotaService { // ✅ Handle 403 Forbidden specifically - return immediately, do not retry if (status === 403) { - console.warn(`Account no permission (403 Forbidden), marked as forbidden`); + logger.warn(`Account no permission (403 Forbidden), marked as forbidden`); return { quotaData: { models: {}, @@ -146,9 +147,9 @@ export class QuotaService { }; } - console.warn(`API Error: ${status} - ${text} (Attempt ${attempt}/${maxRetries})`); + logger.warn(`API Error: ${status} - ${text} (Attempt ${attempt}/${maxRetries})`); } else { - console.warn(`Request Failed: ${error.message} (Attempt ${attempt}/${maxRetries})`); + logger.warn(`Request Failed: ${error.message} (Attempt ${attempt}/${maxRetries})`); } if (attempt < maxRetries) { diff --git a/src/lib/antigravity/SignatureStore.ts b/src/lib/antigravity/SignatureStore.ts index 3cc20a9..25ff02c 100644 --- a/src/lib/antigravity/SignatureStore.ts +++ b/src/lib/antigravity/SignatureStore.ts @@ -2,6 +2,7 @@ * Global thought_signature storage shared by all endpoints * Used to capture and replay signatures for Gemini 3+ function calls when clients don't pass them back. */ +import { logger } from '../../utils/logger'; class SignatureStoreImpl { private static instance: SignatureStoreImpl; @@ -28,12 +29,12 @@ class SignatureStoreImpl { const newLen = sig.length; if (newLen > existingLen) { - console.log( + logger.info( `[ThoughtSig] Storing new signature (length: ${newLen}, replacing old: ${existingLen})`, ); this.signature = sig; } else { - console.debug( + logger.debug( `[ThoughtSig] Skipping shorter signature (new length: ${newLen}, existing: ${existingLen})`, ); } diff --git a/src/lib/antigravity/thinking-utils.ts b/src/lib/antigravity/thinking-utils.ts index 2cde648..efbb92b 100644 --- a/src/lib/antigravity/thinking-utils.ts +++ b/src/lib/antigravity/thinking-utils.ts @@ -5,6 +5,7 @@ * Problem: Invalid signatures cause thinking blocks to be dropped, losing content * Solution: Validate and convert invalid blocks to text instead of dropping */ +import { logger } from '../../utils/logger'; /** Minimum length for a valid thought signature */ const MIN_SIGNATURE_LENGTH = 10; @@ -82,7 +83,7 @@ export function gracefulDegradeThinking(block: ContentBlock): ContentBlock | nul // Invalid signature - check if we have content to preserve if (thinkingBlock.thinking && thinkingBlock.thinking.length > 0) { // Convert to text block to preserve content - console.info( + logger.info( `[Thinking-Utils] Converting invalid thinking block to text. Content length: ${thinkingBlock.thinking.length}`, ); return { @@ -92,7 +93,7 @@ export function gracefulDegradeThinking(block: ContentBlock): ContentBlock | nul } // Empty content + invalid signature = drop - console.debug('[Thinking-Utils] Dropping empty thinking block with invalid signature'); + logger.debug('[Thinking-Utils] Dropping empty thinking block with invalid signature'); return null; } diff --git a/src/main.ts b/src/main.ts index b137d55..79d54d1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -55,7 +55,7 @@ function logPacket(data: any) { ); } catch (e) { if (e instanceof Error) { - console.error(e.message); + logger.error('Failed to append ORPC packet log', e); } } } @@ -315,7 +315,7 @@ async function setupORPC() { logPacket(data); } catch (e) { - console.log('[RAW ORPC MSG] (unparseable)', msgEvent.data); + logger.debug('[RAW ORPC MSG] (unparseable)', msgEvent.data); } }); diff --git a/src/services/GoogleAPIService.ts b/src/services/GoogleAPIService.ts index 011be9d..8ca742f 100644 --- a/src/services/GoogleAPIService.ts +++ b/src/services/GoogleAPIService.ts @@ -1,5 +1,6 @@ import { ConfigManager } from '../ipc/config/manager'; import { ProxyAgent } from 'undici'; +import { logger } from '../utils/logger'; // --- Constants & Config --- @@ -85,7 +86,7 @@ export class GoogleAPIService { } } catch (e) { // Fallback or log if config load fails (shouldn't happen usually) - console.warn('[GoogleAPIService] Failed to load proxy config', e); + logger.warn('[GoogleAPIService] Failed to load proxy config', e); } return {}; } @@ -246,7 +247,7 @@ export class GoogleAPIService { } } } catch (e) { - console.warn(`[GoogleAPIService] Failed to fetch project ID (Attempt ${i + 1}):`, e); + logger.warn(`[GoogleAPIService] Failed to fetch project ID (Attempt ${i + 1}):`, e); await new Promise((r) => setTimeout(r, 500)); // Sleep 500ms } } @@ -296,7 +297,7 @@ export class GoogleAPIService { } const errorMsg = `HTTP ${status} - ${text}`; - console.warn( + logger.warn( `[GoogleAPIService] API Error: ${errorMsg} (Attempt ${attempt}/${maxRetries})`, ); @@ -327,7 +328,7 @@ export class GoogleAPIService { return result; } catch (e: unknown) { if (e instanceof Error) { - console.warn( + logger.warn( `[GoogleAPIService] Request failed: ${e.message} (Attempt ${attempt}/${maxRetries})`, ); } diff --git a/src/tests/unit/logger.test.ts b/src/tests/unit/logger.test.ts index e3853a3..acac1e5 100644 --- a/src/tests/unit/logger.test.ts +++ b/src/tests/unit/logger.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; import { logger } from '../../utils/logger'; import fs from 'fs'; import path from 'path'; @@ -12,40 +12,65 @@ vi.mock('../../utils/paths', async () => { describe('Logger Utilities', () => { const testLogDir = path.join(process.cwd(), 'temp_test_logs'); - const testLogFile = path.join(testLogDir, 'app.log'); + + const getLatestLogFile = () => { + const files = fs + .readdirSync(testLogDir) + .filter((file) => /^app-\d{4}-\d{2}-\d{2}(\.\d+)?\.log$/.test(file)) + .sort(); + + if (files.length === 0) { + return null; + } + + return path.join(testLogDir, files[files.length - 1]); + }; + + const waitForLogContains = async (text: string) => { + for (let i = 0; i < 20; i++) { + const filePath = getLatestLogFile(); + if (filePath && fs.existsSync(filePath)) { + const content = fs.readFileSync(filePath, 'utf-8'); + if (content.includes(text)) { + return filePath; + } + } + await new Promise((resolve) => setTimeout(resolve, 50)); + } + + return null; + }; beforeEach(() => { - console.log('Test Log Dir:', testLogDir); - if (fs.existsSync(testLogDir)) { - fs.rmSync(testLogDir, { recursive: true, force: true }); - } - fs.mkdirSync(testLogDir, { recursive: true }); - console.log('Dir created:', fs.existsSync(testLogDir)); - }); - - afterEach(() => { - if (fs.existsSync(testLogDir)) { - fs.rmSync(testLogDir, { recursive: true, force: true }); + if (!fs.existsSync(testLogDir)) { + fs.mkdirSync(testLogDir, { recursive: true }); } }); - it('should create log file if it does not exist', () => { - logger.info('Test message'); - expect(fs.existsSync(testLogFile)).toBe(true); + it('should create rotated log file', async () => { + const message = 'Test message'; + logger.info(message); + const filePath = await waitForLogContains(message); + expect(filePath).not.toBeNull(); + expect(fs.existsSync(filePath as string)).toBe(true); }); - it('should write formatted message to file', () => { + it('should write formatted message to file', async () => { const message = 'Test info message'; logger.info(message); - const content = fs.readFileSync(testLogFile, 'utf-8'); + const filePath = await waitForLogContains(message); + expect(filePath).not.toBeNull(); + const content = fs.readFileSync(filePath as string, 'utf-8'); expect(content).toContain('[INFO]'); expect(content).toContain(message); }); - it('should log error messages', () => { + it('should log error messages', async () => { const message = 'Test error message'; logger.error(message); - const content = fs.readFileSync(testLogFile, 'utf-8'); + const filePath = await waitForLogContains(message); + expect(filePath).not.toBeNull(); + const content = fs.readFileSync(filePath as string, 'utf-8'); expect(content).toContain('[ERROR]'); expect(content).toContain(message); }); diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 806839b..bfff4bf 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,11 +1,15 @@ import fs from 'fs'; import path from 'path'; +import winston from 'winston'; +import DailyRotateFile from 'winston-daily-rotate-file'; import { getAgentDir } from './paths'; export type LogLevel = 'info' | 'warn' | 'error' | 'debug'; const LOG_WINDOW_MS = 30_000; const MAX_LOG_ENTRIES = 200; +const LOG_RETENTION = '30d'; +const LOG_MAX_SIZE = '10m'; interface LogEntry { timestamp: number; @@ -49,13 +53,14 @@ function safeStringify(obj: unknown): string { } class Logger { - private logFilePath: string; + private winstonLogger: winston.Logger; private recentLogs: LogEntry[] = []; private sentryReporter: SentryReporter | null = null; private sentryEnabled = false; constructor() { const agentDir = getAgentDir(); + if (!fs.existsSync(agentDir)) { try { fs.mkdirSync(agentDir, { recursive: true }); @@ -63,36 +68,50 @@ class Logger { console.error('Failed to create agent directory for logs', e); } } - this.logFilePath = path.join(agentDir, 'app.log'); - } - private formatMessage(level: LogLevel, message: string, ...args: unknown[]): string { - const timestamp = new Date().toISOString(); - const formattedArgs = args - .map((arg) => (typeof arg === 'object' ? safeStringify(arg) : String(arg))) - .join(' '); - return `[${timestamp}] [${level.toUpperCase()}] ${message} ${formattedArgs}`; - } + const fileFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.printf(({ timestamp, level, message }) => { + return `[${timestamp}] [${level.toUpperCase()}] ${message}`; + }), + ); - private writeToFile(formattedMessage: string) { - try { - fs.appendFileSync(this.logFilePath, formattedMessage + '\n'); - } catch (e) { - console.error('Failed to write to log file', e); - } - } + const consoleFormat = winston.format.combine( + winston.format.colorize({ all: true }), + winston.format.printf(({ level, message }) => { + return `[${level.toUpperCase()}] ${message}`; + }), + ); - private writeToConsole(level: LogLevel, message: string, ...args: unknown[]) { - const colorMap: Record = { - info: '\x1b[36m', // Cyan - warn: '\x1b[33m', // Yellow - error: '\x1b[31m', // Red - debug: '\x1b[90m', // Gray - }; - const reset = '\x1b[0m'; - const color = colorMap[level]; + const rotateTransport = new DailyRotateFile({ + filename: path.join(agentDir, 'app-%DATE%.log'), + datePattern: 'YYYY-MM-DD', + maxSize: LOG_MAX_SIZE, + maxFiles: LOG_RETENTION, + zippedArchive: false, + auditFile: path.join(agentDir, '.app-log-audit.json'), + level: 'debug', + format: fileFormat, + }); - console.log(`${color}[${level.toUpperCase()}]${reset} ${message}`, ...args); + rotateTransport.on('error', (error) => { + console.error('DailyRotateFile transport error', error); + }); + + const consoleTransport = new winston.transports.Console({ + level: 'debug', + format: consoleFormat, + }); + + consoleTransport.on('error', (error) => { + console.error('Console transport error', error); + }); + + this.winstonLogger = winston.createLogger({ + level: 'debug', + transports: [consoleTransport, rotateTransport], + exitOnError: false, + }); } private pruneLogs(now: number) { @@ -122,24 +141,35 @@ class Logger { this.sentryEnabled = enabled; } + private formatArgs(args: unknown[]): string { + return args + .map((arg) => (typeof arg === 'object' ? safeStringify(arg) : String(arg))) + .join(' '); + } + log(level: LogLevel, message: string, ...args: unknown[]) { - const formattedMessage = this.formatMessage(level, message, ...args); + const formattedArgs = this.formatArgs(args); + const mergedMessage = formattedArgs ? `${message} ${formattedArgs}` : message; const now = Date.now(); + const formattedMessage = `[${new Date(now).toISOString()}] [${level.toUpperCase()}] ${mergedMessage}`; + this.recentLogs.push({ timestamp: now, level, - message, + message: mergedMessage, formatted: formattedMessage, }); this.pruneLogs(now); - this.writeToConsole(level, message, ...args); - this.writeToFile(formattedMessage); + this.winstonLogger.log({ + level, + message: mergedMessage, + }); if (level === 'error' && this.sentryEnabled && this.sentryReporter) { this.sentryReporter({ level, - message, + message: mergedMessage, error: this.extractError(args), logs: [...this.recentLogs], });