refactor: migrate to winston and enable daily rotated app logs (#71)

This commit is contained in:
vin authored and GitHub committed 2026-02-06 21:10:54 +08:00
1 parent ed94abf0d6
commit 2ae2216f1e
17 files changed
+414 -92

No files matched your search

+260 -1
View File
@@ -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",
+4 -2
View File
@@ -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"
}
+1 -1
View File
@@ -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;
}
+2 -1
View File
@@ -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;
}
}),
+3 -2
View File
@@ -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<boolean> => {
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<boolean> => {
try {
return await stopNestServer();
} catch (e) {
console.error('Failed to stop gateway:', e);
logger.error('Failed to stop gateway:', e);
return false;
}
};
+1 -1
View File
@@ -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<Record<never, never>> = new RPCHandler(router);
+2 -1
View File
@@ -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;
}
});
+2 -1
View File
@@ -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;
}
+3 -2
View File
@@ -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(
+6 -9
View File
@@ -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<string, string> = {
// 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'];
+10 -9
View File
@@ -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) {
+3 -2
View File
@@ -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})`,
);
}
+3 -2
View File
@@ -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;
}
+2 -2
View File
@@ -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);
}
});
+5 -4
View File
@@ -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})`,
);
}
+45 -20
View File
@@ -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);
});
+62 -32
View File
@@ -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<LogLevel, string> = {
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],
});