From 673671324c474810a816089534a3cc43df20bbf6 Mon Sep 17 00:00:00 2001 From: tmcollins4 Date: Mon, 6 Apr 2026 14:26:47 -0400 Subject: [PATCH] centralized logging for BWS and BCN. --- packages/bitcore-logging/package-lock.json | 1222 +++++++++++++++++ packages/bitcore-logging/package.json | 47 + packages/bitcore-logging/src/create.ts | 55 + packages/bitcore-logging/src/decorators.ts | 100 ++ packages/bitcore-logging/src/formatters.ts | 40 + packages/bitcore-logging/src/index.ts | 12 + packages/bitcore-logging/src/timestamp.ts | 24 + packages/bitcore-logging/src/types.ts | 8 + packages/bitcore-logging/test/create.test.ts | 78 ++ .../bitcore-logging/test/decorators.test.ts | 145 ++ .../bitcore-logging/test/formatters.test.ts | 56 + .../bitcore-logging/test/timestamp.test.ts | 31 + packages/bitcore-logging/tsconfig.json | 35 + packages/bitcore-logging/tsconfig.prod.json | 6 + packages/bitcore-node/package.json | 5 +- .../bitcore-node/src/decorators/Loggify.ts | 93 +- packages/bitcore-node/src/logger.ts | 83 +- packages/bitcore-wallet-service/package.json | 4 +- .../bitcore-wallet-service/src/lib/logger.ts | 79 +- 19 files changed, 1881 insertions(+), 242 deletions(-) create mode 100644 packages/bitcore-logging/package-lock.json create mode 100644 packages/bitcore-logging/package.json create mode 100644 packages/bitcore-logging/src/create.ts create mode 100644 packages/bitcore-logging/src/decorators.ts create mode 100644 packages/bitcore-logging/src/formatters.ts create mode 100644 packages/bitcore-logging/src/index.ts create mode 100644 packages/bitcore-logging/src/timestamp.ts create mode 100644 packages/bitcore-logging/src/types.ts create mode 100644 packages/bitcore-logging/test/create.test.ts create mode 100644 packages/bitcore-logging/test/decorators.test.ts create mode 100644 packages/bitcore-logging/test/formatters.test.ts create mode 100644 packages/bitcore-logging/test/timestamp.test.ts create mode 100644 packages/bitcore-logging/tsconfig.json create mode 100644 packages/bitcore-logging/tsconfig.prod.json diff --git a/packages/bitcore-logging/package-lock.json b/packages/bitcore-logging/package-lock.json new file mode 100644 index 00000000000..e7467bf2116 --- /dev/null +++ b/packages/bitcore-logging/package-lock.json @@ -0,0 +1,1222 @@ +{ + "name": "@bitpay-labs/bitcore-logging", + "version": "11.6.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@bitpay-labs/bitcore-logging", + "version": "11.6.6", + "license": "MIT", + "dependencies": { + "winston": "3.3.3" + }, + "devDependencies": { + "@types/chai": "^5.2.2", + "@types/mocha": "^5.2.0", + "@types/node": "^22.10.10", + "@types/winston": "2.4.4", + "chai": "^5.2.0", + "mocha": "^5.2.0", + "tsx": "^4.21.0", + "typescript": "^5.7.3" + } + }, + "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/@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/@esbuild/aix-ppc64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.5.tgz", + "integrity": "sha512-nGsF/4C7uzUj+Nj/4J+Zt0bYQ6bz33Phz8Lb2N80Mti1HjGclTJdXZ+9APC4kLvONbjxN1zfvYNd8FEcbBK/MQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.5.tgz", + "integrity": "sha512-Cv781jd0Rfj/paoNrul1/r4G0HLvuFKYh7C9uHZ2Pl8YXstzvCyyeWENTFR9qFnRzNMCjXmsulZuvosDg10Mog==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.5.tgz", + "integrity": "sha512-Oeghq+XFgh1pUGd1YKs4DDoxzxkoUkvko+T/IVKwlghKLvvjbGFB3ek8VEDBmNvqhwuL0CQS3cExdzpmUyIrgA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.5.tgz", + "integrity": "sha512-nQD7lspbzerlmtNOxYMFAGmhxgzn8Z7m9jgFkh6kpkjsAhZee1w8tJW3ZlW+N9iRePz0oPUDrYrXidCPSImD0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.5.tgz", + "integrity": "sha512-I+Ya/MgC6rr8oRWGRDF3BXDfP8K1BVUggHqN6VI2lUZLdDi1IM1v2cy0e3lCPbP+pVcK3Tv8cgUhHse1kaNZZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.5.tgz", + "integrity": "sha512-MCjQUtC8wWJn/pIPM7vQaO69BFgwPD1jriEdqwTCKzWjGgkMbcg+M5HzrOhPhuYe1AJjXlHmD142KQf+jnYj8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.5.tgz", + "integrity": "sha512-X6xVS+goSH0UelYXnuf4GHLwpOdc8rgK/zai+dKzBMnncw7BTQIwquOodE7EKvY2UVUetSqyAfyZC1D+oqLQtg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.5.tgz", + "integrity": "sha512-233X1FGo3a8x1ekLB6XT69LfZ83vqz+9z3TSEQCTYfMNY880A97nr81KbPcAMl9rmOFp11wO0dP+eB18KU/Ucg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.5.tgz", + "integrity": "sha512-0wkVrYHG4sdCCN/bcwQ7yYMXACkaHc3UFeaEOwSVW6e5RycMageYAFv+JS2bKLwHyeKVUvtoVH+5/RHq0fgeFw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.5.tgz", + "integrity": "sha512-euKkilsNOv7x/M1NKsx5znyprbpsRFIzTV6lWziqJch7yWYayfLtZzDxDTl+LSQDJYAjd9TVb/Kt5UKIrj2e4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.5.tgz", + "integrity": "sha512-hVRQX4+P3MS36NxOy24v/Cdsimy/5HYePw+tmPqnNN1fxV0bPrFWR6TMqwXPwoTM2VzbkA+4lbHWUKDd5ZDA/w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.5.tgz", + "integrity": "sha512-mKqqRuOPALI8nDzhOBmIS0INvZOOFGGg5n1osGIXAx8oersceEbKd4t1ACNTHM3sJBXGFAlEgqM+svzjPot+ZQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.5.tgz", + "integrity": "sha512-EE/QXH9IyaAj1qeuIV5+/GZkBTipgGO782Ff7Um3vPS9cvLhJJeATy4Ggxikz2inZ46KByamMn6GqtqyVjhenA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.5.tgz", + "integrity": "sha512-0V2iF1RGxBf1b7/BjurA5jfkl7PtySjom1r6xOK2q9KWw/XCpAdtB6KNMO+9xx69yYfSCRR9FE0TyKfHA2eQMw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.5.tgz", + "integrity": "sha512-rYxThBx6G9HN6tFNuvB/vykeLi4VDsm5hE5pVwzqbAjZEARQrWu3noZSfbEnPZ/CRXP3271GyFk/49up2W190g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.5.tgz", + "integrity": "sha512-uEP2q/4qgd8goEUc4QIdU/1P2NmEtZ/zX5u3OpLlCGhJIuBIv0s0wr7TB2nBrd3/A5XIdEkkS5ZLF0ULuvaaYQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.5.tgz", + "integrity": "sha512-+Gq47Wqq6PLOOZuBzVSII2//9yyHNKZLuwfzCemqexqOQCSz0zy0O26kIzyp9EMNMK+nZ0tFHBZrCeVUuMs/ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.5.tgz", + "integrity": "sha512-3F/5EG8VHfN/I+W5cO1/SV2H9Q/5r7vcHabMnBqhHK2lTWOh3F8vixNzo8lqxrlmBtZVFpW8pmITHnq54+Tq4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.5.tgz", + "integrity": "sha512-28t+Sj3CPN8vkMOlZotOmDgilQwVvxWZl7b8rxpn73Tt/gCnvrHxQUMng4uu3itdFvrtba/1nHejvxqz8xgEMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.5.tgz", + "integrity": "sha512-Doz/hKtiuVAi9hMsBMpwBANhIZc8l238U2Onko3t2xUp8xtM0ZKdDYHMnm/qPFVthY8KtxkXaocwmMh6VolzMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.5.tgz", + "integrity": "sha512-WfGVaa1oz5A7+ZFPkERIbIhKT4olvGl1tyzTRaB5yoZRLqC0KwaO95FeZtOdQj/oKkjW57KcVF944m62/0GYtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.5.tgz", + "integrity": "sha512-Xh+VRuh6OMh3uJ0JkCjI57l+DVe7VRGBYymen8rFPnTVgATBwA6nmToxM2OwTlSvrnWpPKkrQUj93+K9huYC6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.5.tgz", + "integrity": "sha512-aC1gpJkkaUADHuAdQfuVTnqVUTLqqUNhAvEwHwVWcnVVZvNlDPGA0UveZsfXJJ9T6k9Po4eHi3c02gbdwO3g6w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.5.tgz", + "integrity": "sha512-0UNx2aavV0fk6UpZcwXFLztA2r/k9jTUa7OW7SAea1VYUhkug99MW1uZeXEnPn5+cHOd0n8myQay6TlFnBR07w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.5.tgz", + "integrity": "sha512-5nlJ3AeJWCTSzR7AEqVjT/faWyqKU86kCi1lLmxVqmNR+j4HrYdns+eTGjS/vmrzCIe8inGQckUadvS0+JkKdQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.5.tgz", + "integrity": "sha512-PWypQR+d4FLfkhBIV+/kHsUELAnMpx1bRvvsn3p+/sAERbnCzFrtDRG2Xw5n+2zPxBK2+iaP+vetsRl4Ti7WgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", + "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "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/winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", + "deprecated": "This is a stub types definition. winston provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "winston": "*" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "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/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "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/esbuild": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.5.tgz", + "integrity": "sha512-zdQoHBjuDqKsvV5OPaWansOwfSQ0Js+Uj9J85TBvj3bFW1JjWTSULMRwdQAc8qMeIScbClxeMK0jlrtB9linhA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.5", + "@esbuild/android-arm": "0.27.5", + "@esbuild/android-arm64": "0.27.5", + "@esbuild/android-x64": "0.27.5", + "@esbuild/darwin-arm64": "0.27.5", + "@esbuild/darwin-x64": "0.27.5", + "@esbuild/freebsd-arm64": "0.27.5", + "@esbuild/freebsd-x64": "0.27.5", + "@esbuild/linux-arm": "0.27.5", + "@esbuild/linux-arm64": "0.27.5", + "@esbuild/linux-ia32": "0.27.5", + "@esbuild/linux-loong64": "0.27.5", + "@esbuild/linux-mips64el": "0.27.5", + "@esbuild/linux-ppc64": "0.27.5", + "@esbuild/linux-riscv64": "0.27.5", + "@esbuild/linux-s390x": "0.27.5", + "@esbuild/linux-x64": "0.27.5", + "@esbuild/netbsd-arm64": "0.27.5", + "@esbuild/netbsd-x64": "0.27.5", + "@esbuild/openbsd-arm64": "0.27.5", + "@esbuild/openbsd-x64": "0.27.5", + "@esbuild/openharmony-arm64": "0.27.5", + "@esbuild/sunos-x64": "0.27.5", + "@esbuild/win32-arm64": "0.27.5", + "@esbuild/win32-ia32": "0.27.5", + "@esbuild/win32-x64": "0.27.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.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/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/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.7", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", + "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "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/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "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/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "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/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "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/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/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/supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/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/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "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/winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "license": "MIT", + "dependencies": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "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", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/packages/bitcore-logging/package.json b/packages/bitcore-logging/package.json new file mode 100644 index 00000000000..728a105acaa --- /dev/null +++ b/packages/bitcore-logging/package.json @@ -0,0 +1,47 @@ +{ + "name": "@bitpay-labs/bitcore-logging", + "description": "Centralized logging for Bitcore services", + "author": "BitPay Inc", + "version": "11.6.6", + "engines": { + "node": ">=22.0.0" + }, + "keywords": [ + "bitcore", + "logging", + "typescript", + "winston" + ], + "license": "MIT", + "main": "ts_build/src/index.js", + "types": "./ts_build/src/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/bitpay/bitcore/tree/master/packages/bitcore-logging" + }, + "scripts": { + "build": "tsc", + "build:prod": "tsc -p tsconfig.prod.json", + "clean": "rm -rf ts_build", + "compile": "npm run clean && npm run build", + "test": "npm run compile && mocha --exit -r tsx 'test/**/*.test.ts'", + "test:ci": "TEST_LOG_LEVEL=none npm run test", + "lint": "eslint .", + "fix:errors": "eslint --fix --quiet .", + "fix:all": "eslint --fix .", + "fix": "npm run fix:errors", + "precommit": "npm run lint" + }, + "dependencies": { + "winston": "3.3.3" + }, + "devDependencies": { + "@types/chai": "^5.2.2", + "@types/mocha": "^5.2.0", + "@types/node": "^22.10.10", + "chai": "^5.2.0", + "mocha": "^5.2.0", + "tsx": "^4.21.0", + "typescript": "^5.7.3" + } +} diff --git a/packages/bitcore-logging/src/create.ts b/packages/bitcore-logging/src/create.ts new file mode 100644 index 00000000000..f80d9491bb4 --- /dev/null +++ b/packages/bitcore-logging/src/create.ts @@ -0,0 +1,55 @@ +import path from 'path'; +import * as winston from 'winston'; +import { consoleFormat, httpFormat } from './formatters'; +import { LoggerConfig } from './types'; + +/** + * Build the transport array for a given config. + * Reads environment variables using the config prefix: + * {PREFIX}_LOG_LEVEL, {PREFIX}_LOG_HTTP_HOST, etc. + */ +export function getTransports(config: LoggerConfig): winston.transport[] { + const prefix = config.prefix; + const defaultLevel = config.defaultLevel || 'info'; + const logLevel = config.debug ? 'debug' : (process.env[`${prefix}_LOG_LEVEL`] || defaultLevel); + const lowerPrefix = prefix.toLowerCase(); + + const result: winston.transport[] = [ + new winston.transports.Console({ + level: logLevel, + format: consoleFormat() + }) + ]; + + const httpHost = process.env[`${prefix}_LOG_HTTP_HOST`]; + if (httpHost) { + const scriptName = process.argv[1] ? path.parse(process.argv[1]).name : 'unknown'; + const defaultPath = `${lowerPrefix}.${scriptName}`; + const tag = process.env[`${prefix}_LOG_HTTP_TAG`] || defaultPath; + + result.push(new winston.transports.Http({ + level: process.env[`${prefix}_LOG_HTTP_LEVEL`] || logLevel, + host: httpHost, + port: parseInt(process.env[`${prefix}_LOG_HTTP_PORT`] as string) || undefined, + path: process.env[`${prefix}_LOG_HTTP_PATH`] || defaultPath, + headers: { + 'Content-Type': 'application/json' + }, + format: httpFormat(tag) + })); + } + + return result; +} + +/** + * Create a Winston logger configured via environment variables. + * + * Usage: + * const logger = createLogger({ prefix: 'BCN' }); + * // reads BCN_LOG_LEVEL, BCN_LOG_HTTP_HOST, etc. + */ +export function createLogger(config: LoggerConfig): winston.Logger { + const transports = getTransports(config); + return winston.createLogger({ transports }); +} diff --git a/packages/bitcore-logging/src/decorators.ts b/packages/bitcore-logging/src/decorators.ts new file mode 100644 index 00000000000..56f496978bd --- /dev/null +++ b/packages/bitcore-logging/src/decorators.ts @@ -0,0 +1,100 @@ +import util from 'util'; +import * as winston from 'winston'; + +export const PerformanceTracker: Record = {}; + +let _debugEnabled = false; +let _logger: winston.Logger | null = null; + +/** + * Initialize the Loggify decorators with a logger and debug flag. + * Must be called before using LoggifyClass/LoggifyFunction/LoggifyObject. + */ +export function initLoggify(logger: winston.Logger, debug: boolean): void { + if (_logger) { + _logger.warn('initLoggify called more than once — overwriting existing logger'); + } + _logger = logger; + _debugEnabled = debug; +} + +export function SavePerformance(logPrefix: string, startTime: Date, endTime: Date): void { + const totalTime = endTime.getTime() - startTime.getTime(); + if (!PerformanceTracker[logPrefix]) { + PerformanceTracker[logPrefix] = { + time: totalTime, + count: 1, + avg: totalTime, + max: totalTime + }; + } else { + PerformanceTracker[logPrefix].time += totalTime; + PerformanceTracker[logPrefix].count++; + PerformanceTracker[logPrefix].avg = PerformanceTracker[logPrefix].time / PerformanceTracker[logPrefix].count; + PerformanceTracker[logPrefix].max = Math.max(totalTime, PerformanceTracker[logPrefix].max); + } +} + +export function LoggifyClass object>(aClass: T) { + if (!_debugEnabled || !_logger) { + return aClass; + } + const logger = _logger; + return class extends aClass { + constructor(...args: any[]) { + super(...args); + logger.debug(`Loggifying ${aClass.name} with args:: ${util.inspect(args)}`); + for (const prop of Object.getOwnPropertyNames(aClass.prototype)) { + if (typeof this[prop] === 'function') { + logger.debug(`Loggifying ${aClass.name}::${prop}`); + this[prop] = LoggifyFunction(this[prop], `${aClass.name}::${prop}`, this); + } + } + } + }; +} + +export function LoggifyFunction(fn: (...args: any[]) => any, logPrefix: string = '', bind?: any) { + if (!_debugEnabled || !_logger) { + return fn as (...methodargs: any[]) => any; + } + const logger = _logger; + let copy = fn; + if (bind) { + copy = copy.bind(bind); + } + return function(...methodargs: any[]) { + const startTime = new Date(); + logger.debug(`${logPrefix}::called::`); + const returnVal = copy(...methodargs); + if (returnVal && returnVal.then) { + returnVal + .catch((err: any) => { + logger.error(`${logPrefix}::catch::${err}`); + }) + .then((data: any) => { + logger.debug(`${logPrefix}::resolved::`); + SavePerformance(logPrefix, startTime, new Date()); + return data; + }); + } else { + SavePerformance(logPrefix, startTime, new Date()); + logger.debug(`${logPrefix}::returned::`); + } + return returnVal; + }; +} + +export function LoggifyObject(obj: any, logPrefix: string = '', bind?: any) { + if (!_debugEnabled || !_logger) { + return obj; + } + const logger = _logger; + for (const prop of Object.getOwnPropertyNames(obj)) { + if (typeof obj[prop] === 'function') { + logger.debug(`Loggifying ${logPrefix}::${prop}`); + obj[prop] = LoggifyFunction(obj[prop], `${logPrefix}::${prop}`, bind); + } + } + return obj; +} diff --git a/packages/bitcore-logging/src/formatters.ts b/packages/bitcore-logging/src/formatters.ts new file mode 100644 index 00000000000..88a8147bc93 --- /dev/null +++ b/packages/bitcore-logging/src/formatters.ts @@ -0,0 +1,40 @@ +import * as winston from 'winston'; + +/** + * Shared console format: colorize + prettyPrint + splat + simple + custom printf. + * Matches the existing BCN/BWS console output format. + */ +export function consoleFormat(): winston.Logform.Format { + return winston.format.combine( + winston.format.colorize(), + winston.format.prettyPrint(), + winston.format.splat(), + winston.format.simple(), + winston.format.printf(function(info) { + // fallback in case the above formatters don't work. + // eg: logger.log({ some: 'object' }) + if (typeof info.message === 'object') { + info.message = JSON.stringify(info.message, null, 4); + } + return `${info.level} :: ${new Date().toISOString()} :: ${info.message}`; + }) + ); +} + +/** + * Shared HTTP transport format with a tag field for log aggregation. + */ +export function httpFormat(tag: string): winston.Logform.Format { + return winston.format.combine( + winston.format.splat(), + winston.format.simple(), + winston.format.printf(info => { + // fallback in case the above formatters don't work. + // eg: logger.log({ some: 'object' }) + if (typeof info.message === 'object') { + info.message = JSON.stringify(info.message, null, 4); + } + return JSON.stringify({ tag, ...info }); + }) + ); +} diff --git a/packages/bitcore-logging/src/index.ts b/packages/bitcore-logging/src/index.ts new file mode 100644 index 00000000000..90950e67e7f --- /dev/null +++ b/packages/bitcore-logging/src/index.ts @@ -0,0 +1,12 @@ +export { createLogger, getTransports } from './create'; +export { consoleFormat, httpFormat } from './formatters'; +export { formatTimestamp, timestamp } from './timestamp'; +export { + initLoggify, + LoggifyClass, + LoggifyFunction, + LoggifyObject, + SavePerformance, + PerformanceTracker +} from './decorators'; +export { LoggerConfig } from './types'; diff --git a/packages/bitcore-logging/src/timestamp.ts b/packages/bitcore-logging/src/timestamp.ts new file mode 100644 index 00000000000..ead24fe19e3 --- /dev/null +++ b/packages/bitcore-logging/src/timestamp.ts @@ -0,0 +1,24 @@ +const timezone = new Date() + .toLocaleString('en-US', { timeZoneName: 'short' }) + .split(' ') + .pop(); + +export const formatTimestamp = (date: Date): string => + `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date + .getDate() + .toString() + .padStart(2, '0')} ${date + .getHours() + .toString() + .padStart(2, '0')}:${date + .getMinutes() + .toString() + .padStart(2, '0')}:${date + .getSeconds() + .toString() + .padStart(2, '0')}.${date + .getMilliseconds() + .toString() + .padStart(3, '0')} ${timezone}`; + +export const timestamp = (): string => formatTimestamp(new Date()); diff --git a/packages/bitcore-logging/src/types.ts b/packages/bitcore-logging/src/types.ts new file mode 100644 index 00000000000..bcdc8db964b --- /dev/null +++ b/packages/bitcore-logging/src/types.ts @@ -0,0 +1,8 @@ +export interface LoggerConfig { + /** Environment variable prefix, e.g. 'BCN' reads BCN_LOG_LEVEL, BCN_LOG_HTTP_HOST, etc. */ + prefix: string; + /** Default log level when env var is not set (default: 'info') */ + defaultLevel?: string; + /** Force debug log level (e.g. from a --DEBUG CLI flag) */ + debug?: boolean; +} diff --git a/packages/bitcore-logging/test/create.test.ts b/packages/bitcore-logging/test/create.test.ts new file mode 100644 index 00000000000..5a50bee615f --- /dev/null +++ b/packages/bitcore-logging/test/create.test.ts @@ -0,0 +1,78 @@ +import { expect } from 'chai'; +import { createLogger, getTransports } from '../src/create'; + +describe('createLogger', () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('should create a logger with console transport', () => { + const logger = createLogger({ prefix: 'TEST' }); + expect(logger).to.exist; + expect(logger.info).to.be.a('function'); + expect(logger.error).to.be.a('function'); + expect(logger.debug).to.be.a('function'); + expect(logger.warn).to.be.a('function'); + }); + + it('should use defaultLevel when env var is not set', () => { + delete process.env.TEST_LOG_LEVEL; + const transports = getTransports({ prefix: 'TEST', defaultLevel: 'warn' }); + expect(transports).to.have.length(1); + expect((transports[0] as any).level).to.equal('warn'); + }); + + it('should default to info when no defaultLevel and no env var', () => { + delete process.env.TEST_LOG_LEVEL; + const transports = getTransports({ prefix: 'TEST' }); + expect((transports[0] as any).level).to.equal('info'); + }); + + it('should read log level from env var using prefix', () => { + process.env.TEST_LOG_LEVEL = 'error'; + const transports = getTransports({ prefix: 'TEST' }); + expect((transports[0] as any).level).to.equal('error'); + }); + + it('should override to debug when debug flag is true', () => { + process.env.TEST_LOG_LEVEL = 'error'; + const transports = getTransports({ prefix: 'TEST', debug: true }); + expect((transports[0] as any).level).to.equal('debug'); + }); + + it('should only have console transport when no HTTP host set', () => { + delete process.env.TEST_LOG_HTTP_HOST; + const transports = getTransports({ prefix: 'TEST' }); + expect(transports).to.have.length(1); + }); + + it('should add HTTP transport when HTTP host is set', () => { + process.env.TEST_LOG_HTTP_HOST = 'localhost'; + const transports = getTransports({ prefix: 'TEST' }); + expect(transports).to.have.length(2); + }); + + it('should read HTTP port from env var', () => { + process.env.TEST_LOG_HTTP_HOST = 'localhost'; + process.env.TEST_LOG_HTTP_PORT = '9200'; + const transports = getTransports({ prefix: 'TEST' }); + expect(transports).to.have.length(2); + }); + + it('should work with different prefixes', () => { + process.env.BCN_LOG_LEVEL = 'debug'; + process.env.BWS_LOG_LEVEL = 'warn'; + + const bcnTransports = getTransports({ prefix: 'BCN' }); + const bwsTransports = getTransports({ prefix: 'BWS' }); + + expect((bcnTransports[0] as any).level).to.equal('debug'); + expect((bwsTransports[0] as any).level).to.equal('warn'); + }); +}); diff --git a/packages/bitcore-logging/test/decorators.test.ts b/packages/bitcore-logging/test/decorators.test.ts new file mode 100644 index 00000000000..ee9071af88d --- /dev/null +++ b/packages/bitcore-logging/test/decorators.test.ts @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { createLogger } from '../src/create'; +import { + LoggifyClass, + LoggifyFunction, + LoggifyObject, + PerformanceTracker, + SavePerformance, + initLoggify +} from '../src/decorators'; + +describe('Decorators', () => { + beforeEach(() => { + // Clear performance tracker between tests + for (const key of Object.keys(PerformanceTracker)) { + delete PerformanceTracker[key]; + } + }); + + describe('initLoggify', () => { + it('should warn when called more than once', () => { + const logger = createLogger({ prefix: 'TEST' }); + const warnings: string[] = []; + logger.warn = ((msg: string) => { warnings.push(msg); return logger; }) as any; + + initLoggify(logger, false); + initLoggify(logger, false); + + expect(warnings).to.have.length(1); + expect(warnings[0]).to.include('more than once'); + }); + }); + + describe('SavePerformance', () => { + it('should track performance for a new prefix', () => { + const start = new Date(2026, 0, 1, 0, 0, 0, 0); + const end = new Date(2026, 0, 1, 0, 0, 0, 100); + SavePerformance('test::fn', start, end); + + expect(PerformanceTracker['test::fn']).to.deep.equal({ + time: 100, + count: 1, + avg: 100, + max: 100 + }); + }); + + it('should accumulate performance data across calls', () => { + const start1 = new Date(2026, 0, 1, 0, 0, 0, 0); + const end1 = new Date(2026, 0, 1, 0, 0, 0, 100); + SavePerformance('test::fn', start1, end1); + + const start2 = new Date(2026, 0, 1, 0, 0, 0, 0); + const end2 = new Date(2026, 0, 1, 0, 0, 0, 200); + SavePerformance('test::fn', start2, end2); + + expect(PerformanceTracker['test::fn'].count).to.equal(2); + expect(PerformanceTracker['test::fn'].time).to.equal(300); + expect(PerformanceTracker['test::fn'].avg).to.equal(150); + expect(PerformanceTracker['test::fn'].max).to.equal(200); + }); + }); + + describe('LoggifyFunction', () => { + it('should return the original function when debug is disabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, false); + + const fn = () => 42; + const wrapped = LoggifyFunction(fn, 'test'); + expect(wrapped).to.equal(fn); + }); + + it('should wrap the function when debug is enabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, true); + + const fn = () => 42; + const wrapped = LoggifyFunction(fn, 'test'); + expect(wrapped).to.not.equal(fn); + expect(wrapped()).to.equal(42); + }); + + it('should track performance when debug is enabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, true); + + const fn = () => 'result'; + const wrapped = LoggifyFunction(fn, 'perf::test'); + wrapped(); + + expect(PerformanceTracker['perf::test']).to.exist; + expect(PerformanceTracker['perf::test'].count).to.equal(1); + }); + }); + + describe('LoggifyClass', () => { + it('should return the class unchanged when debug is disabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, false); + + class MyClass { + greet() { return 'hello'; } + } + const Wrapped = LoggifyClass(MyClass); + expect(Wrapped).to.equal(MyClass); + expect(new Wrapped().greet()).to.equal('hello'); + }); + + it('should return a subclass that wraps methods when debug is enabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, true); + + class MyClass { + greet() { return 'hello'; } + } + const Wrapped = LoggifyClass(MyClass); + expect(Wrapped).to.not.equal(MyClass); + const instance = new Wrapped(); + expect(instance.greet()).to.equal('hello'); + }); + }); + + describe('LoggifyObject', () => { + it('should return the object unchanged when debug is disabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, false); + + const obj = { fn: () => 42 }; + const result = LoggifyObject(obj, 'test'); + expect(result.fn()).to.equal(42); + }); + + it('should wrap object methods when debug is enabled', () => { + const logger = createLogger({ prefix: 'TEST' }); + initLoggify(logger, true); + + const obj = { fn: () => 42 }; + const original = obj.fn; + LoggifyObject(obj, 'test'); + expect(obj.fn).to.not.equal(original); + expect(obj.fn()).to.equal(42); + }); + }); +}); diff --git a/packages/bitcore-logging/test/formatters.test.ts b/packages/bitcore-logging/test/formatters.test.ts new file mode 100644 index 00000000000..042bb00c485 --- /dev/null +++ b/packages/bitcore-logging/test/formatters.test.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai'; +import { consoleFormat, httpFormat } from '../src/formatters'; + +describe('consoleFormat', () => { + it('should return a valid Winston format', () => { + const format = consoleFormat(); + expect(format).to.exist; + expect(format.transform).to.be.a('function'); + }); + + it('should produce a formatted string from an info object', () => { + const format = consoleFormat(); + const info = { level: 'info', message: 'hello', [Symbol.for('level')]: 'info' } as any; + const result = format.transform(info, {}); + expect(result).to.not.be.false; + const output = result[Symbol.for('message')] as string; + expect(output).to.include('hello'); + }); + + it('should JSON-stringify object messages as fallback', () => { + const format = consoleFormat(); + const obj = { foo: 'bar' }; + const info = { level: 'info', message: obj, [Symbol.for('level')]: 'info' } as any; + format.transform(info, {}); + expect(typeof info.message).to.equal('string'); + expect(info.message).to.include('foo'); + }); +}); + +describe('httpFormat', () => { + it('should return a valid Winston format', () => { + const format = httpFormat('my-tag'); + expect(format).to.exist; + expect(format.transform).to.be.a('function'); + }); + + it('should include the tag in JSON output', () => { + const format = httpFormat('my-tag'); + const info = { level: 'info', message: 'hello', [Symbol.for('level')]: 'info' } as any; + const result = format.transform(info, {}); + expect(result).to.not.be.false; + const output = result[Symbol.for('message')] as string; + const parsed = JSON.parse(output); + expect(parsed.tag).to.equal('my-tag'); + expect(parsed.message).to.equal('hello'); + }); + + it('should JSON-stringify object messages as fallback', () => { + const format = httpFormat('my-tag'); + const obj = { foo: 'bar' }; + const info = { level: 'warn', message: obj, [Symbol.for('level')]: 'warn' } as any; + format.transform(info, {}); + expect(typeof info.message).to.equal('string'); + expect(info.message).to.include('foo'); + }); +}); diff --git a/packages/bitcore-logging/test/timestamp.test.ts b/packages/bitcore-logging/test/timestamp.test.ts new file mode 100644 index 00000000000..74b8b9e3e8e --- /dev/null +++ b/packages/bitcore-logging/test/timestamp.test.ts @@ -0,0 +1,31 @@ +import { expect } from 'chai'; +import { formatTimestamp, timestamp } from '../src/timestamp'; + +describe('formatTimestamp', () => { + it('should format a date with year-month-day hours:minutes:seconds.ms', () => { + const date = new Date(2026, 0, 15, 10, 30, 45, 123); // Jan 15, 2026 10:30:45.123 + const result = formatTimestamp(date); + expect(result).to.match(/^2026-01-15 10:30:45\.123/); + }); + + it('should left-pad milliseconds to 3 digits', () => { + const date = new Date(2026, 2, 5, 8, 5, 3, 7); // Mar 5, 2026 08:05:03.007 + const result = formatTimestamp(date); + expect(result).to.match(/^2026-03-05 08:05:03\.007/); + }); + + it('should include timezone suffix', () => { + const result = formatTimestamp(new Date()); + // Should end with a timezone abbreviation like EST, PST, UTC, etc. + expect(result).to.match(/\s[A-Z]{2,5}$/); + }); +}); + +describe('timestamp', () => { + it('should return a formatted timestamp string for the current time', () => { + const result = timestamp(); + expect(result).to.be.a('string'); + // Should match the date format pattern + expect(result).to.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}/); + }); +}); diff --git a/packages/bitcore-logging/tsconfig.json b/packages/bitcore-logging/tsconfig.json new file mode 100644 index 00000000000..8e74da0f268 --- /dev/null +++ b/packages/bitcore-logging/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "lib": [ + "es5", + "es6", + "es2017", + "es2020", + "es2022", + "dom" + ], + "noImplicitAny": false, + "removeComments": true, + "declaration": true, + "declarationMap": true, + "module": "commonjs", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true, + "target": "ES2023", + "typeRoots": [ + "./node_modules/@types" + ], + "outDir": "ts_build", + "sourceMap": true + }, + "include": [ + "./src/**/*.ts", + "./test/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/packages/bitcore-logging/tsconfig.prod.json b/packages/bitcore-logging/tsconfig.prod.json new file mode 100644 index 00000000000..ff69b63ee05 --- /dev/null +++ b/packages/bitcore-logging/tsconfig.prod.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "test" + ] +} diff --git a/packages/bitcore-node/package.json b/packages/bitcore-node/package.json index 560204a346f..e423d9bb20a 100644 --- a/packages/bitcore-node/package.json +++ b/packages/bitcore-node/package.json @@ -87,7 +87,6 @@ "@types/request": "2.47.0", "@types/sinon": "4.3.3", "@types/through2": "2.0.33", - "@types/winston": "2.4.4", "axios": "0.21.1", "chai": "^5.2.0", "mocha": "5.0.5", @@ -103,6 +102,7 @@ "@bitpay-labs/bitcore-lib-cash": "^11.7.0", "@bitpay-labs/bitcore-lib-doge": "^11.7.0", "@bitpay-labs/bitcore-lib-ltc": "^11.7.0", + "@bitpay-labs/bitcore-logging": "^11.7.0", "@bitpay-labs/bitcore-p2p": "^11.7.0", "@bitpay-labs/bitcore-p2p-cash": "^11.7.0", "@bitpay-labs/bitcore-p2p-doge": "^11.7.0", @@ -121,8 +121,7 @@ "request": "2.88.0", "secp256k1": "4.0.3", "socket.io": "4.8.1", - "source-map-support": "^0.5.13", - "winston": "3.3.0" + "source-map-support": "^0.5.13" }, "gitHead": "a1890ef287c9e5d0e966fb445d15bc59a9393761" } diff --git a/packages/bitcore-node/src/decorators/Loggify.ts b/packages/bitcore-node/src/decorators/Loggify.ts index 9a5deb01557..49f6a59aaf4 100644 --- a/packages/bitcore-node/src/decorators/Loggify.ts +++ b/packages/bitcore-node/src/decorators/Loggify.ts @@ -1,88 +1,15 @@ -import util from 'util'; +import { + LoggifyClass, + LoggifyFunction, + LoggifyObject, + PerformanceTracker, + SavePerformance, + initLoggify +} from '@bitpay-labs/bitcore-logging'; import logger from '../logger'; import parseArgv from '../utils/parseArgv'; -export const PerformanceTracker = {}; const args = parseArgv([], [{ arg: 'DEBUG', type: 'bool' }]); +initLoggify(logger, !!args.DEBUG); -export function SavePerformance(logPrefix, startTime, endTime) { - const totalTime = endTime.getTime() - startTime.getTime(); - if (!PerformanceTracker[logPrefix]) { - PerformanceTracker[logPrefix] = { - time: totalTime, - count: 1, - avg: totalTime, - max: totalTime - }; - } else { - PerformanceTracker[logPrefix].time += totalTime; - PerformanceTracker[logPrefix].count++; - PerformanceTracker[logPrefix].avg = PerformanceTracker[logPrefix].time / PerformanceTracker[logPrefix].count; - PerformanceTracker[logPrefix].max = Math.max(totalTime, PerformanceTracker[logPrefix].max); - } -} - -export function LoggifyClass object>(aClass: T) { - if (!args.DEBUG) { - return aClass; - } - return class extends aClass { - constructor(...args: any[]) { - super(...args); - logger.debug(`Loggifying ${aClass.name} with args:: ${util.inspect(args)}`); - for (const prop of Object.getOwnPropertyNames(aClass.prototype)) { - if (typeof this[prop] === 'function') { - logger.debug(`Loggifying ${aClass.name}::${prop}`); - this[prop] = LoggifyFunction(this[prop], `${aClass.name}::${prop}`, this); - } - } - } - }; -} - -export function LoggifyFunction(fn: (...args: any[]) => any, logPrefix: string = '', bind?: any) { - if (!args.DEBUG) { - return fn as (...methodargs: any[]) => any; - } - let copy = fn; - if (bind) { - copy = copy.bind(bind); - } - return function(...methodargs: any[]) { - const startTime = new Date(); - logger.debug(`${logPrefix}::called::`); - const returnVal = copy(...methodargs); - if (returnVal && returnVal.then) { - returnVal - .catch((err: any) => { - logger.error(`${logPrefix}::catch::${err}`); - }) - .then((data: any) => { - logger.debug(`${logPrefix}::resolved::`); - SavePerformance(logPrefix, startTime, new Date()); - return data; - }); - } else { - SavePerformance(logPrefix, startTime, new Date()); - logger.debug(`${logPrefix}::returned::`); - } - return returnVal; - }; -} - -export function LoggifyObject(obj: any, logPrefix: string = '', bind?: any) { - if (!args.DEBUG) { - return obj; - } - for (const prop of Object.getOwnPropertyNames(obj)) { - if (typeof obj[prop] === 'function') { - let copy = obj[prop]; - if (bind) { - copy = copy.bind(bind); - } - logger.debug(`Loggifying ${logPrefix}::${prop}`); - obj[prop] = LoggifyFunction(obj[prop], `${logPrefix}::${prop}`, bind); - } - } - return obj; -} +export { LoggifyClass, LoggifyFunction, LoggifyObject, SavePerformance, PerformanceTracker }; diff --git a/packages/bitcore-node/src/logger.ts b/packages/bitcore-node/src/logger.ts index 67fd74fa07c..a645d37e25f 100644 --- a/packages/bitcore-node/src/logger.ts +++ b/packages/bitcore-node/src/logger.ts @@ -1,83 +1,8 @@ -import path from 'path'; -import * as winston from 'winston'; +import { createLogger, formatTimestamp, timestamp } from '@bitpay-labs/bitcore-logging'; import parseArgv from './utils/parseArgv'; const args = parseArgv([], [{ arg: 'DEBUG', type: 'bool' }]); -const logLevel = args.DEBUG ? 'debug' : (process.env.BCN_LOG_LEVEL || 'info'); - - -export const transports: winston.transport[] = [ - new winston.transports.Console({ - level: logLevel, - format: winston.format.combine( - winston.format.colorize(), - winston.format.prettyPrint(), - winston.format.splat(), - winston.format.simple(), - winston.format.printf(function(info) { - // fallback in case the above formatters don't work. - // eg: logger.log({ some: 'object' }) - if (typeof info.message === 'object') { - info.message = JSON.stringify(info.message, null, 4); - } - return `${info.level} :: ${new Date().toISOString()} :: ${info.message}`; - }) - ) - }) -]; - -if (process.env.BCN_LOG_HTTP_HOST) { - transports.push(new winston.transports.Http({ - level: process.env.BCN_LOG_HTTP_LEVEL || logLevel, - host: process.env.BCN_LOG_HTTP_HOST, - port: parseInt(process.env.BCN_LOG_HTTP_PORT as string) || undefined, - path: process.env.BCN_LOG_HTTP_PATH || ('bcn.' + path.parse(process.argv[1]).name), - headers: { - 'Content-Type': 'application/json' - }, - format: winston.format.combine( - winston.format.splat(), - winston.format.simple(), - winston.format.printf(info => { - // fallback in case the above formatters don't work. - // eg: logger.log({ some: 'object' }) - if (typeof info.message === 'object') { - info.message = JSON.stringify(info.message, null, 4); - } - return JSON.stringify({ - tag: process.env.BCN_LOG_HTTP_TAG || ('bcn.' + path.parse(process.argv[1]).name), - ...info - }); - }) - ) - })); -} - -export const logger = winston.createLogger({ transports }); - -const timezone = new Date() - .toLocaleString('en-US', { timeZoneName: 'short' }) - .split(' ') - .pop(); - -export const formatTimestamp = (date: Date) => - `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date - .getDate() - .toString() - .padStart(2, '0')} ${date - .getHours() - .toString() - .padStart(2, '0')}:${date - .getMinutes() - .toString() - .padStart(2, '0')}:${date - .getSeconds() - .toString() - .padStart(2, '0')}.${date - .getMilliseconds() - .toString() - .padEnd(3, '0')} ${timezone}`; - -export const timestamp = () => formatTimestamp(new Date()); - +export const logger = createLogger({ prefix: 'BCN', debug: args.DEBUG }); +export const transports = logger.transports; +export { formatTimestamp, timestamp }; export default logger; diff --git a/packages/bitcore-wallet-service/package.json b/packages/bitcore-wallet-service/package.json index 0c61372cb75..9a040551eaa 100644 --- a/packages/bitcore-wallet-service/package.json +++ b/packages/bitcore-wallet-service/package.json @@ -43,6 +43,7 @@ "cp:templates": "cp -r ./templates ./ts_build/templates" }, "dependencies": { + "@bitpay-labs/bitcore-logging": "^11.7.0", "@bitpay-labs/bitcore-tss": "^11.7.0", "@bitpay-labs/crypto-wallet-core": "^11.7.0", "@sendgrid/mail": "6.5.4", @@ -72,8 +73,7 @@ "socket.io-client": "4.8.1", "source-map-support": "0.5.16", "sticky-session": "0.1.0", - "uuid": "3.4.0", - "winston": "3.3.3" + "uuid": "3.4.0" }, "devDependencies": { "@types/async": "^2.4.1", diff --git a/packages/bitcore-wallet-service/src/lib/logger.ts b/packages/bitcore-wallet-service/src/lib/logger.ts index b2dce696bf9..161f59a359d 100644 --- a/packages/bitcore-wallet-service/src/lib/logger.ts +++ b/packages/bitcore-wallet-service/src/lib/logger.ts @@ -1,77 +1,6 @@ -import path from 'path'; -import * as winston from 'winston'; - -// const logLevel = args.DEBUG ? 'debug' : 'info'; -const logLevel = process.env.BWS_LOG_LEVEL || 'debug'; - -export const transports: winston.transport[] = [ - new winston.transports.Console({ - level: logLevel, - format: winston.format.combine( - winston.format.colorize(), - winston.format.prettyPrint(), - winston.format.splat(), - winston.format.simple(), - winston.format.printf(function(info) { - // fallback in case the above formatters don't work. - // eg: logger.log({ some: 'object' }) - if (typeof info.message === 'object') { - info.message = JSON.stringify(info.message, null, 4); - } - return `${info.level} :: ${new Date().toISOString()} :: ${info.message}`; - }) - ), - }) -]; - -if (process.env.BWS_LOG_HTTP_HOST) { - transports.push(new winston.transports.Http({ - level: process.env.BWS_LOG_HTTP_LEVEL || logLevel, - host: process.env.BWS_LOG_HTTP_HOST, - port: parseInt(process.env.BWS_LOG_HTTP_PORT) || undefined, - path: process.env.BWS_LOG_HTTP_PATH || ('bws.' + path.parse(process.argv[1]).name), - headers: { - 'Content-Type': 'application/json' - }, - format: winston.format.combine( - winston.format.splat(), - winston.format.simple(), - winston.format.printf(info => { - // fallback in case the above formatters don't work. - // eg: logger.log({ some: 'object' }) - if (typeof info.message === 'object') { - info.message = JSON.stringify(info.message, null, 4); - } - return JSON.stringify({ - tag: process.env.BWS_LOG_HTTP_TAG || ('bws.' + path.parse(process.argv[1]).name), - ...info - }); - }), - ) - })); -} - -export const logger = winston.createLogger({ transports }); - -export const formatTimestamp = (date: Date): string => - `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date - .getDate() - .toString() - .padStart(2, '0')} ${date - .getHours() - .toString() - .padStart(2, '0')}:${date - .getMinutes() - .toString() - .padStart(2, '0')}:${date - .getSeconds() - .toString() - .padStart(2, '0')}.${date - .getMilliseconds() - .toString() - // .padEnd(3, '0')} ${timezone}`; - .padEnd(3, '0')}`; - -export const timestamp = () => formatTimestamp(new Date()); +import { createLogger, formatTimestamp, timestamp } from '@bitpay-labs/bitcore-logging'; +export const logger = createLogger({ prefix: 'BWS', defaultLevel: 'debug' }); +export const transports = logger.transports; +export { formatTimestamp, timestamp }; export default logger;