From e2aa071ce0dffce58c791e381fb3480691f9dbb2 Mon Sep 17 00:00:00 2001 From: Abdullateef01 Date: Fri, 24 Apr 2026 16:41:23 +0100 Subject: [PATCH] chore: setup winston and morgan logging infrastructure - Integrated Winston with daily log rotation - Integrated Morgan middleware for HTTP request logging - Replaced all console statements with structured logger calls - Updated .gitignore to exclude logs/ directory --- .gitignore | 4 + package-lock.json | 297 ++++++++++++++++++- package.json | 4 +- src/index.ts | 43 +-- src/lib/socket.ts | 5 +- src/middleware/apiKeyMiddleware.ts | 3 +- src/routes/marketRates.ts | 3 +- src/routes/priceUpdates.ts | 17 +- src/routes/stats.ts | 3 +- src/services/intelligenceService.ts | 5 +- src/services/marketRate/ghsFetcher.ts | 7 +- src/services/marketRate/kesFetcher.ts | 43 +-- src/services/marketRate/marketRateService.ts | 27 +- src/services/marketRate/ngnFetcher.ts | 7 +- src/services/multiSigService.ts | 15 +- src/services/multiSigSubmissionService.ts | 27 +- src/services/sorobanEventListener.ts | 21 +- src/services/stellarService.ts | 13 +- src/services/webhook.ts | 3 +- src/utils/logger.ts | 85 ++++++ 20 files changed, 521 insertions(+), 111 deletions(-) create mode 100644 src/utils/logger.ts diff --git a/.gitignore b/.gitignore index c2658d7d..0e8354a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ node_modules/ +logs/ +.env +dist/ +*.log diff --git a/package-lock.json b/package-lock.json index 73036d7a..613dbf5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,9 @@ "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "ts-node": "^10.9.2", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "winston": "^3.19.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@types/cors": "^2.8.19", @@ -113,6 +115,15 @@ "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", "license": "Apache-2.0" }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -125,6 +136,17 @@ "node": ">=12" } }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@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/@electric-sql/pglite": { "version": "0.3.15", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", @@ -920,6 +942,16 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@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/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -1163,6 +1195,12 @@ "@types/serve-static": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@types/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", @@ -1221,6 +1259,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1531,6 +1575,52 @@ "consola": "^3.2.3" } }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/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/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/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/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/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/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1811,6 +1901,12 @@ "node": ">=14" } }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2093,6 +2189,21 @@ "is-retry-allowed": "^3.0.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/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/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -2114,6 +2225,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/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", @@ -2567,6 +2684,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/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/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -2615,6 +2744,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -2650,6 +2785,23 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "license": "MIT" }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/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/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -2744,6 +2896,15 @@ "node": "*" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/morgan": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", @@ -2872,6 +3033,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/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", @@ -2926,6 +3096,15 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/openapi-types": { "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", @@ -3332,6 +3511,20 @@ "react": "^19.2.4" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -3415,6 +3608,15 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -3728,6 +3930,15 @@ "node": ">= 0.6" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -3743,6 +3954,15 @@ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "license": "MIT" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/swagger-jsdoc": { "version": "6.2.8", "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", @@ -3808,6 +4028,12 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", @@ -3846,6 +4072,15 @@ "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", "license": "MIT" }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/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/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -3971,6 +4206,12 @@ "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", "license": "MIT" }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -4045,6 +4286,60 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index b825bb36..ba315c05 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,9 @@ "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "ts-node": "^10.9.2", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "winston": "^3.19.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@types/cors": "^2.8.19", diff --git a/src/index.ts b/src/index.ts index c345de75..df9c834a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ import { SorobanEventListener } from "./services/sorobanEventListener"; import { specs } from "./lib/swagger"; import { multiSigSubmissionService } from "./services/multiSigSubmissionService"; import { apiKeyMiddleware } from "./middleware/apiKeyMiddleware"; +import logger from "./utils/logger"; // Load environment variables dotenv.config(); @@ -32,9 +33,9 @@ for (const envVar of requiredEnvVars) { } if (missingEnvVars.length > 0) { - console.error("❌ Missing required environment variables:"); - missingEnvVars.forEach((varName) => console.error(` - ${varName}`)); - console.error( + logger.error("❌ Missing required environment variables:"); + missingEnvVars.forEach((varName) => logger.error(` - ${varName}`)); + logger.error( "\nPlease set these variables in your .env file and restart the server.", ); process.exit(1); @@ -44,7 +45,7 @@ const dashboardUrl = process.env.DASHBOARD_URL || process.env.FRONTEND_URL || "http://localhost:3000"; if (!dashboardUrl) { - console.error("❌ Missing required environment variable: DASHBOARD_URL"); + logger.error("❌ Missing required environment variable: DASHBOARD_URL"); process.exit(1); } @@ -60,7 +61,13 @@ const horizonUrl = const horizonServer = new Horizon.Server(horizonUrl); // Middleware -app.use(morgan("dev")); +app.use( + morgan(":method :url :status :res[content-length] - :response-time ms", { + stream: { + write: (message) => logger.http(message.trim()), + }, + }), +); app.use( cors({ origin: (origin, callback) => { @@ -242,7 +249,7 @@ app.use( res: express.Response, next: express.NextFunction, ) => { - console.error("Unhandled error:", err); + logger.error(`Unhandled error: ${err.message}`, { stack: err.stack }); res.status(500).json({ success: false, error: "Internal server error", @@ -263,25 +270,25 @@ const httpServer = createServer(app); initSocket(httpServer); httpServer.listen(PORT, () => { - console.log(`🌊 StellarFlow Backend running on port ${PORT}`); - console.log( + logger.info(`🌊 StellarFlow Backend running on port ${PORT}`); + logger.info( `📊 Market Rates API available at http://localhost:${PORT}/api/market-rates`, ); - console.log( + logger.info( `📚 API Documentation available at http://localhost:${PORT}/api/docs`, ); - console.log(`🏥 Health check at http://localhost:${PORT}/health`); - console.log(`🔌 Socket.io ready for dashboard connections`); + logger.info(`🏥 Health check at http://localhost:${PORT}/health`); + logger.info(`🔌 Socket.io ready for dashboard connections`); // Start Soroban event listener to track confirmed on-chain prices try { const eventListener = new SorobanEventListener(); eventListener.start().catch((err) => { - console.error("Failed to start event listener:", err); + logger.error("Failed to start event listener:", err); }); - console.log(`👂 Soroban event listener started`); + logger.info(`👂 Soroban event listener started`); } catch (err) { - console.warn( + logger.warn( "Event listener not started:", err instanceof Error ? err.message : err, ); @@ -291,13 +298,13 @@ httpServer.listen(PORT, () => { if (process.env.MULTI_SIG_ENABLED === "true") { try { multiSigSubmissionService.start().catch((err: Error) => { - console.error("Failed to start multi-sig submission service:", err); + logger.error("Failed to start multi-sig submission service:", err); }); - console.log(`🔐 Multi-Sig submission service started`); + logger.info(`🔐 Multi-Sig submission service started`); } catch (err) { - console.warn( + logger.warn( "Multi-sig submission service not started:", - err instanceof Error ? err.message : err + err instanceof Error ? err.message : err, ); } } diff --git a/src/lib/socket.ts b/src/lib/socket.ts index a15edec8..1480dae1 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -1,4 +1,5 @@ import { Server } from "socket.io"; +import logger from "../utils/logger"; let io: Server | null = null; @@ -8,9 +9,9 @@ export function initSocket(server: import("http").Server): Server { }); io.on("connection", (socket) => { - console.log(`🔌 Client connected: ${socket.id}`); + logger.info(`🔌 Client connected: ${socket.id}`); socket.on("disconnect", () => - console.log(`🔌 Client disconnected: ${socket.id}`) + logger.info(`🔌 Client disconnected: ${socket.id}`) ); }); diff --git a/src/middleware/apiKeyMiddleware.ts b/src/middleware/apiKeyMiddleware.ts index 883961c6..a6e47284 100644 --- a/src/middleware/apiKeyMiddleware.ts +++ b/src/middleware/apiKeyMiddleware.ts @@ -1,11 +1,12 @@ import { Request, Response, NextFunction } from "express"; +import logger from "../utils/logger"; export const apiKeyMiddleware = (req: Request, res: Response, next: NextFunction) => { const apiKey = req.headers["x-api-key"]; const expectedKey = process.env.API_KEY; if (!expectedKey) { - console.error("Critical: API_KEY not set in environment"); + logger.error("Critical: API_KEY not set in environment"); return res.status(500).json({ success: false, error: "Authentication configuration error", diff --git a/src/routes/marketRates.ts b/src/routes/marketRates.ts index e153f6be..ca4ef40d 100644 --- a/src/routes/marketRates.ts +++ b/src/routes/marketRates.ts @@ -2,6 +2,7 @@ import { Router } from "express"; import { getRate, getAllRates } from "../controllers/marketRatesController"; import { MarketRateService } from "../services/marketRate"; +import logger from "../utils/logger"; const marketRateService = new MarketRateService(); @@ -109,7 +110,7 @@ router.get("/latest", async (req, res) => { }); } } catch (error) { - console.error("Error fetching latest prices:", error); + logger.error("Error fetching latest prices:", error); res.status(500).json({ success: false, diff --git a/src/routes/priceUpdates.ts b/src/routes/priceUpdates.ts index c97f5c49..749bc97b 100644 --- a/src/routes/priceUpdates.ts +++ b/src/routes/priceUpdates.ts @@ -1,5 +1,6 @@ import express, { Request, Response } from "express"; import { multiSigService, SignaturePayload } from "../services/multiSigService"; +import logger from "../utils/logger"; const router = express.Router(); @@ -32,7 +33,7 @@ router.post("/multi-sig/request", async (req: Request, res: Response) => { data: signatureRequest, }); } catch (error) { - console.error("[API] Multi-sig request creation failed:", error); + logger.error("[API] Multi-sig request creation failed:", error); res.status(500).json({ success: false, error: String(error), @@ -93,7 +94,7 @@ router.post("/sign", async (req: Request, res: Response) => { }, }); } catch (error) { - console.error("[API] Signature creation failed:", error); + logger.error("[API] Signature creation failed:", error); res.status(400).json({ success: false, error: String(error), @@ -132,7 +133,7 @@ router.post("/multi-sig/:multiSigPriceId/request-signature", async (req: Request res.json({ success: true }); } catch (error) { - console.error("[API] Remote signature request failed:", error); + logger.error("[API] Remote signature request failed:", error); res.status(500).json({ success: false, error: String(error), @@ -184,7 +185,7 @@ router.get("/multi-sig/:multiSigPriceId/status", async (req: Request, res: Respo }, }); } catch (error) { - console.error("[API] Multi-sig status fetch failed:", error); + logger.error("[API] Multi-sig status fetch failed:", error); res.status(500).json({ success: false, error: String(error), @@ -215,7 +216,7 @@ router.get("/multi-sig/pending", async (req: Request, res: Response) => { })), }); } catch (error) { - console.error("[API] Pending multi-sig fetch failed:", error); + logger.error("[API] Pending multi-sig fetch failed:", error); res.status(500).json({ success: false, error: String(error), @@ -275,7 +276,7 @@ router.get("/multi-sig/:multiSigPriceId/signatures", async (req: Request, res: R }, }); } catch (error) { - console.error("[API] Signature fetch failed:", error); + logger.error("[API] Signature fetch failed:", error); res.status(500).json({ success: false, error: String(error), @@ -307,7 +308,7 @@ router.post("/multi-sig/:multiSigPriceId/record-submission", async (req: Request res.json({ success: true }); } catch (error) { - console.error("[API] Submission recording failed:", error); + logger.error("[API] Submission recording failed:", error); res.status(500).json({ success: false, error: String(error), @@ -328,7 +329,7 @@ router.get("/multi-sig/signer-info", async (req: Request, res: Response) => { data: signerInfo, }); } catch (error) { - console.error("[API] Signer info fetch failed:", error); + logger.error("[API] Signer info fetch failed:", error); res.status(500).json({ success: false, error: String(error), diff --git a/src/routes/stats.ts b/src/routes/stats.ts index 8e30acec..b13b2c96 100644 --- a/src/routes/stats.ts +++ b/src/routes/stats.ts @@ -1,5 +1,6 @@ import { Router } from "express"; import prisma from "../lib/prisma"; +import logger from "../utils/logger"; const router = Router(); @@ -128,7 +129,7 @@ router.get("/volume", async (req, res) => { data: volumeStats, }); } catch (error) { - console.error("Error fetching volume stats:", error); + logger.error("Error fetching volume stats:", error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : "Internal server error", diff --git a/src/services/intelligenceService.ts b/src/services/intelligenceService.ts index 1d8b352c..8dd652df 100644 --- a/src/services/intelligenceService.ts +++ b/src/services/intelligenceService.ts @@ -1,4 +1,5 @@ import prisma from "../lib/prisma"; +import logger from "../utils/logger"; export class IntelligenceService { /** @@ -59,7 +60,7 @@ export class IntelligenceService { return `${sign}${changePercent.toFixed(1)}%`; } catch (error) { - console.error(`Error calculating 24h change for ${asset}:`, error); + logger.error(`Error calculating 24h change for ${asset}:`, error); return "0.0%"; } } @@ -99,7 +100,7 @@ export class IntelligenceService { return staleCurrencies; } catch (error) { - console.error("Error detecting stale currencies:", error); + logger.error("Error detecting stale currencies:", error); return []; } } diff --git a/src/services/marketRate/ghsFetcher.ts b/src/services/marketRate/ghsFetcher.ts index 74f0d910..7b66045f 100644 --- a/src/services/marketRate/ghsFetcher.ts +++ b/src/services/marketRate/ghsFetcher.ts @@ -1,5 +1,6 @@ import axios from "axios"; import { MarketRateFetcher, MarketRate, calculateMedian, filterOutliers, SourceTrustLevel, calculateWeightedAverage } from "./types"; +import logger from "../../utils/logger"; import { errorTracker } from "../errorTracker"; import { webhookService } from "../webhook"; @@ -70,7 +71,7 @@ export class GHSRateFetcher implements MarketRateFetcher { errorTracker.trackSuccess("GHS-price-fetch"); } } catch (error) { - console.debug("CoinGecko direct GHS price failed"); + logger.debug("CoinGecko direct GHS price failed"); } // Strategy 2: CoinGecko XLM/USD + ExchangeRate API @@ -127,7 +128,7 @@ export class GHSRateFetcher implements MarketRateFetcher { } } } catch (error) { - console.debug("CoinGecko + ExchangeRate API failed"); + logger.debug("CoinGecko + ExchangeRate API failed"); } // Strategy 3: Try alternative XLM pricing source @@ -173,7 +174,7 @@ export class GHSRateFetcher implements MarketRateFetcher { } } } catch (error) { - console.debug("Alternative XLM pricing source failed"); + logger.debug("Alternative XLM pricing source failed"); } // If we have prices, calculate median diff --git a/src/services/marketRate/kesFetcher.ts b/src/services/marketRate/kesFetcher.ts index 3512ad2c..1bf11fca 100644 --- a/src/services/marketRate/kesFetcher.ts +++ b/src/services/marketRate/kesFetcher.ts @@ -9,6 +9,7 @@ import { SourceTrustLevel, calculateWeightedAverage, } from "./types"; +import logger from "../../utils/logger"; /** * Binance Ticker Response Interface @@ -177,7 +178,7 @@ async function withRetry( config.maxDelayMs, ); - console.debug( + logger.debug( `Retry attempt ${attempt}/${config.maxAttempts} for ${operationName} ` + `after ${delay}ms delay. Error: ${lastError.message}`, ); @@ -286,13 +287,13 @@ export class KESRateFetcher implements MarketRateFetcher { ); if (binanceRate) { - console.info(`✅ KES rate fetched from Binance: ${binanceRate.rate}`); + logger.info(`✅ KES rate fetched from Binance: ${binanceRate.rate}`); return binanceRate; } } catch (error) { const errorMsg = error instanceof Error ? error.message : "Unknown Binance error"; - console.warn(`⚠️ Binance API failed: ${errorMsg}`); + logger.warn(`⚠️ Binance API failed: ${errorMsg}`); errors.push({ source: "Binance API", message: errorMsg, @@ -304,13 +305,13 @@ export class KESRateFetcher implements MarketRateFetcher { try { const cbkRate = await this.fetchFromCBK(); if (cbkRate) { - console.info(`✅ KES rate fetched from CBK: ${cbkRate.rate}`); + logger.info(`✅ KES rate fetched from CBK: ${cbkRate.rate}`); return cbkRate; } } catch (error) { const errorMsg = error instanceof Error ? error.message : "Unknown CBK error"; - console.warn(`⚠️ Central Bank of Kenya API failed: ${errorMsg}`); + logger.warn(`⚠️ Central Bank of Kenya API failed: ${errorMsg}`); errors.push({ source: "Central Bank of Kenya", message: errorMsg, @@ -327,7 +328,7 @@ export class KESRateFetcher implements MarketRateFetcher { source.name, ); if (rate) { - console.info(`✅ KES rate fetched from ${source.name}: ${rate.rate}`); + logger.info(`✅ KES rate fetched from ${source.name}: ${rate.rate}`); return rate; } } catch (error) { @@ -335,7 +336,7 @@ export class KESRateFetcher implements MarketRateFetcher { error instanceof Error ? error.message : `Unknown ${source.name} error`; - console.warn(`⚠️ ${source.name} failed: ${errorMsg}`); + logger.warn(`⚠️ ${source.name} failed: ${errorMsg}`); errors.push({ source: source.name, message: errorMsg, @@ -346,7 +347,7 @@ export class KESRateFetcher implements MarketRateFetcher { // All sources failed - throw comprehensive error const errorMessage = this.buildErrorMessage(errors); - console.error(`❌ All KES rate sources failed: ${errorMessage}`); + logger.error(`❌ All KES rate sources failed: ${errorMessage}`); throw new Error(errorMessage); } @@ -378,7 +379,7 @@ export class KESRateFetcher implements MarketRateFetcher { }); } } catch (error) { - console.debug("Direct XLMKES pair not available"); + logger.debug("Direct XLMKES pair not available"); } // Strategy 2: Try Binance P2P API @@ -393,7 +394,7 @@ export class KESRateFetcher implements MarketRateFetcher { }); } } catch (error) { - console.debug("Binance P2P API not available"); + logger.debug("Binance P2P API not available"); } // Strategy 3: XLMUSDT × KES/USD calculation @@ -408,7 +409,7 @@ export class KESRateFetcher implements MarketRateFetcher { }); } } catch (error) { - console.debug("XLMUSDT pair not available"); + logger.debug("XLMUSDT pair not available"); } // If no prices were collected, return null @@ -536,7 +537,7 @@ export class KESRateFetcher implements MarketRateFetcher { private async fetchFromCBK(): Promise { const cbkSource = RATE_SOURCES[2]; if (!cbkSource) { - console.warn("Central Bank of Kenya source not configured"); + logger.warn("Central Bank of Kenya source not configured"); return null; } @@ -606,7 +607,7 @@ export class KESRateFetcher implements MarketRateFetcher { if (axiosError.response) { // Server responded with error status - console.warn( + logger.warn( `${source} returned status ${axiosError.response.status}: ` + `${axiosError.response.statusText}`, ); @@ -615,20 +616,20 @@ export class KESRateFetcher implements MarketRateFetcher { axiosError.code === "ETIMEDOUT" ) { // Request timeout - console.warn(`${source} request timed out`); + logger.warn(`${source} request timed out`); } else if (axiosError.code === "ERR_NETWORK") { // Network error - console.warn(`${source} network error - service may be down`); + logger.warn(`${source} network error - service may be down`); } else if (axiosError.message.includes("Network Error")) { // CORS or network issue - console.warn( + logger.warn( `${source} network error - check connectivity or CORS settings`, ); } else { - console.warn(`${source} error: ${axiosError.message}`); + logger.warn(`${source} error: ${axiosError.message}`); } } else { - console.warn(`${source} unexpected error:`, error); + logger.warn(`${source} unexpected error:`, error); } } @@ -657,12 +658,12 @@ export class KESRateFetcher implements MarketRateFetcher { ); const healthy = testRate !== null && testRate.rate > 0; - console.debug( + logger.debug( `Health check result: ${healthy ? "HEALTHY" : "UNHEALTHY"}`, ); return healthy; } catch (error) { - console.warn( + logger.warn( "Health check failed:", error instanceof Error ? error.message : "Unknown error", ); @@ -685,6 +686,6 @@ export class KESRateFetcher implements MarketRateFetcher { */ resetCircuitBreaker(): void { this.circuitBreaker.reset(); - console.info("Circuit breaker reset"); + logger.info("Circuit breaker reset"); } } diff --git a/src/services/marketRate/marketRateService.ts b/src/services/marketRate/marketRateService.ts index b94f4eb9..d3daf08e 100644 --- a/src/services/marketRate/marketRateService.ts +++ b/src/services/marketRate/marketRateService.ts @@ -12,6 +12,7 @@ import { multiSigService } from "../multiSigService"; import { getIO } from "../../lib/socket"; import prisma from "../../lib/prisma"; import dotenv from "dotenv"; +import logger from "../../utils/logger"; dotenv.config(); @@ -42,7 +43,7 @@ export class MarketRateService { } if (this.multiSigEnabled) { - console.info( + logger.info( `[MarketRateService] Multi-Sig mode ENABLED with ${this.remoteOracleServers.length} remote servers` ); } @@ -144,7 +145,7 @@ export class MarketRateService { if (this.multiSigEnabled) { // Multi-sig workflow: create request and collect signatures - console.info( + logger.info( `[MarketRateService] Starting multi-sig workflow for ${normalizedCurrency} rate ${rate.rate}` ); @@ -159,11 +160,11 @@ export class MarketRateService { // Sign locally first try { await multiSigService.signMultiSigPrice(signatureRequest.multiSigPriceId); - console.info( + logger.info( `[MarketRateService] Local signature added for multi-sig request ${signatureRequest.multiSigPriceId}` ); } catch (error) { - console.error( + logger.error( `[MarketRateService] Failed to sign locally:`, error ); @@ -175,7 +176,7 @@ export class MarketRateService { signatureRequest.multiSigPriceId, memoId ).catch((err) => { - console.error( + logger.error( `[MarketRateService] Error requesting remote signatures:`, err ); @@ -198,18 +199,18 @@ export class MarketRateService { memoId, txHash ); - console.info( + logger.info( `[MarketRateService] Single-sig price update submitted for ${normalizedCurrency}` ); } } catch (stellarError) { - console.error( + logger.error( "Failed to submit price update to Stellar network:", stellarError ); } } else { - console.warn( + logger.warn( `Manual review required for ${normalizedCurrency} rate ${rate.rate}. Skipping contract submission.` ); } @@ -238,7 +239,7 @@ export class MarketRateService { }, }); } catch (dbError) { - console.error("Failed to persist price history:", dbError); + logger.error("Failed to persist price history:", dbError); } // Broadcast fresh price to all connected dashboard clients @@ -420,7 +421,7 @@ export class MarketRateService { multiSigPriceId: number, memoId: string ): Promise { - console.info( + logger.info( `[MarketRateService] Requesting signatures from ${this.remoteOracleServers.length} remote servers for multi-sig ${multiSigPriceId}` ); @@ -435,16 +436,16 @@ export class MarketRateService { results.forEach((result, index) => { if (result.status === "fulfilled") { if (result.value.success) { - console.info( + logger.info( `[MarketRateService] ✅ Signature request sent to ${this.remoteOracleServers[index]}` ); } else { - console.warn( + logger.warn( `[MarketRateService] ⚠️ Signature request failed for ${this.remoteOracleServers[index]}: ${result.value.error}` ); } } else { - console.error( + logger.error( `[MarketRateService] ❌ Error requesting signature from ${this.remoteOracleServers[index]}:`, result.reason ); diff --git a/src/services/marketRate/ngnFetcher.ts b/src/services/marketRate/ngnFetcher.ts index 6958d2c0..4e78e3bb 100644 --- a/src/services/marketRate/ngnFetcher.ts +++ b/src/services/marketRate/ngnFetcher.ts @@ -1,5 +1,6 @@ import axios from "axios"; import { MarketRateFetcher, MarketRate, calculateMedian, filterOutliers, SourceTrustLevel, calculateWeightedAverage } from "./types"; +import logger from "../../utils/logger"; type CoinGeckoPriceResponse = { stellar?: { @@ -158,7 +159,7 @@ export class NGNRateFetcher implements MarketRateFetcher { } } } catch { - console.debug("VTpass + CoinGecko XLM/USD failed"); + logger.debug("VTpass + CoinGecko XLM/USD failed"); } // Strategy 2: CoinGecko direct XLM/NGN @@ -191,7 +192,7 @@ export class NGNRateFetcher implements MarketRateFetcher { }); } } catch { - console.debug("CoinGecko direct NGN failed"); + logger.debug("CoinGecko direct NGN failed"); } // Strategy 3: CoinGecko XLM/USD × USD/NGN (open.er-api) @@ -245,7 +246,7 @@ export class NGNRateFetcher implements MarketRateFetcher { } } } catch { - console.debug("CoinGecko + ExchangeRate API (NGN) failed"); + logger.debug("CoinGecko + ExchangeRate API (NGN) failed"); } if (prices.length > 0) { diff --git a/src/services/multiSigService.ts b/src/services/multiSigService.ts index 93d989a5..2899369c 100644 --- a/src/services/multiSigService.ts +++ b/src/services/multiSigService.ts @@ -1,6 +1,7 @@ import prisma from "../lib/prisma"; import { Keypair } from "@stellar/stellar-sdk"; import dotenv from "dotenv"; +import logger from "../utils/logger"; dotenv.config(); @@ -76,7 +77,7 @@ export class MultiSigService { }, }); - console.info( + logger.info( `[MultiSig] Created signature request ${created.id} for ${currency} rate ${rate}` ); @@ -156,7 +157,7 @@ export class MultiSigService { }, }); - console.info( + logger.info( `[MultiSig] Added signature ${updated.collectedSignatures}/${updated.requiredSignatures} for MultiSigPrice ${multiSigPriceId}` ); @@ -238,7 +239,7 @@ export class MultiSigService { }, }); - console.info( + logger.info( `[MultiSig] Added remote signature ${updated.collectedSignatures}/${updated.requiredSignatures} for MultiSigPrice ${multiSigPriceId}` ); @@ -250,7 +251,7 @@ export class MultiSigService { return { success: true }; } catch (error) { - console.error( + logger.error( `[MultiSig] Failed to request signature from ${remoteServerUrl}:`, error ); @@ -317,7 +318,7 @@ export class MultiSigService { }); if (result.count > 0) { - console.warn( + logger.warn( `[MultiSig] Expired ${result.count} multi-sig price requests` ); } @@ -337,7 +338,7 @@ export class MultiSigService { }, }); - console.info( + logger.info( `[MultiSig] MultiSigPrice ${multiSigPriceId} is now APPROVED (all signatures collected)` ); } @@ -372,7 +373,7 @@ export class MultiSigService { }, }); - console.info( + logger.info( `[MultiSig] MultiSigPrice ${multiSigPriceId} submitted to Stellar - TxHash: ${stellarTxHash}` ); } diff --git a/src/services/multiSigSubmissionService.ts b/src/services/multiSigSubmissionService.ts index cb4d2676..20b81b84 100644 --- a/src/services/multiSigSubmissionService.ts +++ b/src/services/multiSigSubmissionService.ts @@ -3,6 +3,7 @@ import { StellarService } from "./stellarService"; import { priceReviewService } from "./priceReviewService"; import prisma from "../lib/prisma"; import dotenv from "dotenv"; +import logger from "../utils/logger"; dotenv.config(); @@ -28,14 +29,14 @@ export class MultiSigSubmissionService { */ async start(): Promise { if (this.isRunning) { - console.warn( + logger.warn( "[MultiSigSubmissionService] Service is already running" ); return; } this.isRunning = true; - console.info( + logger.info( `[MultiSigSubmissionService] Started with ${this.pollIntervalMs}ms poll interval` ); @@ -45,7 +46,7 @@ export class MultiSigSubmissionService { // Start periodic polling this.pollTimer = setInterval(() => { this.checkAndSubmitApprovedPrices().catch((err) => { - console.error( + logger.error( "[MultiSigSubmissionService] Polling error:", err ); @@ -62,7 +63,7 @@ export class MultiSigSubmissionService { this.pollTimer = null; } this.isRunning = false; - console.info("[MultiSigSubmissionService] Stopped"); + logger.info("[MultiSigSubmissionService] Stopped"); } /** @@ -91,7 +92,7 @@ export class MultiSigSubmissionService { return; // Nothing to do } - console.info( + logger.info( `[MultiSigSubmissionService] Found ${approvedPrices.length} approved prices to submit` ); @@ -100,7 +101,7 @@ export class MultiSigSubmissionService { try { await this.submitApprovedPrice(multiSigPrice); } catch (error) { - console.error( + logger.error( `[MultiSigSubmissionService] Failed to submit multi-sig price ${multiSigPrice.id}:`, error ); @@ -108,7 +109,7 @@ export class MultiSigSubmissionService { } } } catch (error) { - console.error( + logger.error( "[MultiSigSubmissionService] Error checking approved prices:", error ); @@ -127,13 +128,13 @@ export class MultiSigSubmissionService { })); if (signatures.length === 0) { - console.warn( + logger.warn( `[MultiSigSubmissionService] No signatures found for multi-sig price ${multiSigPrice.id}` ); return; } - console.info( + logger.info( `[MultiSigSubmissionService] Submitting multi-sig price ${multiSigPrice.id} (${multiSigPrice.currency} @ ${multiSigPrice.rate}) with ${signatures.length} signatures` ); @@ -160,11 +161,11 @@ export class MultiSigSubmissionService { txHash ); - console.info( + logger.info( `[MultiSigSubmissionService] ✅ Successfully submitted multi-sig price ${multiSigPrice.id} - TxHash: ${txHash}` ); } catch (error) { - console.error( + logger.error( `[MultiSigSubmissionService] Error submitting multi-sig price ${multiSigPrice.id}:`, error ); @@ -180,13 +181,13 @@ export class MultiSigSubmissionService { try { const count = await multiSigService.cleanupExpiredRequests(); if (count > 0) { - console.info( + logger.info( `[MultiSigSubmissionService] Cleaned up ${count} expired multi-sig requests` ); } return count; } catch (error) { - console.error( + logger.error( "[MultiSigSubmissionService] Error during cleanup:", error ); diff --git a/src/services/sorobanEventListener.ts b/src/services/sorobanEventListener.ts index d4ed65d5..c5312d54 100644 --- a/src/services/sorobanEventListener.ts +++ b/src/services/sorobanEventListener.ts @@ -4,6 +4,7 @@ import type { OnChainPrice } from "@prisma/client"; import prisma from "../lib/prisma"; import { getIO } from "../lib/socket"; import dotenv from "dotenv"; +import logger from "../utils/logger"; dotenv.config(); @@ -47,12 +48,12 @@ export class SorobanEventListener { async start(): Promise { if (this.isRunning) { - console.warn("SorobanEventListener is already running"); + logger.warn("SorobanEventListener is already running"); return; } this.isRunning = true; - console.log( + logger.info( `[EventListener] Starting listener for account ${this.oraclePublicKey}` ); @@ -62,7 +63,7 @@ export class SorobanEventListener { }); if (lastRecord) { this.lastProcessedLedger = lastRecord.ledgerSeq; - console.log( + logger.info( `[EventListener] Resuming from ledger ${this.lastProcessedLedger}` ); } @@ -73,7 +74,7 @@ export class SorobanEventListener { // Start periodic polling this.pollTimer = setInterval(() => { this.pollTransactions().catch((err) => { - console.error("[EventListener] Poll error:", err); + logger.error("[EventListener] Poll error:", err); }); }, this.pollIntervalMs); } @@ -84,7 +85,7 @@ export class SorobanEventListener { this.pollTimer = null; } this.isRunning = false; - console.log("[EventListener] Stopped"); + logger.info("[EventListener] Stopped"); } private async pollTransactions(): Promise { @@ -133,7 +134,7 @@ export class SorobanEventListener { error instanceof Error && error.message.includes("status code 404") ) { - console.log("[EventListener] No transactions found for oracle account"); + logger.info("[EventListener] No transactions found for oracle account"); return; } throw error; @@ -183,7 +184,7 @@ export class SorobanEventListener { const rate = parseFloat(valueStr); if (isNaN(rate)) { - console.warn( + logger.warn( `[EventListener] Invalid rate value for ${currency}: ${valueStr}` ); continue; @@ -199,7 +200,7 @@ export class SorobanEventListener { }); } } catch (error) { - console.error( + logger.error( `[EventListener] Error parsing operations for tx ${tx.hash}:`, error ); @@ -228,11 +229,11 @@ export class SorobanEventListener { confirmedAt: price.confirmedAt, }, }); - console.log( + logger.info( `[EventListener] Saved confirmed price: ${price.currency} = ${price.rate} (tx: ${price.txHash.substring(0, 8)}...)` ); } catch (error) { - console.error( + logger.error( `[EventListener] Error saving price for ${price.currency}:`, error ); diff --git a/src/services/stellarService.ts b/src/services/stellarService.ts index 1d3c273a..7db97d35 100644 --- a/src/services/stellarService.ts +++ b/src/services/stellarService.ts @@ -9,6 +9,7 @@ import { xdr, } from "@stellar/stellar-sdk"; import dotenv from "dotenv"; +import logger from "../utils/logger"; dotenv.config(); @@ -78,7 +79,8 @@ export class StellarService { baseFee ); - console.info(`✅ Price update for ${currency} confirmed. Hash: ${result.hash}`); + + logger.info(`✅ Price update for ${currency} confirmed. Hash: ${result.hash}`); return result.hash; } @@ -119,7 +121,8 @@ export class StellarService { baseFee ); - console.info(`✅ Multi-signed price update for ${currency} confirmed. Hash: ${result.hash}`); + + logger.info(`✅ Multi-signed price update for ${currency} confirmed. Hash: ${result.hash}`); return result.hash; } @@ -152,7 +155,7 @@ export class StellarService { const isStuck = this.isStuckError(error); if (isStuck && attempt <= maxRetries) { - console.warn(`⚠️ Transaction stuck or fee too low (Attempt ${attempt}). Bumping fee and retrying in ${this.RETRY_DELAY_MS}ms...`); + logger.warn(`⚠️ Transaction stuck or fee too low (Attempt ${attempt}). Bumping fee and retrying in ${this.RETRY_DELAY_MS}ms...`); await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY_MS)); continue; } @@ -217,7 +220,7 @@ export class StellarService { transaction.signatures.push(decoratedSignature); } catch (error) { - console.error( + logger.error( `[StellarService] Failed to add signature for ${sig.signerPublicKey}:`, error ); @@ -232,7 +235,7 @@ export class StellarService { const isStuck = this.isStuckError(error); if (isStuck && attempt <= maxRetries) { - console.warn(`⚠️ Multi-sig transaction stuck or fee too low (Attempt ${attempt}). Bumping fee and retrying in ${this.RETRY_DELAY_MS}ms...`); + logger.warn(`⚠️ Multi-sig transaction stuck or fee too low (Attempt ${attempt}). Bumping fee and retrying in ${this.RETRY_DELAY_MS}ms...`); await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY_MS)); continue; } diff --git a/src/services/webhook.ts b/src/services/webhook.ts index f31b8ba1..c1df867e 100644 --- a/src/services/webhook.ts +++ b/src/services/webhook.ts @@ -1,4 +1,5 @@ import axios from "axios"; +import logger from "../utils/logger"; export class WebhookService { private webhookUrl: string | undefined; @@ -47,7 +48,7 @@ export class WebhookService { timeout: 5000, }); } catch (error) { - console.error("Failed to send webhook notification:", error); + logger.error("Failed to send webhook notification:", error); } } diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 00000000..a9ed1e2a --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,85 @@ +import winston from "winston"; +import DailyRotateFile from "winston-daily-rotate-file"; +import path from "path"; + +// Define log levels +const levels = { + error: 0, + warn: 1, + info: 2, + http: 3, + debug: 4, +}; + +// Define level based on environment +const level = () => { + const env = process.env.NODE_ENV || "development"; + const isDevelopment = env === "development"; + return isDevelopment ? "debug" : "warn"; +}; + +// Define colors for each level +const colors = { + error: "red", + warn: "yellow", + info: "green", + http: "magenta", + debug: "white", +}; + +// Tell winston that we want to link the colors +winston.addColors(colors); + +// Chose the aspect of the log customizing the log format +const format = winston.format.combine( + // Add the message timestamp with the preferred format + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), + // Tell Winston that the logs must be colored + winston.format.colorize({ all: true }), + // Define the format of the message showing the timestamp, the level and the message + winston.format.printf( + (info) => `${info.timestamp} ${info.level}: ${info.message}`, + ), +); + +// Define which transports the logger must use to print out messages. +const transports = [ + // Allow the use the console to print the messages + new winston.transports.Console(), + // Allow to print all the error level messages inside the error.log file + new DailyRotateFile({ + filename: "logs/error-%DATE%.log", + datePattern: "YYYY-MM-DD", + zippedArchive: true, + maxSize: "20m", + maxFiles: "14d", + level: "error", + format: winston.format.combine( + winston.format.uncolorize(), + winston.format.json(), + ), + }), + // Allow to print all the error level messages inside the combined.log file + new DailyRotateFile({ + filename: "logs/combined-%DATE%.log", + datePattern: "YYYY-MM-DD", + zippedArchive: true, + maxSize: "20m", + maxFiles: "14d", + format: winston.format.combine( + winston.format.uncolorize(), + winston.format.json(), + ), + }), +]; + +// Create the logger instance that has to be exported +// and used to log messages. +const logger = winston.createLogger({ + level: level(), + levels, + format, + transports, +}); + +export default logger;