From f3afc12a7c911c456e06906c937c8459b9ccad45 Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 00:24:45 +0530 Subject: [PATCH 1/8] refactor: migrate logging to pino --- .../20240108_130100_add_event_tags_table.js | 8 +- package-lock.json | 427 ++++++++++++++++-- package.json | 3 +- src/adapters/redis-adapter.ts | 2 +- src/adapters/web-server-adapter.ts | 4 +- src/adapters/web-socket-adapter.ts | 18 +- src/app/app.ts | 10 +- src/app/maintenance-worker.ts | 2 +- src/app/static-mirroring-worker.ts | 2 +- src/app/worker.ts | 6 +- .../callbacks/lnbits-callback-controller.ts | 4 +- .../callbacks/nodeless-callback-controller.ts | 6 +- .../callbacks/opennode-callback-controller.ts | 4 +- .../callbacks/zebedee-callback-controller.ts | 4 +- .../invoices/get-invoice-status-controller.ts | 2 +- .../invoices/post-invoice-controller.ts | 7 +- src/database/client.ts | 6 +- src/factories/logger-factory.ts | 80 +++- .../lnbits-payments-processor-factory.ts | 5 +- .../nodeless-payments-processor-factory.ts | 5 +- .../opennode-payments-processor-factory.ts | 7 +- .../zebedee-payments-processor-factory.ts | 9 +- src/factories/worker-factory.ts | 5 +- src/handlers/event-message-handler.ts | 15 +- src/logger.ts | 19 + .../lnbits-payment-processor.ts | 4 +- .../lnurl-payments-processor.ts | 4 +- .../nodeless-payments-processor.ts | 4 +- .../opennode-payments-processor.ts | 4 +- .../zebedee-payments-processor.ts | 4 +- src/repositories/invoice-repository.ts | 2 +- src/services/payments-service.ts | 14 +- src/tor/client.ts | 2 +- src/utils/http.ts | 5 +- src/utils/settings.ts | 2 +- 35 files changed, 575 insertions(+), 130 deletions(-) create mode 100644 src/logger.ts diff --git a/migrations/20240108_130100_add_event_tags_table.js b/migrations/20240108_130100_add_event_tags_table.js index 2c57180a..582753ae 100644 --- a/migrations/20240108_130100_add_event_tags_table.js +++ b/migrations/20240108_130100_add_event_tags_table.js @@ -1,3 +1,9 @@ +const pino = require('pino') + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', +}) + exports.up = async function (knex) { // Create the event_tags table await knex.schema.createTable('event_tags', function (table) { @@ -70,7 +76,7 @@ exports.up = async function (knex) { processedEvents++ const currentPercentage = Math.floor(processedEvents / totalEvents * 100) if (currentPercentage > lastPercentage) { - console.log(`${new Date().toLocaleString()} Migration progress: ${currentPercentage}% (${processedEvents}/${totalEvents})`) + logger.info(`${new Date().toLocaleString()} Migration progress: ${currentPercentage}% (${processedEvents}/${totalEvents})`) lastPercentage = currentPercentage } } diff --git a/package-lock.json b/package-lock.json index ee895909..8f42ffd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "knex": "2.4.2", "pg": "8.9.0", "pg-query-stream": "4.3.0", + "pino": "^8.21.0", "ramda": "0.28.0", "redis": "4.5.1", "ws": "^8.18.0", @@ -32,7 +33,6 @@ "@cucumber/pretty-formatter": "1.0.0", "@types/chai": "^4.3.1", "@types/chai-as-promised": "^7.1.5", - "@types/debug": "4.1.7", "@types/express": "4.17.21", "@types/js-yaml": "4.0.5", "@types/mocha": "^9.1.1", @@ -49,6 +49,7 @@ "mocha": "^11.7.5", "mochawesome": "^7.1.3", "nyc": "^15.1.0", + "pino-pretty": "^13.1.3", "rimraf": "^3.0.2", "rxjs": "7.8.0", "sinon": "15.0.1", @@ -2672,16 +2673,6 @@ "@types/node": "*" } }, - "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, "node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -2736,13 +2727,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", @@ -2900,6 +2884,18 @@ "node": ">=12.10" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3138,6 +3134,15 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/axios": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", @@ -3166,6 +3171,26 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/baseline-browser-mapping": { "version": "2.10.17", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz", @@ -3320,6 +3345,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3954,6 +4003,16 @@ "node": ">= 6" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4221,6 +4280,16 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -4367,6 +4436,24 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -4459,6 +4546,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-copy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz", + "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4496,6 +4590,15 @@ "node": ">= 6" } }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -5149,6 +5252,13 @@ "he": "bin/he" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -5257,6 +5367,26 @@ "dev": true, "license": "public domain" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5766,6 +5896,16 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6772,16 +6912,6 @@ "marge": "bin/cli.js" } }, - "node_modules/mochawesome-report-generator/node_modules/dateformat": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", - "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/mochawesome-report-generator/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -7307,6 +7437,15 @@ "node": ">=0.10.0" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -7850,6 +7989,121 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", + "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.6.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/pino-pretty": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz", + "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^4.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/pino-pretty/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/pino-pretty/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "license": "MIT" + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -7990,6 +8244,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-on-spawn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", @@ -8003,6 +8266,12 @@ "node": ">=8" } }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -8054,6 +8323,17 @@ "node": ">=10" } }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -8119,6 +8399,12 @@ ], "license": "MIT" }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -8404,6 +8690,22 @@ "node": ">=4" } }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8417,6 +8719,15 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -8776,12 +9087,38 @@ ], "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", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/seed-random": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", @@ -9086,6 +9423,15 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9261,22 +9607,14 @@ } }, "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, + "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.1.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, "node_modules/string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -9585,6 +9923,15 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index 33e61c2f..c9bc97d6 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "@cucumber/pretty-formatter": "1.0.0", "@types/chai": "^4.3.1", "@types/chai-as-promised": "^7.1.5", - "@types/debug": "4.1.7", "@types/express": "4.17.21", "@types/js-yaml": "4.0.5", "@types/mocha": "^9.1.1", @@ -110,6 +109,7 @@ "mocha": "^11.7.5", "mochawesome": "^7.1.3", "nyc": "^15.1.0", + "pino-pretty": "^13.1.3", "rimraf": "^3.0.2", "rxjs": "7.8.0", "sinon": "15.0.1", @@ -133,6 +133,7 @@ "knex": "2.4.2", "pg": "8.9.0", "pg-query-stream": "4.3.0", + "pino": "^8.21.0", "ramda": "0.28.0", "redis": "4.5.1", "ws": "^8.18.0", diff --git a/src/adapters/redis-adapter.ts b/src/adapters/redis-adapter.ts index a3816588..47078346 100644 --- a/src/adapters/redis-adapter.ts +++ b/src/adapters/redis-adapter.ts @@ -42,7 +42,7 @@ export class RedisAdapter implements ICacheAdapter { } private onClientError(error: Error) { - debug('Unable to connect to Redis.', error) + debug.error('Unable to connect to Redis.', error) // throw error } diff --git a/src/adapters/web-server-adapter.ts b/src/adapters/web-server-adapter.ts index 4e479965..a509a2d2 100644 --- a/src/adapters/web-server-adapter.ts +++ b/src/adapters/web-server-adapter.ts @@ -27,14 +27,14 @@ export class WebServerAdapter extends EventEmitter implements IWebServerAdapter } private onError(error: Error) { - console.error('web-server-adapter: error:', error) + debug.error('web-server-adapter: error:', error) } private onClientError(error: Error, socket: Duplex) { if (error['code'] === 'ECONNRESET' || !socket.writable) { return } - console.error('web-server-adapter: client socket error:', error) + debug.error('web-server-adapter: client socket error:', error) socket.end('HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\n') } diff --git a/src/adapters/web-socket-adapter.ts b/src/adapters/web-socket-adapter.ts index e436c013..2da59dc4 100644 --- a/src/adapters/web-socket-adapter.ts +++ b/src/adapters/web-socket-adapter.ts @@ -57,13 +57,11 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter this.client .on('error', (error) => { if (error.name === 'RangeError' && error.message === 'Max payload size exceeded') { - console.error( - `web-socket-adapter: client ${this.clientId} (${this.getClientAddress()}) sent payload too large`, - ) + debug.error(`web-socket-adapter: client ${this.clientId} (${this.getClientAddress()}) sent payload too large`) } else if (error.name === 'RangeError' && error.message === 'Invalid WebSocket frame: RSV1 must be clear') { debug(`client ${this.clientId} (${this.getClientAddress()}) enabled compression`) } else { - console.error(`web-socket-adapter: client error ${this.clientId} (${this.getClientAddress()}):`, error) + debug.error(`web-socket-adapter: client error ${this.clientId} (${this.getClientAddress()}):`, error) } this.client.close() @@ -129,7 +127,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter public onHeartbeat(): void { if (!this.alive && !this.subscriptions.size) { - console.error(`web-socket-adapter: pong timeout for client ${this.clientId} (${this.getClientAddress()})`) + debug.error(`web-socket-adapter: pong timeout for client ${this.clientId} (${this.getClientAddress()})`) this.client.close() return } @@ -161,7 +159,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter messageHandler = this.createMessageHandler([message, this]) as IMessageHandler & IAbortable if (!messageHandler) { - console.error('web-socket-adapter: unhandled message: no handler found:', message) + debug.error('web-socket-adapter: unhandled message: no handler found:', message) return } @@ -177,7 +175,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter } catch (error) { if (error instanceof Error) { if (error.name === 'AbortError') { - console.error(`web-socket-adapter: abort from client ${this.clientId} (${this.getClientAddress()})`) + debug.error(`web-socket-adapter: abort from client ${this.clientId} (${this.getClientAddress()})`) } else if (error.name === 'SyntaxError' || error instanceof ZodError) { debug('invalid message client %s (%s): %s', this.clientId, this.getClientAddress(), error.message) const notice = @@ -186,10 +184,10 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter : `invalid: ${error.message}` this.sendMessage(createNoticeMessage(notice)) } else { - console.error('web-socket-adapter: unable to handle message:', error) + debug.error('web-socket-adapter: unable to handle message:', error) } } else { - console.error('web-socket-adapter: unable to handle message:', error) + debug.error('web-socket-adapter: unable to handle message:', error) } } finally { if (abortable && messageHandler) { @@ -250,7 +248,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter try { handler.abort() } catch (error) { - console.error('Unable to abort message handler', error) + debug.error('Unable to abort message handler', error) } } } diff --git a/src/app/app.ts b/src/app/app.ts index 864010f5..6bb6ea6c 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -36,7 +36,7 @@ export class App implements IRunnable { public run(): void { const settings = this.settings() this.watchers = SettingsStatic.watchSettings() - console.log(` + debug.info(` ███▄ █ ▒█████ ██████ ▄▄▄█████▓ ██▀███ ▓█████ ▄▄▄ ███▄ ▄███▓ ██ ▀█ █ ▒██▒ ██▒▒██ ▒ ▓ ██▒ ▓▒▓██ ▒ ██▒▓█ ▀▒████▄ ▓██▒▀█▀ ██▒ ▓██ ▀█ ██▒▒██░ ██▒░ ▓██▄ ▒ ▓██░ ▒░▓██ ░▄█ ▒▒███ ▒██ ▀█▄ ▓██ ▓██░ @@ -52,7 +52,7 @@ export class App implements IRunnable { const logCentered = (input: string, width: number) => { const start = (width - input.length) >> 1 - console.log(' '.repeat(start), input) + debug.info(' '.repeat(start), input) } logCentered(`v${packageJson.version}`, width) logCentered(`NIPs implemented: ${packageJson.supportedNips}`, width) @@ -68,7 +68,7 @@ export class App implements IRunnable { this.process.env.SECRET === '' || this.process.env.SECRET === 'changeme') ) { - console.error('Please configure the secret using the SECRET environment variable.') + debug.error('Please configure the secret using the SECRET environment variable.') this.process.exit(1) } @@ -152,14 +152,14 @@ export class App implements IRunnable { } private onExit() { - console.log('exiting') + debug.info('exiting') this.close(() => { this.process.exit(0) }) } public close(callback?: (...args: any[]) => void): void { - console.log('close') + debug.info('close') if (Array.isArray(this.watchers)) { for (const watcher of this.watchers) { watcher.close() diff --git a/src/app/maintenance-worker.ts b/src/app/maintenance-worker.ts index db10cd14..0e93dbf7 100644 --- a/src/app/maintenance-worker.ts +++ b/src/app/maintenance-worker.ts @@ -168,7 +168,7 @@ export class MaintenanceWorker implements IRunnable { } successful++ } catch (error) { - console.error('Unable to update invoice from payment processor. Reason:', error) + debug.error('Unable to update invoice from payment processor. Reason:', error) } debug('updated %d of %d invoices successfully', successful, invoices.length) diff --git a/src/app/static-mirroring-worker.ts b/src/app/static-mirroring-worker.ts index 64582ff2..346c0dfd 100644 --- a/src/app/static-mirroring-worker.ts +++ b/src/app/static-mirroring-worker.ts @@ -53,7 +53,7 @@ export class StaticMirroringWorker implements IRunnable { public run(): void { const currentSettings = this.settings() - console.log('mirroring', currentSettings.mirroring) + debug.info('mirroring', currentSettings.mirroring) this.config = path(['mirroring', 'static', process.env.MIRROR_INDEX], currentSettings) as Mirror diff --git a/src/app/worker.ts b/src/app/worker.ts index 93b29a5a..4e75d5ab 100644 --- a/src/app/worker.ts +++ b/src/app/worker.ts @@ -36,12 +36,12 @@ export class AppWorker implements IRunnable { private onError(error: Error) { if (error.name === 'TypeError' && error.message === "Cannot read properties of undefined (reading '__knexUid')") { - console.error( - "Unable to acquire connection. Please increase DB_MAX_POOL_SIZE, DB_ACQUIRE_CONNECTION_TIMEOUT and tune postgresql.conf to make use of server's resources.", + debug.error( + 'Unable to acquire connection. Please increase DB_MAX_POOL_SIZE, DB_ACQUIRE_CONNECTION_TIMEOUT and tune postgresql.conf to make use of server\'s resources.' ) return } - console.error('uncaught error:', error) + debug.error('uncaught error:', error) } private onExit() { diff --git a/src/controllers/callbacks/lnbits-callback-controller.ts b/src/controllers/callbacks/lnbits-callback-controller.ts index 49bde798..a5845ded 100644 --- a/src/controllers/callbacks/lnbits-callback-controller.ts +++ b/src/controllers/callbacks/lnbits-callback-controller.ts @@ -75,7 +75,7 @@ export class LNbitsCallbackController implements IController { try { await this.paymentsService.updateInvoice(invoice) } catch (error) { - console.error(`Unable to persist invoice ${invoice.id}`, error) + debug.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -97,7 +97,7 @@ export class LNbitsCallbackController implements IController { await this.paymentsService.confirmInvoice(invoice as Invoice) await this.paymentsService.sendInvoiceUpdateNotification(invoice as Invoice) } catch (error) { - console.error(`Unable to confirm invoice ${invoice.id}`, error) + debug.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/callbacks/nodeless-callback-controller.ts b/src/controllers/callbacks/nodeless-callback-controller.ts index 1dc55f26..bf00aca1 100644 --- a/src/controllers/callbacks/nodeless-callback-controller.ts +++ b/src/controllers/callbacks/nodeless-callback-controller.ts @@ -37,7 +37,7 @@ export class NodelessCallbackController implements IController { const actual = request.headers['nodeless-signature'] if (expected !== actual) { - console.error('nodeless callback request rejected: signature mismatch:', { expected, actual }) + debug.error('nodeless callback request rejected: signature mismatch:', { expected, actual }) response.status(403).send('Forbidden') return } @@ -68,7 +68,7 @@ export class NodelessCallbackController implements IController { updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice) debug('updated invoice: %O', updatedInvoice) } catch (error) { - console.error(`Unable to persist invoice ${invoice.id}`, error) + debug.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -86,7 +86,7 @@ export class NodelessCallbackController implements IController { await this.paymentsService.confirmInvoice(invoice) await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice) } catch (error) { - console.error(`Unable to confirm invoice ${invoice.id}`, error) + debug.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/callbacks/opennode-callback-controller.ts b/src/controllers/callbacks/opennode-callback-controller.ts index 350f0631..a854dc0f 100644 --- a/src/controllers/callbacks/opennode-callback-controller.ts +++ b/src/controllers/callbacks/opennode-callback-controller.ts @@ -105,7 +105,7 @@ export class OpenNodeCallbackController implements IController { try { updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice) } catch (error) { - console.error(`Unable to persist invoice ${invoice.id}`, error) + debug.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -133,7 +133,7 @@ export class OpenNodeCallbackController implements IController { }) await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice) } catch (error) { - console.error(`Unable to confirm invoice ${invoice.id}`, error) + debug.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/callbacks/zebedee-callback-controller.ts b/src/controllers/callbacks/zebedee-callback-controller.ts index 32120eea..486c055d 100644 --- a/src/controllers/callbacks/zebedee-callback-controller.ts +++ b/src/controllers/callbacks/zebedee-callback-controller.ts @@ -52,7 +52,7 @@ export class ZebedeeCallbackController implements IController { try { updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice) } catch (error) { - console.error(`Unable to persist invoice ${invoice.id}`, error) + debug.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -77,7 +77,7 @@ export class ZebedeeCallbackController implements IController { }) await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice) } catch (error) { - console.error(`Unable to confirm invoice ${invoice.id}`, error) + debug.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/invoices/get-invoice-status-controller.ts b/src/controllers/invoices/get-invoice-status-controller.ts index 10977940..92114b49 100644 --- a/src/controllers/invoices/get-invoice-status-controller.ts +++ b/src/controllers/invoices/get-invoice-status-controller.ts @@ -37,7 +37,7 @@ export class GetInvoiceStatusController implements IController { status: invoice.status, }) } catch (error) { - console.error(`get-invoice-status-controller: unable to get invoice ${invoiceId}:`, error) + debug.error(`get-invoice-status-controller: unable to get invoice ${invoiceId}:`, error) response .status(500) diff --git a/src/controllers/invoices/post-invoice-controller.ts b/src/controllers/invoices/post-invoice-controller.ts index 1d908903..81426e83 100644 --- a/src/controllers/invoices/post-invoice-controller.ts +++ b/src/controllers/invoices/post-invoice-controller.ts @@ -115,8 +115,11 @@ export class PostInvoiceController implements IController { invoice = await this.paymentsService.createInvoice(pubkey, amount, description) } catch (error) { - console.error('Unable to create invoice. Reason:', error) - response.status(500).setHeader('content-type', 'text/plain').send('Unable to create invoice') + debug.error('Unable to create invoice. Reason:', error) + response + .status(500) + .setHeader('content-type', 'text/plain') + .send('Unable to create invoice') return } diff --git a/src/database/client.ts b/src/database/client.ts index e5265ad3..5ba1813e 100644 --- a/src/database/client.ts +++ b/src/database/client.ts @@ -3,6 +3,8 @@ import 'pg-query-stream' import knex, { Knex } from 'knex' import { createLogger } from '../factories/logger-factory' +const poolLogger = createLogger('database-client:pool-monitor') + ;((knex) => { const lastUpdate = {} knex.Client.prototype.releaseConnection = function (connection) { @@ -14,9 +16,7 @@ import { createLogger } from '../factories/logger-factory' lastUpdate[tag] = lastUpdate[tag] ?? now if (now - lastUpdate[tag] >= 60000) { lastUpdate[tag] = now - console.log( - `${tag} connection pool: ${this.pool.numUsed()} used / ${this.pool.numFree()} free / ${this.pool.numPendingAcquires()} pending`, - ) + poolLogger.info(`${tag} connection pool: ${this.pool.numUsed()} used / ${this.pool.numFree()} free / ${this.pool.numPendingAcquires()} pending`) } } diff --git a/src/factories/logger-factory.ts b/src/factories/logger-factory.ts index ba40d0cd..0b41fd7e 100644 --- a/src/factories/logger-factory.ts +++ b/src/factories/logger-factory.ts @@ -1,19 +1,81 @@ +import type { LevelWithSilent, Logger as PinoLogger } from 'pino' import cluster from 'cluster' -import debug from 'debug' +import { format } from 'node:util' + +import logger from '../logger' + +type MessageLogger = ((message?: unknown, ...args: unknown[]) => void) & { + debug: (message?: unknown, ...args: unknown[]) => void + info: (message?: unknown, ...args: unknown[]) => void + warn: (message?: unknown, ...args: unknown[]) => void + error: (message?: unknown, ...args: unknown[]) => void + fatal: (message?: unknown, ...args: unknown[]) => void + extend: (namespace: string) => MessageLogger + child: (bindings: Record) => MessageLogger +} + +const logAtLevel = ( + instance: PinoLogger, + level: LevelWithSilent, + message: unknown, + args: unknown[] +) => { + if (message instanceof Error) { + instance[level]({ err: message }) + return + } + + if (typeof message === 'string') { + instance[level](format(message, ...args)) + return + } + + if (args.length > 0) { + instance[level](format('%O', [message, ...args])) + return + } + + instance[level](message) +} + +const createMethod = (instance: PinoLogger, level: LevelWithSilent) => + (message?: unknown, ...args: unknown[]) => { + logAtLevel(instance, level, message, args) + } + +const createMessageLogger = (instance: PinoLogger, scope: string): MessageLogger => { + const fn = ((message?: unknown, ...args: unknown[]) => { + logAtLevel(instance, 'debug', message, args) + }) as MessageLogger + + fn.debug = createMethod(instance, 'debug') + fn.info = createMethod(instance, 'info') + fn.warn = createMethod(instance, 'warn') + fn.error = createMethod(instance, 'error') + fn.fatal = createMethod(instance, 'fatal') + fn.child = (bindings: Record) => createMessageLogger(instance.child(bindings), scope) + fn.extend = (namespace: string) => { + const nextScope = scope ? `${scope}:${namespace}` : namespace + + return createMessageLogger(instance.child({ scope: nextScope }), nextScope) + } + + return fn +} export const createLogger = ( namespace: string, options: { enabled?: boolean; stdout?: boolean } = { enabled: false, stdout: false }, ) => { - const prefix = cluster.isWorker ? process.env.WORKER_TYPE : 'primary' - const instance = debug(prefix) - if (options.enabled) { - debug.enable(`${prefix}:${namespace}:*`) - } - if (options.stdout) { - instance.log = console.log.bind(console) + const prefix = cluster.isWorker ? (process.env.WORKER_TYPE ?? 'worker') : 'primary' + const scope = namespace ? `${prefix}:${namespace}` : prefix + const instance = logger.child({ scope }) + + if (options.enabled && instance.level !== 'debug') { + instance.level = 'debug' } - const fn = instance.extend(namespace) + + const fn = createMessageLogger(instance, scope) return fn } diff --git a/src/factories/payments-processors/lnbits-payments-processor-factory.ts b/src/factories/payments-processors/lnbits-payments-processor-factory.ts index e10097ba..08aac899 100644 --- a/src/factories/payments-processors/lnbits-payments-processor-factory.ts +++ b/src/factories/payments-processors/lnbits-payments-processor-factory.ts @@ -1,11 +1,14 @@ import axios, { CreateAxiosDefaults } from 'axios' import { path } from 'ramda' +import { createLogger } from '../logger-factory' import { createSettings } from '../settings-factory' import { IPaymentsProcessor } from '../../@types/clients' import { LNbitsPaymentsProcessor } from '../../payments-processors/lnbits-payment-processor' import { Settings } from '../../@types/settings' +const debug = createLogger('lnbits-payments-processor-factory') + const getLNbitsAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.LNBITS_API_KEY) { throw new Error('LNBITS_API_KEY must be set to an invoice or admin key.') @@ -25,7 +28,7 @@ export const createLNbitsPaymentProcessor = (settings: Settings): IPaymentsProce const callbackBaseURL = path(['paymentsProcessors', 'lnbits', 'callbackBaseURL'], settings) as string | undefined if (typeof callbackBaseURL === 'undefined' || callbackBaseURL.indexOf('nostream.your-domain.com') >= 0) { const error = new Error('Setting paymentsProcessor.lnbits.callbackBaseURL is not configured.') - console.error('Unable to create payments processor.', error) + debug.error('Unable to create payments processor. %o', error) throw error } diff --git a/src/factories/payments-processors/nodeless-payments-processor-factory.ts b/src/factories/payments-processors/nodeless-payments-processor-factory.ts index 4e1f2a03..95ba63ef 100644 --- a/src/factories/payments-processors/nodeless-payments-processor-factory.ts +++ b/src/factories/payments-processors/nodeless-payments-processor-factory.ts @@ -1,15 +1,18 @@ import axios, { CreateAxiosDefaults } from 'axios' import { path } from 'ramda' +import { createLogger } from '../logger-factory' import { createSettings } from '../settings-factory' import { IPaymentsProcessor } from '../../@types/clients' import { NodelessPaymentsProcessor } from '../../payments-processors/nodeless-payments-processor' import { Settings } from '../../@types/settings' +const debug = createLogger('nodeless-payments-processor-factory') + const getNodelessAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.NODELESS_API_KEY) { const error = new Error('NODELESS_API_KEY must be set.') - console.error('Unable to get Nodeless config.', error) + debug.error('Unable to get Nodeless config. %o', error) throw error } diff --git a/src/factories/payments-processors/opennode-payments-processor-factory.ts b/src/factories/payments-processors/opennode-payments-processor-factory.ts index 8e934fa0..ea9661d8 100644 --- a/src/factories/payments-processors/opennode-payments-processor-factory.ts +++ b/src/factories/payments-processors/opennode-payments-processor-factory.ts @@ -1,15 +1,18 @@ import axios, { CreateAxiosDefaults } from 'axios' import { path } from 'ramda' +import { createLogger } from '../logger-factory' import { createSettings } from '../settings-factory' import { IPaymentsProcessor } from '../../@types/clients' import { OpenNodePaymentsProcessor } from '../../payments-processors/opennode-payments-processor' import { Settings } from '../../@types/settings' +const debug = createLogger('opennode-payments-processor-factory') + const getOpenNodeAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.OPENNODE_API_KEY) { const error = new Error('OPENNODE_API_KEY must be set.') - console.error('Unable to get OpenNode config.', error) + debug.error('Unable to get OpenNode config. %o', error) throw error } @@ -27,7 +30,7 @@ export const createOpenNodePaymentsProcessor = (settings: Settings): IPaymentsPr const callbackBaseURL = path(['paymentsProcessors', 'opennode', 'callbackBaseURL'], settings) as string | undefined if (typeof callbackBaseURL === 'undefined' || callbackBaseURL.indexOf('nostream.your-domain.com') >= 0) { const error = new Error('Setting paymentsProcessor.opennode.callbackBaseURL is not configured.') - console.error('Unable to create payments processor.', error) + debug.error('Unable to create payments processor. %o', error) throw error } diff --git a/src/factories/payments-processors/zebedee-payments-processor-factory.ts b/src/factories/payments-processors/zebedee-payments-processor-factory.ts index 3cdd0e3e..521a736a 100644 --- a/src/factories/payments-processors/zebedee-payments-processor-factory.ts +++ b/src/factories/payments-processors/zebedee-payments-processor-factory.ts @@ -1,15 +1,18 @@ import axios, { CreateAxiosDefaults } from 'axios' import { path } from 'ramda' +import { createLogger } from '../logger-factory' import { createSettings } from '../settings-factory' import { IPaymentsProcessor } from '../../@types/clients' import { Settings } from '../../@types/settings' import { ZebedeePaymentsProcessor } from '../../payments-processors/zebedee-payments-processor' +const debug = createLogger('zebedee-payments-processor-factory') + const getZebedeeAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.ZEBEDEE_API_KEY) { const error = new Error('ZEBEDEE_API_KEY must be set.') - console.error('Unable to get Zebedee config.', error) + debug.error('Unable to get Zebedee config. %o', error) throw error } @@ -27,7 +30,7 @@ export const createZebedeePaymentsProcessor = (settings: Settings): IPaymentsPro const callbackBaseURL = path(['paymentsProcessors', 'zebedee', 'callbackBaseURL'], settings) as string | undefined if (typeof callbackBaseURL === 'undefined' || callbackBaseURL.indexOf('nostream.your-domain.com') >= 0) { const error = new Error('Setting paymentsProcessor.zebedee.callbackBaseURL is not configured.') - console.error('Unable to create payments processor.', error) + debug.error('Unable to create payments processor. %o', error) throw error } @@ -37,7 +40,7 @@ export const createZebedeePaymentsProcessor = (settings: Settings): IPaymentsPro !settings.paymentsProcessors?.zebedee?.ipWhitelist?.length ) { const error = new Error('Setting paymentsProcessor.zebedee.ipWhitelist is empty.') - console.error('Unable to create payments processor.', error) + debug.error('Unable to create payments processor. %o', error) throw error } diff --git a/src/factories/worker-factory.ts b/src/factories/worker-factory.ts index 4f4cba9a..9cd1f018 100644 --- a/src/factories/worker-factory.ts +++ b/src/factories/worker-factory.ts @@ -5,6 +5,7 @@ import { WebSocketServer } from 'ws' import { getMasterDbClient, getReadReplicaDbClient } from '../database/client' import { AppWorker } from '../app/worker' +import { createLogger } from './logger-factory' import { createSettings } from '../factories/settings-factory' import { createWebApp } from './web-app-factory' import { EventRepository } from '../repositories/event-repository' @@ -13,6 +14,8 @@ import { UserRepository } from '../repositories/user-repository' import { webSocketAdapterFactory } from './websocket-adapter-factory' import { WebSocketServerAdapter } from '../adapters/web-socket-server-adapter' +const debug = createLogger('worker-factory') + export const workerFactory = (): AppWorker => { const dbClient = getMasterDbClient() const readReplicaDbClient = getReadReplicaDbClient() @@ -29,7 +32,7 @@ export const workerFactory = (): AppWorker => { let maxPayloadSize: number | undefined if (pathSatisfies(is(String), ['network', 'max_payload_size'], settings)) { - console.warn(`WARNING: Setting network.max_payload_size is deprecated and will be removed in a future version. + debug.warn(`WARNING: Setting network.max_payload_size is deprecated and will be removed in a future version. Use network.maxPayloadSize instead.`) maxPayloadSize = path(['network', 'max_payload_size'], settings) } else { diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 2c5fd5e0..227f3f6d 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -2,16 +2,9 @@ import { ContextMetadataKey, EventExpirationTimeMetadataKey, EventKinds } from ' import { DEFAULT_NIP05_VERIFY_EXPIRATION_MS, extractNip05FromEvent, - isDomainAllowed, - Nip05VerificationOutcome, - parseNip05Identifier, - verifyNip05Identifier, -} from '../utils/nip05' -import { Event, ExpiringEvent } from '../@types/event' import { EventRateLimit, FeeSchedule, Settings } from '../@types/settings' import { getEventExpiration, - getEventProofOfWork, getPubkeyProofOfWork, getPublicKey, getRelayPrivateKey, @@ -121,11 +114,9 @@ export class EventMessageHandler implements IMessageHandler { try { await strategy.execute(event) this.processNip05Metadata(event) - } catch (_error) { - this.webSocket.emit( - WebSocketAdapterEvent.Message, - createCommandResult(event.id, false, 'error: unable to process event'), - ) + } catch (error) { + debug.error('error handling message', message, error) + this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, 'error: unable to process event')) } } diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 00000000..08c9f3d7 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,19 @@ +import pino from 'pino' + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + transport: process.env.NODE_ENV === 'development' ? { + target: 'pino-pretty', + options: { + colorize: true, + translateTime: 'HH:MM:ss', + ignore: 'pid,hostname', + }, + } : undefined, + serializers: { + err: pino.stdSerializers.err, + }, +}) + +export default logger +export { logger } diff --git a/src/payments-processors/lnbits-payment-processor.ts b/src/payments-processors/lnbits-payment-processor.ts index 31eaee57..14bc5343 100644 --- a/src/payments-processors/lnbits-payment-processor.ts +++ b/src/payments-processors/lnbits-payment-processor.ts @@ -69,7 +69,7 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { invoice.updatedAt = new Date() return invoice } catch (error) { - console.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } @@ -131,7 +131,7 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { return invoice } catch (error) { - console.error('Unable to request invoice. Reason:', error.message) + debug.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/lnurl-payments-processor.ts b/src/payments-processors/lnurl-payments-processor.ts index 4d290e85..730e58ef 100644 --- a/src/payments-processors/lnurl-payments-processor.ts +++ b/src/payments-processors/lnurl-payments-processor.ts @@ -27,7 +27,7 @@ export class LnurlPaymentsProcessor implements IPaymentsProcessor { status: response.data.settled ? InvoiceStatus.COMPLETED : InvoiceStatus.PENDING, } } catch (error) { - console.error(`Unable to get invoice ${invoice.id}. Reason:`, error) + debug.error(`Unable to get invoice ${invoice.id}. Reason:`, error) throw error } @@ -60,7 +60,7 @@ export class LnurlPaymentsProcessor implements IPaymentsProcessor { return result } catch (error) { - console.error('Unable to request invoice. Reason:', error.message) + debug.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/nodeless-payments-processor.ts b/src/payments-processors/nodeless-payments-processor.ts index 1652d86e..88e37eca 100644 --- a/src/payments-processors/nodeless-payments-processor.ts +++ b/src/payments-processors/nodeless-payments-processor.ts @@ -26,7 +26,7 @@ export class NodelessPaymentsProcessor implements IPaymentsProcessor { return fromNodelessInvoice(response.data.data) } catch (error) { - console.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } @@ -66,7 +66,7 @@ export class NodelessPaymentsProcessor implements IPaymentsProcessor { return result } catch (error) { - console.error('Unable to request invoice. Reason:', error.message) + debug.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/opennode-payments-processor.ts b/src/payments-processors/opennode-payments-processor.ts index 3b1f4aa8..559e4b29 100644 --- a/src/payments-processors/opennode-payments-processor.ts +++ b/src/payments-processors/opennode-payments-processor.ts @@ -24,7 +24,7 @@ export class OpenNodePaymentsProcessor implements IPaymentsProcessor { return fromOpenNodeInvoice(response.data.data) } catch (error) { - console.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } @@ -56,7 +56,7 @@ export class OpenNodePaymentsProcessor implements IPaymentsProcessor { return result } catch (error) { - console.error('Unable to request invoice. Reason:', error.message) + debug.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/zebedee-payments-processor.ts b/src/payments-processors/zebedee-payments-processor.ts index 3a147b42..7a18349f 100644 --- a/src/payments-processors/zebedee-payments-processor.ts +++ b/src/payments-processors/zebedee-payments-processor.ts @@ -24,7 +24,7 @@ export class ZebedeePaymentsProcessor implements IPaymentsProcessor { return fromZebedeeInvoice(response.data.data) } catch (error) { - console.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } @@ -53,7 +53,7 @@ export class ZebedeePaymentsProcessor implements IPaymentsProcessor { return result } catch (error) { - console.error('Unable to request invoice. Reason:', error.message) + debug.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/repositories/invoice-repository.ts b/src/repositories/invoice-repository.ts index f002a150..3969ee08 100644 --- a/src/repositories/invoice-repository.ts +++ b/src/repositories/invoice-repository.ts @@ -23,7 +23,7 @@ export class InvoiceRepository implements IInvoiceRepository { try { await client.raw('select confirm_invoice(?, ?, ?)', [invoiceId, amountPaid.toString(), confirmedAt.toISOString()]) } catch (error) { - console.error('Unable to confirm invoice. Reason:', error.message) + debug.error('Unable to confirm invoice. Reason:', error.message) throw error } diff --git a/src/services/payments-service.ts b/src/services/payments-service.ts index 237e8907..573a0cc5 100644 --- a/src/services/payments-service.ts +++ b/src/services/payments-service.ts @@ -29,7 +29,7 @@ export class PaymentsService implements IPaymentsService { try { return await this.invoiceRepository.findPendingInvoices(0, 10) } catch (error) { - console.log('Unable to get pending invoices.', error) + debug.info('Unable to get pending invoices.', error) throw error } @@ -41,7 +41,7 @@ export class PaymentsService implements IPaymentsService { typeof invoice === 'string' || invoice?.verifyURL ? invoice : invoice.id, ) } catch (error) { - console.log('Unable to get invoice from payments processor. Reason:', error) + debug.info('Unable to get invoice from payments processor. Reason:', error) throw error } @@ -98,7 +98,7 @@ export class PaymentsService implements IPaymentsService { } } catch (error) { await transaction.rollback() - console.error('Unable to create invoice:', error) + debug.error('Unable to create invoice:', error) throw error } @@ -112,7 +112,7 @@ export class PaymentsService implements IPaymentsService { status: invoice.status, }) } catch (error) { - console.error('Unable to update invoice. Reason:', error) + debug.error('Unable to update invoice. Reason:', error) throw error } } @@ -122,7 +122,7 @@ export class PaymentsService implements IPaymentsService { try { return await this.invoiceRepository.updateStatus(invoice) } catch (error) { - console.error('Unable to update invoice. Reason:', error) + debug.error('Unable to update invoice. Reason:', error) throw error } } @@ -177,7 +177,7 @@ export class PaymentsService implements IPaymentsService { await transaction.commit() } catch (error) { - console.error('Unable to confirm invoice. Reason:', error) + debug.error('Unable to confirm invoice. Reason:', error) await transaction.rollback() throw error @@ -230,7 +230,7 @@ export class PaymentsService implements IPaymentsService { return event } - const logError = (error: Error) => console.error('Unable to send notification', error) + const logError = (error: Error) => debug.error('Unable to send notification', error) await pipe( identifyEvent, diff --git a/src/tor/client.ts b/src/tor/client.ts index e1c10355..f2565fbb 100644 --- a/src/tor/client.ts +++ b/src/tor/client.ts @@ -203,7 +203,7 @@ export const addOnion = async (port: number, host?: string): Promise => debug('hidden service: %s:%d', hiddenService.ServiceID, port) if (hiddenService?.PrivateKey) { - console.log('saving private key to %s', path) + debug.info('saving private key to %s', path) debug('saving private key to %s', path) await writeFile(path, hiddenService.PrivateKey, 'utf8') diff --git a/src/utils/http.ts b/src/utils/http.ts index 652714dd..3987a633 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -1,12 +1,15 @@ import { IncomingMessage } from 'http' +import { createLogger } from '../factories/logger-factory' import { Settings } from '../@types/settings' +const debug = createLogger('http-utils') + export const getRemoteAddress = (request: IncomingMessage, settings: Settings): string => { let header: string | undefined // TODO: Remove deprecation warning if ('network' in settings && 'remote_ip_header' in settings.network) { - console.warn(`WARNING: Setting network.remote_ip_header is deprecated and will be removed in a future version. + debug.warn(`WARNING: Setting network.remote_ip_header is deprecated and will be removed in a future version. Use network.remoteIpHeader instead.`) header = settings.network['remote_ip_header'] as string } else { diff --git a/src/utils/settings.ts b/src/utils/settings.ts index 7d27a363..c9c9fd7d 100644 --- a/src/utils/settings.ts +++ b/src/utils/settings.ts @@ -53,7 +53,7 @@ export class SettingsStatic { switch (fileType) { case SettingsFileTypes.json: { - console.warn('settings.json is deprecated, please use a yaml file based on resources/default-settings.yaml') + debug.warn('settings.json is deprecated, please use a yaml file based on resources/default-settings.yaml') return SettingsStatic.loadAndParseJsonFile(path) } case SettingsFileTypes.yaml: { From ebad308313f0edf382476ec648704a265a1fa70c Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 00:54:23 +0530 Subject: [PATCH 2/8] fix: stabilize logging after rebase --- src/factories/logger-factory.ts | 26 +++++++++++++++++++-- src/handlers/event-message-handler.ts | 7 ++++++ test/unit/app/maintenance-worker.spec.ts | 3 --- test/unit/services/payments-service.spec.ts | 5 ---- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/factories/logger-factory.ts b/src/factories/logger-factory.ts index 0b41fd7e..5cdb0273 100644 --- a/src/factories/logger-factory.ts +++ b/src/factories/logger-factory.ts @@ -14,6 +14,28 @@ type MessageLogger = ((message?: unknown, ...args: unknown[]) => void) & { child: (bindings: Record) => MessageLogger } +const stringifyForLog = (input: unknown): string => { + if (input instanceof Error) { + return input.stack ?? input.message + } + + try { + return format('%O', input) + } catch { + return '[Unserializable]' + } +} + +const safeFormat = (template: string, args: unknown[]): string => { + try { + return format(template, ...args) + } catch { + const extra = args.map(stringifyForLog).join(' ') + + return extra ? `${template} ${extra}` : template + } +} + const logAtLevel = ( instance: PinoLogger, level: LevelWithSilent, @@ -26,12 +48,12 @@ const logAtLevel = ( } if (typeof message === 'string') { - instance[level](format(message, ...args)) + instance[level](safeFormat(message, args)) return } if (args.length > 0) { - instance[level](format('%O', [message, ...args])) + instance[level]([message, ...args].map(stringifyForLog).join(' ')) return } diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 227f3f6d..0d76a1a3 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -2,9 +2,16 @@ import { ContextMetadataKey, EventExpirationTimeMetadataKey, EventKinds } from ' import { DEFAULT_NIP05_VERIFY_EXPIRATION_MS, extractNip05FromEvent, + isDomainAllowed, + Nip05VerificationOutcome, + parseNip05Identifier, + verifyNip05Identifier, +} from '../utils/nip05' +import { Event, ExpiringEvent } from '../@types/event' import { EventRateLimit, FeeSchedule, Settings } from '../@types/settings' import { getEventExpiration, + getEventProofOfWork, getPubkeyProofOfWork, getPublicKey, getRelayPrivateKey, diff --git a/test/unit/app/maintenance-worker.spec.ts b/test/unit/app/maintenance-worker.spec.ts index bd571a6a..7eca639b 100644 --- a/test/unit/app/maintenance-worker.spec.ts +++ b/test/unit/app/maintenance-worker.spec.ts @@ -435,12 +435,9 @@ describe('MaintenanceWorker', () => { .onFirstCall().rejects(new Error('processor error')) .onSecondCall().resolves({ id: 'inv-2', status: InvoiceStatus.PENDING }) - const consoleErrorStub = sandbox.stub(console, 'error') - await (worker as any).onSchedule() expect(maintenanceService.clearOldEvents).to.have.been.calledOnce - expect(consoleErrorStub).to.have.been.calledOnce expect(paymentsService.updateInvoiceStatus).to.have.been.calledOnce }) }) diff --git a/test/unit/services/payments-service.spec.ts b/test/unit/services/payments-service.spec.ts index 6d4e7035..7bac5a9f 100644 --- a/test/unit/services/payments-service.spec.ts +++ b/test/unit/services/payments-service.spec.ts @@ -464,16 +464,11 @@ describe('PaymentsService', () => { }) it('calls logError and does not throw when the pipeline fails', async () => { - const consoleErrorStub = sandbox.stub(console, 'error') ;(eventUtils.identifyEvent as Sinon.SinonStub).rejects(new Error('identify failed')) // otherwise() swallows the error — the method must resolve, not reject await service.sendInvoiceUpdateNotification(stubInvoice({ amountPaid: 100n })) - expect(consoleErrorStub).to.have.been.calledWith( - 'Unable to send notification', - Sinon.match.instanceOf(Error), - ) expect(eventRepository.create).not.to.have.been.called }) }) From 90d7edd8918c4181e374324edbe832e226c474b8 Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 01:46:09 +0530 Subject: [PATCH 3/8] chore: remove unused debug dependency --- package-lock.json | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f42ffd1..700a6701 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@noble/secp256k1": "1.7.1", "accepts": "^1.3.8", "axios": "^1.15.0", - "debug": "4.3.4", "express": "4.22.1", "js-yaml": "4.1.1", "knex": "2.4.2", diff --git a/package.json b/package.json index c9bc97d6..a61e0571 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,6 @@ "@noble/secp256k1": "1.7.1", "accepts": "^1.3.8", "axios": "^1.15.0", - "debug": "4.3.4", "express": "4.22.1", "js-yaml": "4.1.1", "knex": "2.4.2", From c460112801b0268f0bc8791095a2c53ad7f43d0c Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 02:12:29 +0530 Subject: [PATCH 4/8] fix: stabilize coverage and integration checks --- migrations/20240108_130100_add_event_tags_table.js | 8 +------- test/unit/utils/settings.spec.ts | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/migrations/20240108_130100_add_event_tags_table.js b/migrations/20240108_130100_add_event_tags_table.js index 582753ae..2c57180a 100644 --- a/migrations/20240108_130100_add_event_tags_table.js +++ b/migrations/20240108_130100_add_event_tags_table.js @@ -1,9 +1,3 @@ -const pino = require('pino') - -const logger = pino({ - level: process.env.LOG_LEVEL || 'info', -}) - exports.up = async function (knex) { // Create the event_tags table await knex.schema.createTable('event_tags', function (table) { @@ -76,7 +70,7 @@ exports.up = async function (knex) { processedEvents++ const currentPercentage = Math.floor(processedEvents / totalEvents * 100) if (currentPercentage > lastPercentage) { - logger.info(`${new Date().toLocaleString()} Migration progress: ${currentPercentage}% (${processedEvents}/${totalEvents})`) + console.log(`${new Date().toLocaleString()} Migration progress: ${currentPercentage}% (${processedEvents}/${totalEvents})`) lastPercentage = currentPercentage } } diff --git a/test/unit/utils/settings.spec.ts b/test/unit/utils/settings.spec.ts index d96b0e04..fb0c7126 100644 --- a/test/unit/utils/settings.spec.ts +++ b/test/unit/utils/settings.spec.ts @@ -198,7 +198,7 @@ describe('SettingsStatic', () => { expect(SettingsStatic.createSettings()).to.be.an('object') - expect(existsSyncStub).to.have.been.calledOnceWithExactly('/some/path/settings.json') + expect(existsSyncStub).to.have.been.calledWithExactly('/some/path/settings.json') expect(getSettingsFileBasePathStub).to.have.been.calledOnce expect(saveSettingsStub).to.have.been.calledOnceWithExactly('/some/path/settings.json', Sinon.match.object) expect(loadSettingsStub).to.have.been.called From 66e60c38a78e3498e23dbf339dc59aece52e4f52 Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 02:31:38 +0530 Subject: [PATCH 5/8] chore: add changeset for logging migration --- .changeset/migrate-logging-to-pino.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/migrate-logging-to-pino.md diff --git a/.changeset/migrate-logging-to-pino.md b/.changeset/migrate-logging-to-pino.md new file mode 100644 index 00000000..8384ad6e --- /dev/null +++ b/.changeset/migrate-logging-to-pino.md @@ -0,0 +1,5 @@ +--- +"nostream": patch +--- + +Migrate runtime logging to pino across adapters, services, workers, and controllers, and stabilize CI-related fixes for coverage and integration workflows after rebasing. From aaabcc2c83347ccd0ca42b14d46cc40ae7869daf Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 02:57:06 +0530 Subject: [PATCH 6/8] refactor: enforce logger naming and no-console --- biome.json | 17 +++++- src/adapters/redis-adapter.ts | 24 ++++----- src/adapters/web-server-adapter.ts | 18 +++---- src/adapters/web-socket-adapter.ts | 34 ++++++------ src/adapters/web-socket-server-adapter.ts | 18 +++---- src/app/app.ts | 30 +++++------ src/app/maintenance-worker.ts | 30 +++++------ src/app/static-mirroring-worker.ts | 54 +++++++++---------- src/app/worker.ts | 12 ++--- src/cache/client.ts | 4 +- .../get-admission-check-controller.ts | 4 +- .../callbacks/lnbits-callback-controller.ts | 16 +++--- .../callbacks/nodeless-callback-controller.ts | 22 ++++---- .../callbacks/opennode-callback-controller.ts | 22 ++++---- .../callbacks/zebedee-callback-controller.ts | 18 +++---- .../invoices/get-invoice-status-controller.ts | 10 ++-- .../invoices/post-invoice-controller.ts | 10 ++-- src/database/client.ts | 8 +-- src/factories/payments-processor-factory.ts | 4 +- .../lnbits-payments-processor-factory.ts | 4 +- .../nodeless-payments-processor-factory.ts | 4 +- .../opennode-payments-processor-factory.ts | 6 +-- .../zebedee-payments-processor-factory.ts | 8 +-- src/factories/worker-factory.ts | 4 +- src/handlers/event-message-handler.ts | 36 ++++++------- .../default-event-strategy.ts | 4 +- .../event-strategies/delete-event-strategy.ts | 4 +- .../ephemeral-event-strategy.ts | 4 +- .../gift-wrap-event-strategy.ts | 4 +- ...arameterized-replaceable-event-strategy.ts | 4 +- .../replaceable-event-strategy.ts | 4 +- .../event-strategies/vanish-event-strategy.ts | 4 +- .../rate-limiter-middleware.ts | 6 +-- src/handlers/subscribe-message-handler.ts | 10 ++-- .../lnbits-payment-processor.ts | 16 +++--- .../lnurl-payments-processor.ts | 12 ++--- .../nodeless-payments-processor.ts | 18 +++---- .../opennode-payments-processor.ts | 14 ++--- .../zebedee-payments-processor.ts | 14 ++--- src/repositories/event-repository.ts | 16 +++--- src/repositories/invoice-repository.ts | 12 ++--- .../nip05-verification-repository.ts | 10 ++-- src/repositories/user-repository.ts | 14 ++--- src/services/maintenance-service.ts | 8 +-- src/services/payments-service.ts | 28 +++++----- src/tor/client.ts | 20 +++---- src/utils/http.ts | 4 +- src/utils/nip05.ts | 16 +++--- src/utils/settings.ts | 14 ++--- src/utils/sliding-window-rate-limiter.ts | 4 +- .../unit/services/maintenance-service.spec.ts | 5 -- 51 files changed, 348 insertions(+), 338 deletions(-) diff --git a/biome.json b/biome.json index 473e5d60..0891c403 100644 --- a/biome.json +++ b/biome.json @@ -24,9 +24,24 @@ "recommended": false, "correctness": { "noUnusedVariables": "error" }, "style": { "useBlockStatements": "warn" }, - "suspicious": { "noExplicitAny": "off" } + "suspicious": { + "noExplicitAny": "off", + "noConsole": "error" + } } }, + "overrides": [ + { + "includes": ["migrations/**", "src/clean-db.ts", "src/import-events.ts", "src/scripts/**", "test/**"], + "linter": { + "rules": { + "suspicious": { + "noConsole": "off" + } + } + } + } + ], "javascript": { "formatter": { "quoteStyle": "single", diff --git a/src/adapters/redis-adapter.ts b/src/adapters/redis-adapter.ts index 47078346..2f0b5cd9 100644 --- a/src/adapters/redis-adapter.ts +++ b/src/adapters/redis-adapter.ts @@ -2,7 +2,7 @@ import { CacheClient } from '../@types/cache' import { createLogger } from '../factories/logger-factory' import { ICacheAdapter } from '../@types/adapters' -const debug = createLogger('redis-adapter') +const logger = createLogger('redis-adapter') export class RedisAdapter implements ICacheAdapter { private connection: Promise @@ -13,11 +13,11 @@ export class RedisAdapter implements ICacheAdapter { this.connection.catch((error) => this.onClientError(error)) this.client - .on('connect', () => debug('connecting')) - .on('ready', () => debug('connected')) + .on('connect', () => logger('connecting')) + .on('ready', () => logger('connected')) .on('error', (error) => this.onClientError(error)) .on('reconnecting', () => { - debug('reconnecting') + logger('reconnecting') this.connection = new Promise((resolve, reject) => { const cleanup = () => { this.client.removeListener('ready', onReady) @@ -42,25 +42,25 @@ export class RedisAdapter implements ICacheAdapter { } private onClientError(error: Error) { - debug.error('Unable to connect to Redis.', error) + logger.error('Unable to connect to Redis.', error) // throw error } public async hasKey(key: string): Promise { await this.connection - debug('has %s key', key) + logger('has %s key', key) return Boolean(this.client.exists(key)) } public async getKey(key: string): Promise { await this.connection - debug('get %s key', key) + logger('get %s key', key) return this.client.get(key) } public async setKey(key: string, value: string, expirySeconds?: number): Promise { await this.connection - debug('set %s key', key) + logger('set %s key', key) if (typeof expirySeconds === 'number') { return 'OK' === (await this.client.set(key, value, { EX: expirySeconds })) } @@ -69,25 +69,25 @@ export class RedisAdapter implements ICacheAdapter { public async removeRangeByScoreFromSortedSet(key: string, min: number, max: number): Promise { await this.connection - debug('remove %d..%d range from sorted set %s', min, max, key) + logger('remove %d..%d range from sorted set %s', min, max, key) return this.client.zRemRangeByScore(key, min, max) } public async getRangeFromSortedSet(key: string, min: number, max: number): Promise { await this.connection - debug('get %d..%d range from sorted set %s', min, max, key) + logger('get %d..%d range from sorted set %s', min, max, key) return this.client.zRange(key, min, max) } public async setKeyExpiry(key: string, expiry: number): Promise { await this.connection - debug('expire at %d from sorted set %s', expiry, key) + logger('expire at %d from sorted set %s', expiry, key) await this.client.expire(key, expiry) } public async addToSortedSet(key: string, set: Record): Promise { await this.connection - debug('add %o to sorted set %s', set, key) + logger('add %o to sorted set %s', set, key) const members = Object.entries(set).map(([value, score]) => ({ score: Number(score), value })) return this.client.zAdd(key, members) diff --git a/src/adapters/web-server-adapter.ts b/src/adapters/web-server-adapter.ts index a509a2d2..6e6bd740 100644 --- a/src/adapters/web-server-adapter.ts +++ b/src/adapters/web-server-adapter.ts @@ -4,11 +4,11 @@ import { Server } from 'http' import { createLogger } from '../factories/logger-factory' import { IWebServerAdapter } from '../@types/adapters' -const debug = createLogger('web-server-adapter') +const logger = createLogger('web-server-adapter') export class WebServerAdapter extends EventEmitter implements IWebServerAdapter { public constructor(protected readonly webServer: Server) { - debug('created') + logger('created') super() this.webServer .on('error', this.onError.bind(this)) @@ -18,28 +18,28 @@ export class WebServerAdapter extends EventEmitter implements IWebServerAdapter } public listen(port: number): void { - debug('attempt to listen on port %d', port) + logger('attempt to listen on port %d', port) this.webServer.listen(port) } private onListening() { - debug('listening for incoming connections') + logger('listening for incoming connections') } private onError(error: Error) { - debug.error('web-server-adapter: error:', error) + logger.error('web-server-adapter: error:', error) } private onClientError(error: Error, socket: Duplex) { if (error['code'] === 'ECONNRESET' || !socket.writable) { return } - debug.error('web-server-adapter: client socket error:', error) + logger.error('web-server-adapter: client socket error:', error) socket.end('HTTP/1.1 400 Bad Request\r\nContent-Type: text/html\r\n') } public close(callback?: () => void): void { - debug('closing') + logger('closing') this.webServer.close(() => { this.webServer.removeAllListeners() this.removeAllListeners() @@ -47,10 +47,10 @@ export class WebServerAdapter extends EventEmitter implements IWebServerAdapter callback() } }) - debug('closed') + logger('closed') } protected onClose() { - debug('stopped listening to incoming connections') + logger('stopped listening to incoming connections') } } diff --git a/src/adapters/web-socket-adapter.ts b/src/adapters/web-socket-adapter.ts index 2da59dc4..8232df48 100644 --- a/src/adapters/web-socket-adapter.ts +++ b/src/adapters/web-socket-adapter.ts @@ -22,8 +22,8 @@ import { messageSchema } from '../schemas/message-schema' import { Settings } from '../@types/settings' import { SocketAddress } from 'net' -const debug = createLogger('web-socket-adapter') -const debugHeartbeat = debug.extend('heartbeat') +const logger = createLogger('web-socket-adapter') +const debugHeartbeat = logger.extend('heartbeat') const abortableMessageHandlers: WeakMap = new WeakMap() @@ -57,11 +57,11 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter this.client .on('error', (error) => { if (error.name === 'RangeError' && error.message === 'Max payload size exceeded') { - debug.error(`web-socket-adapter: client ${this.clientId} (${this.getClientAddress()}) sent payload too large`) + logger.error(`web-socket-adapter: client ${this.clientId} (${this.getClientAddress()}) sent payload too large`) } else if (error.name === 'RangeError' && error.message === 'Invalid WebSocket frame: RSV1 must be clear') { - debug(`client ${this.clientId} (${this.getClientAddress()}) enabled compression`) + logger(`client ${this.clientId} (${this.getClientAddress()}) enabled compression`) } else { - debug.error(`web-socket-adapter: client error ${this.clientId} (${this.getClientAddress()}):`, error) + logger.error(`web-socket-adapter: client error ${this.clientId} (${this.getClientAddress()}):`, error) } this.client.close() @@ -78,7 +78,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter .on(WebSocketAdapterEvent.Broadcast, this.onBroadcast.bind(this)) .on(WebSocketAdapterEvent.Message, this.sendMessage.bind(this)) - debug('client %s connected from %s', this.clientId, this.clientAddress.address) + logger('client %s connected from %s', this.clientId, this.clientAddress.address) } public getClientId(): string { @@ -90,12 +90,12 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter } public onUnsubscribed(subscriptionId: string): void { - debug('client %s unsubscribed %s', this.clientId, subscriptionId) + logger('client %s unsubscribed %s', this.clientId, subscriptionId) this.subscriptions.delete(subscriptionId) } public onSubscribed(subscriptionId: string, filters: SubscriptionFilter[]): void { - debug('client %s subscribed %s to %o', this.clientId, subscriptionId, filters) + logger('client %s subscribed %s to %o', this.clientId, subscriptionId, filters) this.subscriptions.set(subscriptionId, filters) } @@ -112,7 +112,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter public onSendEvent(event: Event): void { this.subscriptions.forEach((filters, subscriptionId) => { if (filters.map(isEventMatchingFilter).some((isMatch) => isMatch(event))) { - debug('sending event to client %s: %o', this.clientId, event) + logger('sending event to client %s: %o', this.clientId, event) this.sendMessage(createOutgoingEventMessage(subscriptionId, event)) } }) @@ -127,7 +127,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter public onHeartbeat(): void { if (!this.alive && !this.subscriptions.size) { - debug.error(`web-socket-adapter: pong timeout for client ${this.clientId} (${this.getClientAddress()})`) + logger.error(`web-socket-adapter: pong timeout for client ${this.clientId} (${this.getClientAddress()})`) this.client.close() return } @@ -159,7 +159,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter messageHandler = this.createMessageHandler([message, this]) as IMessageHandler & IAbortable if (!messageHandler) { - debug.error('web-socket-adapter: unhandled message: no handler found:', message) + logger.error('web-socket-adapter: unhandled message: no handler found:', message) return } @@ -175,19 +175,19 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter } catch (error) { if (error instanceof Error) { if (error.name === 'AbortError') { - debug.error(`web-socket-adapter: abort from client ${this.clientId} (${this.getClientAddress()})`) + logger.error(`web-socket-adapter: abort from client ${this.clientId} (${this.getClientAddress()})`) } else if (error.name === 'SyntaxError' || error instanceof ZodError) { - debug('invalid message client %s (%s): %s', this.clientId, this.getClientAddress(), error.message) + logger('invalid message client %s (%s): %s', this.clientId, this.getClientAddress(), error.message) const notice = error instanceof ZodError ? `invalid: ${error.issues[0]?.message ?? error.message}` : `invalid: ${error.message}` this.sendMessage(createNoticeMessage(notice)) } else { - debug.error('web-socket-adapter: unable to handle message:', error) + logger.error('web-socket-adapter: unable to handle message:', error) } } else { - debug.error('web-socket-adapter: unable to handle message:', error) + logger.error('web-socket-adapter: unable to handle message:', error) } } finally { if (abortable && messageHandler) { @@ -218,7 +218,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter const isRateLimited = await hit(period, rate) if (isRateLimited) { - debug('rate limited %s: %d messages / %d ms exceeded', client, rate, period) + logger('rate limited %s: %d messages / %d ms exceeded', client, rate, period) limited = true } @@ -248,7 +248,7 @@ export class WebSocketAdapter extends EventEmitter implements IWebSocketAdapter try { handler.abort() } catch (error) { - debug.error('Unable to abort message handler', error) + logger.error('Unable to abort message handler', error) } } } diff --git a/src/adapters/web-socket-server-adapter.ts b/src/adapters/web-socket-server-adapter.ts index a1246f34..bc93a754 100644 --- a/src/adapters/web-socket-server-adapter.ts +++ b/src/adapters/web-socket-server-adapter.ts @@ -12,7 +12,7 @@ import { isRateLimited } from '../handlers/request-handlers/rate-limiter-middlew import { Settings } from '../@types/settings' import { WebServerAdapter } from './web-server-adapter' -const debug = createLogger('web-socket-server-adapter') +const logger = createLogger('web-socket-server-adapter') const WSS_CLIENT_HEALTH_PROBE_INTERVAL = 120000 @@ -30,7 +30,7 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock >, private readonly settings: () => Settings, ) { - debug('created') + logger('created') super(webServer) this.webSocketsAdapters = new WeakMap() @@ -40,29 +40,29 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock this.webSocketServer .on(WebSocketServerAdapterEvent.Connection, this.onConnection.bind(this)) .on('error', (error) => { - debug('error: %o', error) + logger('error: %o', error) }) this.heartbeatInterval = setInterval(this.onHeartbeat.bind(this), WSS_CLIENT_HEALTH_PROBE_INTERVAL) } public close(callback?: () => void): void { super.close(() => { - debug('closing') + logger('closing') clearInterval(this.heartbeatInterval) this.webSocketServer.clients.forEach((webSocket: WebSocket) => { const webSocketAdapter = this.webSocketsAdapters.get(webSocket) if (webSocketAdapter) { - debug('terminating client %s: %s', webSocketAdapter.getClientId(), webSocketAdapter.getClientAddress()) + logger('terminating client %s: %s', webSocketAdapter.getClientId(), webSocketAdapter.getClientAddress()) } webSocket.terminate() }) - debug('closing web socket server') + logger('closing web socket server') this.webSocketServer.close(() => { this.webSocketServer.removeAllListeners() if (typeof callback !== 'undefined') { callback() } - debug('closed') + logger('closed') }) }) this.removeAllListeners() @@ -89,10 +89,10 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock const currentSettings = this.settings() const remoteAddress = getRemoteAddress(req, currentSettings) - debug('client %s connected: %o', remoteAddress, req.headers) + logger('client %s connected: %o', remoteAddress, req.headers) if (await isRateLimited(remoteAddress, currentSettings)) { - debug('client %s terminated: rate-limited', remoteAddress) + logger('client %s terminated: rate-limited', remoteAddress) client.terminate() return } diff --git a/src/app/app.ts b/src/app/app.ts index 6bb6ea6c..9ac4bd94 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -11,7 +11,7 @@ import { Serializable } from 'child_process' import { Settings } from '../@types/settings' import { SettingsStatic } from '../utils/settings' -const debug = createLogger('app-primary') +const logger = createLogger('app-primary') export class App implements IRunnable { private workers: WeakMap> @@ -22,7 +22,7 @@ export class App implements IRunnable { private readonly cluster: Cluster, private readonly settings: () => Settings, ) { - debug('starting') + logger('starting') this.workers = new WeakMap() @@ -30,13 +30,13 @@ export class App implements IRunnable { this.process.on('SIGTERM', this.onExit.bind(this)) - debug('started') + logger('started') } public run(): void { const settings = this.settings() this.watchers = SettingsStatic.watchSettings() - debug.info(` + logger.info(` ███▄ █ ▒█████ ██████ ▄▄▄█████▓ ██▀███ ▓█████ ▄▄▄ ███▄ ▄███▓ ██ ▀█ █ ▒██▒ ██▒▒██ ▒ ▓ ██▒ ▓▒▓██ ▒ ██▒▓█ ▀▒████▄ ▓██▒▀█▀ ██▒ ▓██ ▀█ ██▒▒██░ ██▒░ ▓██▄ ▒ ▓██░ ▒░▓██ ░▄█ ▒▒███ ▒██ ▀█▄ ▓██ ▓██░ @@ -52,7 +52,7 @@ export class App implements IRunnable { const logCentered = (input: string, width: number) => { const start = (width - input.length) >> 1 - debug.info(' '.repeat(start), input) + logger.info(' '.repeat(start), input) } logCentered(`v${packageJson.version}`, width) logCentered(`NIPs implemented: ${packageJson.supportedNips}`, width) @@ -68,7 +68,7 @@ export class App implements IRunnable { this.process.env.SECRET === '' || this.process.env.SECRET === 'changeme') ) { - debug.error('Please configure the secret using the SECRET environment variable.') + logger.error('Please configure the secret using the SECRET environment variable.') this.process.exit(1) } @@ -82,7 +82,7 @@ export class App implements IRunnable { } for (let i = 0; i < workerCount; i++) { - debug('starting worker') + logger('starting worker') createWorker({ WORKER_TYPE: 'worker', WORKER_INDEX: i.toString(), @@ -107,7 +107,7 @@ export class App implements IRunnable { logCentered(`${mirrors.length} static-mirroring worker started`, width) } - debug('settings: %O', settings) + logger('settings: %O', settings) const host = `${hostname()}:${port}` addOnion(torHiddenServicePort, host).then( @@ -121,25 +121,25 @@ export class App implements IRunnable { } private onClusterMessage(source: Worker, message: Serializable) { - debug('message received from worker %s: %o', source.process.pid, message) + logger('message received from worker %s: %o', source.process.pid, message) for (const worker of Object.values(this.cluster.workers as any) as Worker[]) { if (source.id === worker.id) { continue } - debug('sending message to worker %s: %o', worker.process.pid, message) + logger('sending message to worker %s: %o', worker.process.pid, message) worker.send(message) } } private onClusterExit(deadWorker: Worker, code: number, signal: string) { - debug('worker %s died', deadWorker.process.pid) + logger('worker %s died', deadWorker.process.pid) if (code === 0 || signal === 'SIGINT') { return } setTimeout(() => { - debug('starting worker') + logger('starting worker') const workerEnv = this.workers.get(deadWorker) if (!workerEnv) { throw new Error('Mistakes were made') @@ -147,19 +147,19 @@ export class App implements IRunnable { const newWorker = this.cluster.fork(workerEnv) this.workers.set(newWorker, workerEnv) - debug('started worker %s', newWorker.process.pid) + logger('started worker %s', newWorker.process.pid) }, 10000) } private onExit() { - debug.info('exiting') + logger.info('exiting') this.close(() => { this.process.exit(0) }) } public close(callback?: (...args: any[]) => void): void { - debug.info('close') + logger.info('close') if (Array.isArray(this.watchers)) { for (const watcher of this.watchers) { watcher.close() diff --git a/src/app/maintenance-worker.ts b/src/app/maintenance-worker.ts index 0e93dbf7..f0a87499 100644 --- a/src/app/maintenance-worker.ts +++ b/src/app/maintenance-worker.ts @@ -19,7 +19,7 @@ const UPDATE_INVOICE_INTERVAL = 60000 const NIP05_REVERIFICATION_BATCH_SIZE = 50 const CLEAR_OLD_EVENTS_TIMEOUT_MS = 5000 -const debug = createLogger('maintenance-worker') +const logger = createLogger('maintenance-worker') /** * Merge a re-verification outcome onto an existing verification row. @@ -94,14 +94,14 @@ export class MaintenanceWorker implements IRunnable { }), ]) } catch (error) { - debug('unable to clear old events: %o', error) + logger('unable to clear old events: %o', error) } } public run(): void { this.interval = setInterval(async () => { if (this.isRunning) { - debug('skipping scheduled maintenance run because previous run is still in progress') + logger('skipping scheduled maintenance run because previous run is still in progress') return } @@ -128,17 +128,17 @@ export class MaintenanceWorker implements IRunnable { } const invoices = await this.paymentsService.getPendingInvoices() - debug('found %d pending invoices', invoices.length) + logger('found %d pending invoices', invoices.length) const delay = () => delayMs(100 + Math.floor(Math.random() * 10)) let successful = 0 for (const invoice of invoices) { try { - debug('getting invoice %s from payment processor: %o', invoice.id, invoice) + logger('getting invoice %s from payment processor: %o', invoice.id, invoice) const updatedInvoice = await this.paymentsService.getInvoiceFromPaymentsProcessor(invoice) await delay() - debug('updating invoice status %s: %o', updatedInvoice.id, updatedInvoice) + logger('updating invoice status %s: %o', updatedInvoice.id, updatedInvoice) if (typeof updatedInvoice.id !== 'string' || typeof updatedInvoice.status !== 'string') { continue @@ -152,7 +152,7 @@ export class MaintenanceWorker implements IRunnable { updatedInvoice.status == InvoiceStatus.COMPLETED && updatedInvoice.confirmedAt ) { - debug('confirming invoice %s & notifying %s', invoice.id, invoice.pubkey) + logger('confirming invoice %s & notifying %s', invoice.id, invoice.pubkey) const update = pipe( mergeDeepLeft(updatedInvoice), @@ -168,10 +168,10 @@ export class MaintenanceWorker implements IRunnable { } successful++ } catch (error) { - debug.error('Unable to update invoice from payment processor. Reason:', error) + logger.error('Unable to update invoice from payment processor. Reason:', error) } - debug('updated %d of %d invoices successfully', successful, invoices.length) + logger('updated %d of %d invoices successfully', successful, invoices.length) } await clearOldEventsPromise @@ -197,7 +197,7 @@ export class MaintenanceWorker implements IRunnable { return } - debug('found %d NIP-05 verifications to re-check', pendingVerifications.length) + logger('found %d NIP-05 verifications to re-check', pendingVerifications.length) for (const verification of pendingVerifications) { try { @@ -206,28 +206,28 @@ export class MaintenanceWorker implements IRunnable { await this.nip05VerificationRepository.upsert(updated) await delayMs(200 + Math.floor(Math.random() * 100)) } catch (error) { - debug('failed to re-verify NIP-05 for %s: %o', verification.pubkey, error) + logger('failed to re-verify NIP-05 for %s: %o', verification.pubkey, error) } } } catch (error) { - debug('NIP-05 re-verification batch failed: %o', error) + logger('NIP-05 re-verification batch failed: %o', error) } } private onError(error: Error) { - debug('error: %o', error) + logger('error: %o', error) throw error } private onExit() { - debug('exiting') + logger('exiting') this.close(() => { this.process.exit(0) }) } public close(callback?: () => void) { - debug('closing') + logger('closing') clearInterval(this.interval) if (typeof callback === 'function') { callback() diff --git a/src/app/static-mirroring-worker.ts b/src/app/static-mirroring-worker.ts index 346c0dfd..c2ec083e 100644 --- a/src/app/static-mirroring-worker.ts +++ b/src/app/static-mirroring-worker.ts @@ -29,7 +29,7 @@ import { OutgoingEventMessage } from '../@types/messages' import { RelayedEvent } from '../@types/event' import { WebSocketServerAdapterEvent } from '../constants/adapter' -const debug = createLogger('static-mirror-worker') +const logger = createLogger('static-mirror-worker') export class StaticMirroringWorker implements IRunnable { private client: WebSocket | undefined @@ -53,7 +53,7 @@ export class StaticMirroringWorker implements IRunnable { public run(): void { const currentSettings = this.settings() - debug.info('mirroring', currentSettings.mirroring) + logger.info('mirroring', currentSettings.mirroring) this.config = path(['mirroring', 'static', process.env.MIRROR_INDEX], currentSettings) as Mirror @@ -62,16 +62,16 @@ export class StaticMirroringWorker implements IRunnable { const createMirror = (config: Mirror) => { const subscriptionId = `mirror-${randomUUID()}` - debug('connecting to %s', config.address) + logger('connecting to %s', config.address) return new WebSocket(config.address, { timeout: 5000 }) .on('open', function () { - debug('connected to %s', config.address) + logger('connected to %s', config.address) if (Array.isArray(config.filters) && config.filters?.length) { const filters = config.filters.map((filter) => ({ ...filter, since })) - debug('subscribing with %s: %o', subscriptionId, filters) + logger('subscribing with %s: %o', subscriptionId, filters) this.send(JSON.stringify(createSubscriptionMessage(subscriptionId, filters))) } @@ -85,7 +85,7 @@ export class StaticMirroringWorker implements IRunnable { } if (message[0] !== 'EVENT' || message[1] !== subscriptionId) { - debug('%s >> local: %o', config.address, message) + logger('%s >> local: %o', config.address, message) return } @@ -126,7 +126,7 @@ export class StaticMirroringWorker implements IRunnable { since = Math.floor(Date.now() / 1000) - 30 - debug('%s >> local: %s', config.address, event.id) + logger('%s >> local: %s', config.address, event.id) const inserted = await this.eventRepository.create(event) @@ -138,11 +138,11 @@ export class StaticMirroringWorker implements IRunnable { }) } } catch (error) { - debug('unable to process message: %o', error) + logger('unable to process message: %o', error) } }) .on('close', (code, reason) => { - debug(`disconnected (${code}): ${reason.toString()}`) + logger(`disconnected (${code}): ${reason.toString()}`) setTimeout(() => { this.client.removeAllListeners() @@ -150,7 +150,7 @@ export class StaticMirroringWorker implements IRunnable { }, 5000) }) .on('error', function (error) { - debug('connection error: %o', error) + logger('connection error: %o', error) }) } @@ -164,7 +164,7 @@ export class StaticMirroringWorker implements IRunnable { private canAcceptEvent(event: Event): boolean { if (this.getRelayPublicKey() === event.pubkey) { - debug(`event ${event.id} not accepted: pubkey is relay pubkey`) + logger(`event ${event.id} not accepted: pubkey is relay pubkey`) return false } @@ -184,7 +184,7 @@ export class StaticMirroringWorker implements IRunnable { event.content.length > limit.maxLength && (!Array.isArray(limit.kinds) || limit.kinds.some(isEventKindOrRangeMatch(event))) ) { - debug(`event ${event.id} not accepted: content is longer than ${limit.maxLength} bytes`) + logger(`event ${event.id} not accepted: content is longer than ${limit.maxLength} bytes`) return false } } @@ -194,7 +194,7 @@ export class StaticMirroringWorker implements IRunnable { event.content.length > limits.content.maxLength && (!Array.isArray(limits.content.kinds) || limits.content.kinds.some(isEventKindOrRangeMatch(event))) ) { - debug(`event ${event.id} not accepted: content is longer than ${limits.content.maxLength} bytes`) + logger(`event ${event.id} not accepted: content is longer than ${limits.content.maxLength} bytes`) return false } @@ -203,7 +203,7 @@ export class StaticMirroringWorker implements IRunnable { limits.createdAt.maxPositiveDelta > 0 && event.created_at > now + limits.createdAt.maxPositiveDelta ) { - debug( + logger( `event ${event.id} not accepted: created_at is more than ${limits.createdAt.maxPositiveDelta} seconds in the future`, ) return false @@ -214,7 +214,7 @@ export class StaticMirroringWorker implements IRunnable { limits.createdAt.maxNegativeDelta > 0 && event.created_at < now - limits.createdAt.maxNegativeDelta ) { - debug( + logger( `event ${event.id} not accepted: created_at is more than ${limits.createdAt.maxNegativeDelta} seconds in the past`, ) return false @@ -223,7 +223,7 @@ export class StaticMirroringWorker implements IRunnable { if (typeof limits.eventId?.minLeadingZeroBits !== 'undefined' && limits.eventId.minLeadingZeroBits > 0) { const pow = getEventProofOfWork(event.id) if (pow < limits.eventId.minLeadingZeroBits) { - debug(`event ${event.id} not accepted: pow difficulty ${pow}<${limits.eventId.minLeadingZeroBits}`) + logger(`event ${event.id} not accepted: pow difficulty ${pow}<${limits.eventId.minLeadingZeroBits}`) return false } } @@ -231,7 +231,7 @@ export class StaticMirroringWorker implements IRunnable { if (typeof limits.pubkey?.minLeadingZeroBits !== 'undefined' && limits.pubkey.minLeadingZeroBits > 0) { const pow = getPubkeyProofOfWork(event.pubkey) if (pow < limits.pubkey.minLeadingZeroBits) { - debug(`event ${event.id} not accepted: pow pubkey difficulty ${pow}<${limits.pubkey.minLeadingZeroBits}`) + logger(`event ${event.id} not accepted: pow pubkey difficulty ${pow}<${limits.pubkey.minLeadingZeroBits}`) return false } } @@ -241,7 +241,7 @@ export class StaticMirroringWorker implements IRunnable { limits.pubkey.whitelist.length > 0 && !limits.pubkey.whitelist.some((prefix) => event.pubkey.startsWith(prefix)) ) { - debug(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`) + logger(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`) return false } @@ -250,7 +250,7 @@ export class StaticMirroringWorker implements IRunnable { limits.pubkey.blacklist.length > 0 && limits.pubkey.blacklist.some((prefix) => event.pubkey.startsWith(prefix)) ) { - debug(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`) + logger(`event ${event.id} not accepted: pubkey not allowed: ${event.pubkey}`) return false } @@ -259,7 +259,7 @@ export class StaticMirroringWorker implements IRunnable { limits.kind.whitelist.length > 0 && !limits.kind.whitelist.some(isEventKindOrRangeMatch(event)) ) { - debug(`blocked: event kind ${event.kind} not allowed`) + logger(`blocked: event kind ${event.kind} not allowed`) return false } @@ -268,7 +268,7 @@ export class StaticMirroringWorker implements IRunnable { limits.kind.blacklist.length > 0 && limits.kind.blacklist.some(isEventKindOrRangeMatch(event)) ) { - debug(`blocked: event kind ${event.kind} not allowed`) + logger(`blocked: event kind ${event.kind} not allowed`) return false } @@ -299,13 +299,13 @@ export class StaticMirroringWorker implements IRunnable { const user = await this.userRepository.findByPubkey(event.pubkey) if (user?.isAdmitted !== true) { - debug(`user not admitted: ${event.pubkey}`) + logger(`user not admitted: ${event.pubkey}`) return false } const minBalance = currentSettings.limits?.event?.pubkey?.minBalance if (minBalance && user.balance < minBalance) { - debug(`user not admitted: user balance ${user.balance} < ${minBalance}`) + logger(`user not admitted: user balance ${user.balance} < ${minBalance}`) return false } @@ -326,24 +326,24 @@ export class StaticMirroringWorker implements IRunnable { const eventToRelay = createRelayedEventMessage(event, this.config.secret) const outboundMessage = JSON.stringify(eventToRelay) - debug('%s >> %s: %s', message.source ?? 'local', this.config.address, outboundMessage) + logger('%s >> %s: %s', message.source ?? 'local', this.config.address, outboundMessage) this.client.send(outboundMessage) } private onError(error: Error) { - debug('error: %o', error) + logger('error: %o', error) throw error } private onExit() { - debug('exiting') + logger('exiting') this.close(() => { this.process.exit(0) }) } public close(callback?: () => void) { - debug('closing') + logger('closing') if (this.client) { this.client.terminate() } diff --git a/src/app/worker.ts b/src/app/worker.ts index 4e75d5ab..b6e93e38 100644 --- a/src/app/worker.ts +++ b/src/app/worker.ts @@ -6,7 +6,7 @@ import { createLogger } from '../factories/logger-factory' import { FSWatcher } from 'fs' import { SettingsStatic } from '../utils/settings' -const debug = createLogger('app-worker') +const logger = createLogger('app-worker') export class AppWorker implements IRunnable { private watchers: FSWatcher[] | undefined @@ -36,23 +36,23 @@ export class AppWorker implements IRunnable { private onError(error: Error) { if (error.name === 'TypeError' && error.message === "Cannot read properties of undefined (reading '__knexUid')") { - debug.error( + logger.error( 'Unable to acquire connection. Please increase DB_MAX_POOL_SIZE, DB_ACQUIRE_CONNECTION_TIMEOUT and tune postgresql.conf to make use of server\'s resources.' ) return } - debug.error('uncaught error:', error) + logger.error('uncaught error:', error) } private onExit() { - debug('exiting') + logger('exiting') this.close(() => { this.process.exit(0) }) } public close(callback?: () => void) { - debug('closing') + logger('closing') if (Array.isArray(this.watchers)) { for (const watcher of this.watchers) { watcher.close() @@ -62,6 +62,6 @@ export class AppWorker implements IRunnable { await closeCacheClient() callback?.() }) - debug('closed') + logger('closed') } } diff --git a/src/cache/client.ts b/src/cache/client.ts index b73c1fc6..d9064657 100644 --- a/src/cache/client.ts +++ b/src/cache/client.ts @@ -2,7 +2,7 @@ import { createClient, RedisClientOptions } from 'redis' import { CacheClient } from '../@types/cache' import { createLogger } from '../factories/logger-factory' -const debug = createLogger('cache-client') +const logger = createLogger('cache-client') export const getCacheConfig = (): RedisClientOptions => ({ url: process.env.REDIS_URI @@ -18,7 +18,7 @@ export const getCacheClient = (): CacheClient => { const config = getCacheConfig() // eslint-disable-next-line @typescript-eslint/no-unused-vars const { password: _, ...loggableConfig } = config - debug('config: %o', loggableConfig) + logger('config: %o', loggableConfig) instance = createClient(config) } diff --git a/src/controllers/admission/get-admission-check-controller.ts b/src/controllers/admission/get-admission-check-controller.ts index 0fafb668..f68e8025 100644 --- a/src/controllers/admission/get-admission-check-controller.ts +++ b/src/controllers/admission/get-admission-check-controller.ts @@ -7,7 +7,7 @@ import { IUserRepository } from '../../@types/repositories' import { path } from 'ramda' import { Settings } from '../../@types/settings' -const debug = createLogger('get-admission-check-controller') +const logger = createLogger('get-admission-check-controller') export class GetSubmissionCheckController implements IController { public constructor( @@ -54,7 +54,7 @@ export class GetSubmissionCheckController implements IController { const rateLimiter = this.rateLimiter() for (const { rate, period } of rateLimits) { if (await rateLimiter.hit(`${remoteAddress}:admission-check:${period}`, 1, { period, rate })) { - debug('rate limited %s: %d in %d milliseconds', remoteAddress, rate, period) + logger('rate limited %s: %d in %d milliseconds', remoteAddress, rate, period) limited = true } } diff --git a/src/controllers/callbacks/lnbits-callback-controller.ts b/src/controllers/callbacks/lnbits-callback-controller.ts index a5845ded..30fdae34 100644 --- a/src/controllers/callbacks/lnbits-callback-controller.ts +++ b/src/controllers/callbacks/lnbits-callback-controller.ts @@ -11,7 +11,7 @@ import { IInvoiceRepository } from '../../@types/repositories' import { IPaymentsService } from '../../@types/services' import { validateSchema } from '../../utils/validation' -const debug = createLogger('lnbits-callback-controller') +const logger = createLogger('lnbits-callback-controller') export class LNbitsCallbackController implements IController { public constructor( @@ -20,22 +20,22 @@ export class LNbitsCallbackController implements IController { ) {} public async handleRequest(request: Request, response: Response) { - debug('request headers: %o', request.headers) - debug('request body: %o', request.body) + logger('request headers: %o', request.headers) + logger('request body: %o', request.body) const settings = createSettings() const remoteAddress = getRemoteAddress(request, settings) const paymentProcessor = settings.payments?.processor ?? 'null' if (paymentProcessor !== 'lnbits') { - debug('denied request from %s to /callbacks/lnbits which is not the current payment processor', remoteAddress) + logger('denied request from %s to /callbacks/lnbits which is not the current payment processor', remoteAddress) response.status(403).send('Forbidden') return } const queryValidation = validateSchema(lnbitsCallbackQuerySchema)(request.query) if (queryValidation.error) { - debug('unauthorized request from %s to /callbacks/lnbits: invalid query %o', remoteAddress, queryValidation.error) + logger('unauthorized request from %s to /callbacks/lnbits: invalid query %o', remoteAddress, queryValidation.error) response.status(403).send('Forbidden') return } @@ -52,7 +52,7 @@ export class LNbitsCallbackController implements IController { !hasValidExpiry || expiry <= Date.now() ) { - debug('unauthorized request from %s to /callbacks/lnbits: hmac signature mismatch or expired', remoteAddress) + logger('unauthorized request from %s to /callbacks/lnbits: hmac signature mismatch or expired', remoteAddress) response.status(403).send('Forbidden') return } @@ -75,7 +75,7 @@ export class LNbitsCallbackController implements IController { try { await this.paymentsService.updateInvoice(invoice) } catch (error) { - debug.error(`Unable to persist invoice ${invoice.id}`, error) + logger.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -97,7 +97,7 @@ export class LNbitsCallbackController implements IController { await this.paymentsService.confirmInvoice(invoice as Invoice) await this.paymentsService.sendInvoiceUpdateNotification(invoice as Invoice) } catch (error) { - debug.error(`Unable to confirm invoice ${invoice.id}`, error) + logger.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/callbacks/nodeless-callback-controller.ts b/src/controllers/callbacks/nodeless-callback-controller.ts index bf00aca1..72f01e4c 100644 --- a/src/controllers/callbacks/nodeless-callback-controller.ts +++ b/src/controllers/callbacks/nodeless-callback-controller.ts @@ -11,18 +11,18 @@ import { IPaymentsService } from '../../@types/services' import { nodelessCallbackBodySchema } from '../../schemas/nodeless-callback-schema' import { validateSchema } from '../../utils/validation' -const debug = createLogger('nodeless-callback-controller') +const logger = createLogger('nodeless-callback-controller') export class NodelessCallbackController implements IController { public constructor(private readonly paymentsService: IPaymentsService) {} public async handleRequest(request: Request, response: Response) { - debug('callback request headers: %o', request.headers) - debug('callback request body: %O', request.body) + logger('callback request headers: %o', request.headers) + logger('callback request body: %O', request.body) const bodyValidation = validateSchema(nodelessCallbackBodySchema)(request.body) if (bodyValidation.error) { - debug('nodeless callback request rejected: invalid body %o', bodyValidation.error) + logger('nodeless callback request rejected: invalid body %o', bodyValidation.error) response .status(400) .setHeader('content-type', 'application/json; charset=utf8') @@ -37,13 +37,13 @@ export class NodelessCallbackController implements IController { const actual = request.headers['nodeless-signature'] if (expected !== actual) { - debug.error('nodeless callback request rejected: signature mismatch:', { expected, actual }) + logger.error('nodeless callback request rejected: signature mismatch:', { expected, actual }) response.status(403).send('Forbidden') return } if (paymentProcessor !== 'nodeless') { - debug('denied request from %s to /callbacks/nodeless which is not the current payment processor') + logger('denied request from %s to /callbacks/nodeless which is not the current payment processor') response.status(403).send('Forbidden') return } @@ -57,18 +57,18 @@ export class NodelessCallbackController implements IController { createdAt: ifElse(propSatisfies(is(String), 'createdAt'), prop('createdAt'), path(['metadata', 'createdAt'])), })(request.body) - debug('nodeless invoice: %O', nodelessInvoice) + logger('nodeless invoice: %O', nodelessInvoice) const invoice = fromNodelessInvoice(nodelessInvoice) - debug('invoice: %O', invoice) + logger('invoice: %O', invoice) let updatedInvoice: Invoice try { updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice) - debug('updated invoice: %O', updatedInvoice) + logger('updated invoice: %O', updatedInvoice) } catch (error) { - debug.error(`Unable to persist invoice ${invoice.id}`, error) + logger.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -86,7 +86,7 @@ export class NodelessCallbackController implements IController { await this.paymentsService.confirmInvoice(invoice) await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice) } catch (error) { - debug.error(`Unable to confirm invoice ${invoice.id}`, error) + logger.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/callbacks/opennode-callback-controller.ts b/src/controllers/callbacks/opennode-callback-controller.ts index a854dc0f..66eb7981 100644 --- a/src/controllers/callbacks/opennode-callback-controller.ts +++ b/src/controllers/callbacks/opennode-callback-controller.ts @@ -12,20 +12,20 @@ import { IPaymentsService } from '../../@types/services' import { opennodeWebhookCallbackBodySchema } from '../../schemas/opennode-callback-schema' import { validateSchema } from '../../utils/validation' -const debug = createLogger('opennode-callback-controller') +const logger = createLogger('opennode-callback-controller') export class OpenNodeCallbackController implements IController { public constructor(private readonly paymentsService: IPaymentsService) {} public async handleRequest(request: Request, response: Response) { - debug('request headers: %o', request.headers) + logger('request headers: %o', request.headers) const settings = createSettings() const remoteAddress = getRemoteAddress(request, settings) const paymentProcessor = settings.payments?.processor if (paymentProcessor !== 'opennode') { - debug('denied request from %s to /callbacks/opennode which is not the current payment processor', remoteAddress) + logger('denied request from %s to /callbacks/opennode which is not the current payment processor', remoteAddress) response .status(403) .send('Forbidden') @@ -34,13 +34,13 @@ export class OpenNodeCallbackController implements IController { const bodyValidation = validateSchema(opennodeWebhookCallbackBodySchema)(request.body) if (bodyValidation.error) { - debug('opennode callback request rejected: invalid body %o', bodyValidation.error) + logger('opennode callback request rejected: invalid body %o', bodyValidation.error) response.status(400).setHeader('content-type', 'text/plain; charset=utf8').send('Malformed body') return } const body = bodyValidation.value - debug( + logger( 'request body metadata: hasId=%s hasHashedOrder=%s status=%s', typeof body.id === 'string', typeof body.hashed_order === 'string', @@ -49,7 +49,7 @@ export class OpenNodeCallbackController implements IController { const openNodeApiKey = process.env.OPENNODE_API_KEY if (!openNodeApiKey) { - debug('OPENNODE_API_KEY is not configured; unable to verify OpenNode callback from %s', remoteAddress) + logger('OPENNODE_API_KEY is not configured; unable to verify OpenNode callback from %s', remoteAddress) response .status(500) .setHeader('content-type', 'text/plain; charset=utf8') @@ -65,7 +65,7 @@ export class OpenNodeCallbackController implements IController { actualHex.length !== expectedHexLength || !/^[0-9a-f]+$/i.test(actualHex) ) { - debug('invalid hashed_order format from %s to /callbacks/opennode', remoteAddress) + logger('invalid hashed_order format from %s to /callbacks/opennode', remoteAddress) response .status(400) .setHeader('content-type', 'text/plain; charset=utf8') @@ -78,7 +78,7 @@ export class OpenNodeCallbackController implements IController { if ( !timingSafeEqual(expectedBuf, actualBuf) ) { - debug('unauthorized request from %s to /callbacks/opennode: hashed_order mismatch', remoteAddress) + logger('unauthorized request from %s to /callbacks/opennode: hashed_order mismatch', remoteAddress) response .status(403) .send('Forbidden') @@ -99,13 +99,13 @@ export class OpenNodeCallbackController implements IController { status: statusMap[body.status], } - debug('invoice', invoice) + logger('invoice', invoice) let updatedInvoice: Invoice try { updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice) } catch (error) { - debug.error(`Unable to persist invoice ${invoice.id}`, error) + logger.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -133,7 +133,7 @@ export class OpenNodeCallbackController implements IController { }) await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice) } catch (error) { - debug.error(`Unable to confirm invoice ${invoice.id}`, error) + logger.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/callbacks/zebedee-callback-controller.ts b/src/controllers/callbacks/zebedee-callback-controller.ts index 486c055d..e9c448a3 100644 --- a/src/controllers/callbacks/zebedee-callback-controller.ts +++ b/src/controllers/callbacks/zebedee-callback-controller.ts @@ -10,18 +10,18 @@ import { IPaymentsService } from '../../@types/services' import { validateSchema } from '../../utils/validation' import { zebedeeCallbackBodySchema } from '../../schemas/zebedee-callback-schema' -const debug = createLogger('zebedee-callback-controller') +const logger = createLogger('zebedee-callback-controller') export class ZebedeeCallbackController implements IController { public constructor(private readonly paymentsService: IPaymentsService) {} public async handleRequest(request: Request, response: Response) { - debug('request headers: %o', request.headers) - debug('request body: %O', request.body) + logger('request headers: %o', request.headers) + logger('request body: %O', request.body) const bodyValidation = validateSchema(zebedeeCallbackBodySchema)(request.body) if (bodyValidation.error) { - debug('zebedee callback request rejected: invalid body %o', bodyValidation.error) + logger('zebedee callback request rejected: invalid body %o', bodyValidation.error) response.status(400).setHeader('content-type', 'text/plain; charset=utf8').send('Malformed body') return } @@ -33,26 +33,26 @@ export class ZebedeeCallbackController implements IController { const paymentProcessor = settings.payments?.processor if (ipWhitelist.length && !ipWhitelist.includes(remoteAddress)) { - debug('unauthorized request from %s to /callbacks/zebedee', remoteAddress) + logger('unauthorized request from %s to /callbacks/zebedee', remoteAddress) response.status(403).send('Forbidden') return } if (paymentProcessor !== 'zebedee') { - debug('denied request from %s to /callbacks/zebedee which is not the current payment processor', remoteAddress) + logger('denied request from %s to /callbacks/zebedee which is not the current payment processor', remoteAddress) response.status(403).send('Forbidden') return } const invoice = fromZebedeeInvoice(request.body) - debug('invoice', invoice) + logger('invoice', invoice) let updatedInvoice: Invoice try { updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice) } catch (error) { - debug.error(`Unable to persist invoice ${invoice.id}`, error) + logger.error(`Unable to persist invoice ${invoice.id}`, error) throw error } @@ -77,7 +77,7 @@ export class ZebedeeCallbackController implements IController { }) await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice) } catch (error) { - debug.error(`Unable to confirm invoice ${invoice.id}`, error) + logger.error(`Unable to confirm invoice ${invoice.id}`, error) throw error } diff --git a/src/controllers/invoices/get-invoice-status-controller.ts b/src/controllers/invoices/get-invoice-status-controller.ts index 92114b49..73aa6660 100644 --- a/src/controllers/invoices/get-invoice-status-controller.ts +++ b/src/controllers/invoices/get-invoice-status-controller.ts @@ -3,7 +3,7 @@ import { createLogger } from '../../factories/logger-factory' import { IController } from '../../@types/controllers' import { IInvoiceRepository } from '../../@types/repositories' -const debug = createLogger('get-invoice-status-controller') +const logger = createLogger('get-invoice-status-controller') export class GetInvoiceStatusController implements IController { public constructor(private readonly invoiceRepository: IInvoiceRepository) {} @@ -11,7 +11,7 @@ export class GetInvoiceStatusController implements IController { public async handleRequest(request: Request, response: Response): Promise { const invoiceId = request.params.invoiceId if (typeof invoiceId !== 'string' || !invoiceId) { - debug('invalid invoice id: %s', invoiceId) + logger('invalid invoice id: %s', invoiceId) response .status(400) .setHeader('content-type', 'application/json; charset=utf8') @@ -20,11 +20,11 @@ export class GetInvoiceStatusController implements IController { } try { - debug('fetching invoice: %s', invoiceId) + logger('fetching invoice: %s', invoiceId) const invoice = await this.invoiceRepository.findById(invoiceId) if (!invoice) { - debug('invoice not found: %s', invoiceId) + logger('invoice not found: %s', invoiceId) response .status(404) .setHeader('content-type', 'application/json; charset=utf8') @@ -37,7 +37,7 @@ export class GetInvoiceStatusController implements IController { status: invoice.status, }) } catch (error) { - debug.error(`get-invoice-status-controller: unable to get invoice ${invoiceId}:`, error) + logger.error(`get-invoice-status-controller: unable to get invoice ${invoiceId}:`, error) response .status(500) diff --git a/src/controllers/invoices/post-invoice-controller.ts b/src/controllers/invoices/post-invoice-controller.ts index 81426e83..59239d2d 100644 --- a/src/controllers/invoices/post-invoice-controller.ts +++ b/src/controllers/invoices/post-invoice-controller.ts @@ -17,7 +17,7 @@ import { getPublicKey, getRelayPrivateKey } from '../../utils/event' import { getRemoteAddress } from '../../utils/http' import { getTemplate } from '../../utils/template-cache' -const debug = createLogger('post-invoice-controller') +const logger = createLogger('post-invoice-controller') export class PostInvoiceController implements IController { public constructor( @@ -28,8 +28,8 @@ export class PostInvoiceController implements IController { ) {} public async handleRequest(request: Request, response: Response): Promise { - debug('params: %o', request.params) - debug('body: %o', request.body) + logger('params: %o', request.params) + logger('body: %o', request.body) const currentSettings = this.settings() @@ -115,7 +115,7 @@ export class PostInvoiceController implements IController { invoice = await this.paymentsService.createInvoice(pubkey, amount, description) } catch (error) { - debug.error('Unable to create invoice. Reason:', error) + logger.error('Unable to create invoice. Reason:', error) response .status(500) .setHeader('content-type', 'text/plain') @@ -166,7 +166,7 @@ export class PostInvoiceController implements IController { const rateLimiter = this.rateLimiter() for (const { rate, period } of rateLimits) { if (await rateLimiter.hit(`${remoteAddress}:invoice:${period}`, 1, { period, rate })) { - debug('rate limited %s: %d in %d milliseconds', remoteAddress, rate, period) + logger('rate limited %s: %d in %d milliseconds', remoteAddress, rate, period) limited = true } } diff --git a/src/database/client.ts b/src/database/client.ts index 5ba1813e..86cbdd7f 100644 --- a/src/database/client.ts +++ b/src/database/client.ts @@ -81,10 +81,10 @@ const getReadReplicaConfig = (): Knex.Config => { let writeClient: Knex export const getMasterDbClient = () => { - const debug = createLogger('database-client:get-db-client') + const logger = createLogger('database-client:get-db-client') if (!writeClient) { const config = getMasterConfig() - debug('config: %o', config) + logger('config: %o', config) writeClient = knex(config) } @@ -98,10 +98,10 @@ export const getReadReplicaDbClient = () => { return getMasterDbClient() } - const debug = createLogger('database-client:get-read-replica-db-client') + const logger = createLogger('database-client:get-read-replica-db-client') if (!readClient) { const config = getReadReplicaConfig() - debug('config: %o', config) + logger('config: %o', config) readClient = knex(config) } diff --git a/src/factories/payments-processor-factory.ts b/src/factories/payments-processor-factory.ts index b26f045f..cf73ebc0 100644 --- a/src/factories/payments-processor-factory.ts +++ b/src/factories/payments-processor-factory.ts @@ -8,10 +8,10 @@ import { createZebedeePaymentsProcessor } from './payments-processors/zebedee-pa import { IPaymentsProcessor } from '../@types/clients' import { NullPaymentsProcessor } from '../payments-processors/null-payments-processor' -const debug = createLogger('create-payments-processor') +const logger = createLogger('create-payments-processor') export const createPaymentsProcessor = (): IPaymentsProcessor => { - debug('create payments processor') + logger('create payments processor') const settings = createSettings() if (!settings.payments?.enabled) { diff --git a/src/factories/payments-processors/lnbits-payments-processor-factory.ts b/src/factories/payments-processors/lnbits-payments-processor-factory.ts index 08aac899..7748101e 100644 --- a/src/factories/payments-processors/lnbits-payments-processor-factory.ts +++ b/src/factories/payments-processors/lnbits-payments-processor-factory.ts @@ -7,7 +7,7 @@ import { IPaymentsProcessor } from '../../@types/clients' import { LNbitsPaymentsProcessor } from '../../payments-processors/lnbits-payment-processor' import { Settings } from '../../@types/settings' -const debug = createLogger('lnbits-payments-processor-factory') +const logger = createLogger('lnbits-payments-processor-factory') const getLNbitsAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.LNBITS_API_KEY) { @@ -28,7 +28,7 @@ export const createLNbitsPaymentProcessor = (settings: Settings): IPaymentsProce const callbackBaseURL = path(['paymentsProcessors', 'lnbits', 'callbackBaseURL'], settings) as string | undefined if (typeof callbackBaseURL === 'undefined' || callbackBaseURL.indexOf('nostream.your-domain.com') >= 0) { const error = new Error('Setting paymentsProcessor.lnbits.callbackBaseURL is not configured.') - debug.error('Unable to create payments processor. %o', error) + logger.error('Unable to create payments processor. %o', error) throw error } diff --git a/src/factories/payments-processors/nodeless-payments-processor-factory.ts b/src/factories/payments-processors/nodeless-payments-processor-factory.ts index 95ba63ef..3515e49c 100644 --- a/src/factories/payments-processors/nodeless-payments-processor-factory.ts +++ b/src/factories/payments-processors/nodeless-payments-processor-factory.ts @@ -7,12 +7,12 @@ import { IPaymentsProcessor } from '../../@types/clients' import { NodelessPaymentsProcessor } from '../../payments-processors/nodeless-payments-processor' import { Settings } from '../../@types/settings' -const debug = createLogger('nodeless-payments-processor-factory') +const logger = createLogger('nodeless-payments-processor-factory') const getNodelessAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.NODELESS_API_KEY) { const error = new Error('NODELESS_API_KEY must be set.') - debug.error('Unable to get Nodeless config. %o', error) + logger.error('Unable to get Nodeless config. %o', error) throw error } diff --git a/src/factories/payments-processors/opennode-payments-processor-factory.ts b/src/factories/payments-processors/opennode-payments-processor-factory.ts index ea9661d8..f5c0bb74 100644 --- a/src/factories/payments-processors/opennode-payments-processor-factory.ts +++ b/src/factories/payments-processors/opennode-payments-processor-factory.ts @@ -7,12 +7,12 @@ import { IPaymentsProcessor } from '../../@types/clients' import { OpenNodePaymentsProcessor } from '../../payments-processors/opennode-payments-processor' import { Settings } from '../../@types/settings' -const debug = createLogger('opennode-payments-processor-factory') +const logger = createLogger('opennode-payments-processor-factory') const getOpenNodeAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.OPENNODE_API_KEY) { const error = new Error('OPENNODE_API_KEY must be set.') - debug.error('Unable to get OpenNode config. %o', error) + logger.error('Unable to get OpenNode config. %o', error) throw error } @@ -30,7 +30,7 @@ export const createOpenNodePaymentsProcessor = (settings: Settings): IPaymentsPr const callbackBaseURL = path(['paymentsProcessors', 'opennode', 'callbackBaseURL'], settings) as string | undefined if (typeof callbackBaseURL === 'undefined' || callbackBaseURL.indexOf('nostream.your-domain.com') >= 0) { const error = new Error('Setting paymentsProcessor.opennode.callbackBaseURL is not configured.') - debug.error('Unable to create payments processor. %o', error) + logger.error('Unable to create payments processor. %o', error) throw error } diff --git a/src/factories/payments-processors/zebedee-payments-processor-factory.ts b/src/factories/payments-processors/zebedee-payments-processor-factory.ts index 521a736a..4f15d44d 100644 --- a/src/factories/payments-processors/zebedee-payments-processor-factory.ts +++ b/src/factories/payments-processors/zebedee-payments-processor-factory.ts @@ -7,12 +7,12 @@ import { IPaymentsProcessor } from '../../@types/clients' import { Settings } from '../../@types/settings' import { ZebedeePaymentsProcessor } from '../../payments-processors/zebedee-payments-processor' -const debug = createLogger('zebedee-payments-processor-factory') +const logger = createLogger('zebedee-payments-processor-factory') const getZebedeeAxiosConfig = (settings: Settings): CreateAxiosDefaults => { if (!process.env.ZEBEDEE_API_KEY) { const error = new Error('ZEBEDEE_API_KEY must be set.') - debug.error('Unable to get Zebedee config. %o', error) + logger.error('Unable to get Zebedee config. %o', error) throw error } @@ -30,7 +30,7 @@ export const createZebedeePaymentsProcessor = (settings: Settings): IPaymentsPro const callbackBaseURL = path(['paymentsProcessors', 'zebedee', 'callbackBaseURL'], settings) as string | undefined if (typeof callbackBaseURL === 'undefined' || callbackBaseURL.indexOf('nostream.your-domain.com') >= 0) { const error = new Error('Setting paymentsProcessor.zebedee.callbackBaseURL is not configured.') - debug.error('Unable to create payments processor. %o', error) + logger.error('Unable to create payments processor. %o', error) throw error } @@ -40,7 +40,7 @@ export const createZebedeePaymentsProcessor = (settings: Settings): IPaymentsPro !settings.paymentsProcessors?.zebedee?.ipWhitelist?.length ) { const error = new Error('Setting paymentsProcessor.zebedee.ipWhitelist is empty.') - debug.error('Unable to create payments processor. %o', error) + logger.error('Unable to create payments processor. %o', error) throw error } diff --git a/src/factories/worker-factory.ts b/src/factories/worker-factory.ts index 9cd1f018..5d9fe549 100644 --- a/src/factories/worker-factory.ts +++ b/src/factories/worker-factory.ts @@ -14,7 +14,7 @@ import { UserRepository } from '../repositories/user-repository' import { webSocketAdapterFactory } from './websocket-adapter-factory' import { WebSocketServerAdapter } from '../adapters/web-socket-server-adapter' -const debug = createLogger('worker-factory') +const logger = createLogger('worker-factory') export const workerFactory = (): AppWorker => { const dbClient = getMasterDbClient() @@ -32,7 +32,7 @@ export const workerFactory = (): AppWorker => { let maxPayloadSize: number | undefined if (pathSatisfies(is(String), ['network', 'max_payload_size'], settings)) { - debug.warn(`WARNING: Setting network.max_payload_size is deprecated and will be removed in a future version. + logger.warn(`WARNING: Setting network.max_payload_size is deprecated and will be removed in a future version. Use network.maxPayloadSize instead.`) maxPayloadSize = path(['network', 'max_payload_size'], settings) } else { diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index 0d76a1a3..4e7599f7 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -37,7 +37,7 @@ import { IWebSocketAdapter } from '../@types/adapters' import { Nip05Verification } from '../@types/nip05' import { WebSocketAdapterEvent } from '../constants/adapter' -const debug = createLogger('event-message-handler') +const logger = createLogger('event-message-handler') export class EventMessageHandler implements IMessageHandler { public constructor( @@ -58,13 +58,13 @@ export class EventMessageHandler implements IMessageHandler { let reason = await this.isEventValid(event) if (reason) { - debug('event %s rejected: %s', event.id, reason) + logger('event %s rejected: %s', event.id, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) return } if (isExpiredEvent(event)) { - debug('event %s rejected: expired') + logger('event %s rejected: expired') this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, 'event is expired')) return } @@ -72,7 +72,7 @@ export class EventMessageHandler implements IMessageHandler { event = this.addExpirationMetadata(event) if (await this.isRateLimited(event)) { - debug('event %s rejected: rate-limited') + logger('event %s rejected: rate-limited') this.webSocket.emit( WebSocketAdapterEvent.Message, createCommandResult(event.id, false, 'rate-limited: slow down'), @@ -82,28 +82,28 @@ export class EventMessageHandler implements IMessageHandler { reason = this.canAcceptEvent(event) if (reason) { - debug('event %s rejected: %s', event.id, reason) + logger('event %s rejected: %s', event.id, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) return } reason = await this.isBlockedByRequestToVanish(event) if (reason) { - debug('event %s rejected: %s', event.id, reason) + logger('event %s rejected: %s', event.id, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) return } reason = await this.isUserAdmitted(event) if (reason) { - debug('event %s rejected: %s', event.id, reason) + logger('event %s rejected: %s', event.id, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) return } reason = await this.checkNip05Verification(event) if (reason) { - debug('event %s rejected: %s', event.id, reason) + logger('event %s rejected: %s', event.id, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, reason)) return } @@ -122,7 +122,7 @@ export class EventMessageHandler implements IMessageHandler { await strategy.execute(event) this.processNip05Metadata(event) } catch (error) { - debug.error('error handling message', message, error) + logger.error('error handling message', message, error) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, false, 'error: unable to process event')) } } @@ -309,7 +309,7 @@ export class EventMessageHandler implements IMessageHandler { const isRateLimited = await hit({ period, rate, kinds }) if (isRateLimited) { - debug('rate limited %s: %d events / %d ms exceeded', event.pubkey, rate, period) + logger('rate limited %s: %d events / %d ms exceeded', event.pubkey, rate, period) limited = true } @@ -343,19 +343,19 @@ export class EventMessageHandler implements IMessageHandler { try { const cachedValue = await this.cache.getKey(cacheKey) if (cachedValue === CacheAdmissionState.ADMITTED) { - debug('cache hit for %s admission: admitted', event.pubkey) + logger('cache hit for %s admission: admitted', event.pubkey) return } if (cachedValue === CacheAdmissionState.BLOCKED_NOT_ADMITTED) { - debug('cache hit for %s admission: blocked', event.pubkey) + logger('cache hit for %s admission: blocked', event.pubkey) return 'blocked: pubkey not admitted' } if (cachedValue === CacheAdmissionState.BLOCKED_INSUFFICIENT_BALANCE) { - debug('cache hit for %s admission: insufficient balance', event.pubkey) + logger('cache hit for %s admission: insufficient balance', event.pubkey) return 'blocked: insufficient balance' } } catch (error) { - debug('cache error for %s: %o', event.pubkey, error) + logger('cache error for %s: %o', event.pubkey, error) } const user = await this.userRepository.findByPubkey(event.pubkey) @@ -374,7 +374,7 @@ export class EventMessageHandler implements IMessageHandler { } private cacheSet(key: string, value: string, ttl: number): void { - this.cache.setKey(key, value, ttl).catch((error) => debug('unable to cache %s: %o', key, error)) + this.cache.setKey(key, value, ttl).catch((error) => logger('unable to cache %s: %o', key, error)) } protected addExpirationMetadata(event: Event): Event | ExpiringEvent { @@ -448,7 +448,7 @@ export class EventMessageHandler implements IMessageHandler { const nip05Identifier = extractNip05FromEvent(event) if (!nip05Identifier) { this.nip05VerificationRepository.deleteByPubkey(event.pubkey).catch((error) => { - debug('failed to remove NIP-05 verification for %s: %o', event.pubkey, error) + logger('failed to remove NIP-05 verification for %s: %o', event.pubkey, error) }) return } @@ -459,7 +459,7 @@ export class EventMessageHandler implements IMessageHandler { } if (!isDomainAllowed(parsed.domain, nip05Settings.domainWhitelist, nip05Settings.domainBlacklist)) { - debug('NIP-05 domain %s not allowed for %s', parsed.domain, event.pubkey) + logger('NIP-05 domain %s not allowed for %s', parsed.domain, event.pubkey) return } @@ -470,7 +470,7 @@ export class EventMessageHandler implements IMessageHandler { return repo.upsert(verification) }) .catch((error) => { - debug('NIP-05 verification failed for %s: %o', event.pubkey, error) + logger('NIP-05 verification failed for %s: %o', event.pubkey, error) }) } } diff --git a/src/handlers/event-strategies/default-event-strategy.ts b/src/handlers/event-strategies/default-event-strategy.ts index d22f9589..e38cbe3e 100644 --- a/src/handlers/event-strategies/default-event-strategy.ts +++ b/src/handlers/event-strategies/default-event-strategy.ts @@ -6,7 +6,7 @@ import { IEventStrategy } from '../../@types/message-handlers' import { IWebSocketAdapter } from '../../@types/adapters' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('default-event-strategy') +const logger = createLogger('default-event-strategy') export class DefaultEventStrategy implements IEventStrategy> { public constructor( @@ -15,7 +15,7 @@ export class DefaultEventStrategy implements IEventStrategy ) {} public async execute(event: Event): Promise { - debug('received event: %o', event) + logger('received event: %o', event) const count = await this.eventRepository.create(event) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, true, count ? '' : 'duplicate:')) diff --git a/src/handlers/event-strategies/delete-event-strategy.ts b/src/handlers/event-strategies/delete-event-strategy.ts index d2584cab..0be6aa13 100644 --- a/src/handlers/event-strategies/delete-event-strategy.ts +++ b/src/handlers/event-strategies/delete-event-strategy.ts @@ -8,7 +8,7 @@ import { IWebSocketAdapter } from '../../@types/adapters' import { Tag } from '../../@types/base' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('delete-event-strategy') +const logger = createLogger('delete-event-strategy') export class DeleteEventStrategy implements IEventStrategy> { public constructor( @@ -17,7 +17,7 @@ export class DeleteEventStrategy implements IEventStrategy> ) {} public async execute(event: Event): Promise { - debug('received delete event: %o', event) + logger('received delete event: %o', event) const isValidETag = (tag: Tag) => tag.length >= 2 && tag[0] === EventTags.Event && /^[0-9a-f]{64}$/.test(tag[1]) diff --git a/src/handlers/event-strategies/ephemeral-event-strategy.ts b/src/handlers/event-strategies/ephemeral-event-strategy.ts index 5abd5283..252457ce 100644 --- a/src/handlers/event-strategies/ephemeral-event-strategy.ts +++ b/src/handlers/event-strategies/ephemeral-event-strategy.ts @@ -5,13 +5,13 @@ import { IEventStrategy } from '../../@types/message-handlers' import { IWebSocketAdapter } from '../../@types/adapters' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('ephemeral-event-strategy') +const logger = createLogger('ephemeral-event-strategy') export class EphemeralEventStrategy implements IEventStrategy> { public constructor(private readonly webSocket: IWebSocketAdapter) {} public async execute(event: Event): Promise { - debug('received ephemeral event: %o', event) + logger('received ephemeral event: %o', event) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, true, '')) this.webSocket.emit(WebSocketAdapterEvent.Broadcast, event) } diff --git a/src/handlers/event-strategies/gift-wrap-event-strategy.ts b/src/handlers/event-strategies/gift-wrap-event-strategy.ts index 9a7b5e94..0414e461 100644 --- a/src/handlers/event-strategies/gift-wrap-event-strategy.ts +++ b/src/handlers/event-strategies/gift-wrap-event-strategy.ts @@ -8,7 +8,7 @@ import { IWebSocketAdapter } from '../../@types/adapters' import { validateNip44Payload } from '../../utils/nip44' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('gift-wrap-event-strategy') +const logger = createLogger('gift-wrap-event-strategy') export class GiftWrapEventStrategy implements IEventStrategy> { public constructor( @@ -17,7 +17,7 @@ export class GiftWrapEventStrategy implements IEventStrategy { - debug('received gift wrap event: %o', event) + logger('received gift wrap event: %o', event) const reason = this.validateGiftWrap(event) if (reason) { diff --git a/src/handlers/event-strategies/parameterized-replaceable-event-strategy.ts b/src/handlers/event-strategies/parameterized-replaceable-event-strategy.ts index 9862b703..14e9aa5e 100644 --- a/src/handlers/event-strategies/parameterized-replaceable-event-strategy.ts +++ b/src/handlers/event-strategies/parameterized-replaceable-event-strategy.ts @@ -7,7 +7,7 @@ import { IEventStrategy } from '../../@types/message-handlers' import { IWebSocketAdapter } from '../../@types/adapters' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('parameterized-replaceable-event-strategy') +const logger = createLogger('parameterized-replaceable-event-strategy') export class ParameterizedReplaceableEventStrategy implements IEventStrategy> { public constructor( @@ -16,7 +16,7 @@ export class ParameterizedReplaceableEventStrategy implements IEventStrategy { - debug('received parameterized replaceable event: %o', event) + logger('received parameterized replaceable event: %o', event) const [, deduplication] = event.tags.find((tag) => tag.length >= 2 && tag[0] === EventTags.Deduplication) ?? [ null, diff --git a/src/handlers/event-strategies/replaceable-event-strategy.ts b/src/handlers/event-strategies/replaceable-event-strategy.ts index b95dd35c..340fb996 100644 --- a/src/handlers/event-strategies/replaceable-event-strategy.ts +++ b/src/handlers/event-strategies/replaceable-event-strategy.ts @@ -6,7 +6,7 @@ import { IEventStrategy } from '../../@types/message-handlers' import { IWebSocketAdapter } from '../../@types/adapters' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('replaceable-event-strategy') +const logger = createLogger('replaceable-event-strategy') export class ReplaceableEventStrategy implements IEventStrategy> { public constructor( @@ -15,7 +15,7 @@ export class ReplaceableEventStrategy implements IEventStrategy { - debug('received replaceable event: %o', event) + logger('received replaceable event: %o', event) try { const count = await this.eventRepository.upsert(event) this.webSocket.emit(WebSocketAdapterEvent.Message, createCommandResult(event.id, true, count ? '' : 'duplicate:')) diff --git a/src/handlers/event-strategies/vanish-event-strategy.ts b/src/handlers/event-strategies/vanish-event-strategy.ts index 4ccb85a0..519f435c 100644 --- a/src/handlers/event-strategies/vanish-event-strategy.ts +++ b/src/handlers/event-strategies/vanish-event-strategy.ts @@ -7,7 +7,7 @@ import { IEventStrategy } from '../../@types/message-handlers' import { IWebSocketAdapter } from '../../@types/adapters' import { WebSocketAdapterEvent } from '../../constants/adapter' -const debug = createLogger('vanish-event-strategy') +const logger = createLogger('vanish-event-strategy') export class VanishEventStrategy implements IEventStrategy> { public constructor( @@ -17,7 +17,7 @@ export class VanishEventStrategy implements IEventStrategy> ) {} public async execute(event: Event): Promise { - debug('received request to vanish event: %o', event) + logger('received request to vanish event: %o', event) await this.eventRepository.deleteByPubkeyExceptKinds(event.pubkey, [EventKinds.REQUEST_TO_VANISH]) diff --git a/src/handlers/request-handlers/rate-limiter-middleware.ts b/src/handlers/request-handlers/rate-limiter-middleware.ts index 72c7da87..f398ec94 100644 --- a/src/handlers/request-handlers/rate-limiter-middleware.ts +++ b/src/handlers/request-handlers/rate-limiter-middleware.ts @@ -5,14 +5,14 @@ import { getRemoteAddress } from '../../utils/http' import { Settings } from '../../@types/settings' import { slidingWindowRateLimiterFactory } from '../../factories/rate-limiter-factory' -const debug = createLogger('rate-limiter-middleware') +const logger = createLogger('rate-limiter-middleware') export const rateLimiterMiddleware = async (request: Request, response: Response, next: NextFunction) => { const currentSettings = createSettings() const clientAddress = getRemoteAddress(request, currentSettings).split(',')[0] - debug('request received from %s: %o', clientAddress, request.headers) + logger('request received from %s: %o', clientAddress, request.headers) if (await isRateLimited(clientAddress, currentSettings)) { response.destroy() @@ -44,7 +44,7 @@ export async function isRateLimited(remoteAddress: string, settings: Settings): const isRateLimited = await hit(period, rate) if (isRateLimited) { - debug('rate limited %s: %d messages / %d ms exceeded', remoteAddress, rate, period) + logger('rate limited %s: %d messages / %d ms exceeded', remoteAddress, rate, period) limited = true } diff --git a/src/handlers/subscribe-message-handler.ts b/src/handlers/subscribe-message-handler.ts index 457cc4ea..1be58340 100644 --- a/src/handlers/subscribe-message-handler.ts +++ b/src/handlers/subscribe-message-handler.ts @@ -19,7 +19,7 @@ import { Settings } from '../@types/settings' import { SubscribeMessage } from '../@types/messages' import { WebSocketAdapterEvent } from '../constants/adapter' -const debug = createLogger('subscribe-message-handler') +const logger = createLogger('subscribe-message-handler') export class SubscribeMessageHandler implements IMessageHandler, IAbortable { //private readonly abortController: AbortController @@ -42,7 +42,7 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable { const reason = this.canSubscribe(subscriptionId, filters) if (reason) { - debug('subscription %s with %o rejected: %s', subscriptionId, filters, reason) + logger('subscription %s with %o rejected: %s', subscriptionId, filters, reason) this.webSocket.emit(WebSocketAdapterEvent.Message, createNoticeMessage(`Subscription rejected: ${reason}`)) return } @@ -53,7 +53,7 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable { } private async fetchAndSend(subscriptionId: string, filters: SubscriptionFilter[]): Promise { - debug('fetching events for subscription %s with filters %o', subscriptionId, filters) + logger('fetching events for subscription %s with filters %o', subscriptionId, filters) const sendEvent = (event: Event) => this.webSocket.emit(WebSocketAdapterEvent.Message, createOutgoingEventMessage(subscriptionId, event)) const sendEOSE = () => @@ -82,10 +82,10 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable { ) } catch (error) { if (error instanceof Error && error.name === 'AbortError') { - debug('subscription %s aborted: %o', subscriptionId, error) + logger('subscription %s aborted: %o', subscriptionId, error) findEvents.destroy() } else { - debug('error streaming events: %o', error) + logger('error streaming events: %o', error) } throw error } diff --git a/src/payments-processors/lnbits-payment-processor.ts b/src/payments-processors/lnbits-payment-processor.ts index 14bc5343..10037bd5 100644 --- a/src/payments-processors/lnbits-payment-processor.ts +++ b/src/payments-processors/lnbits-payment-processor.ts @@ -8,7 +8,7 @@ import { Factory } from '../@types/base' import { Pubkey } from '../@types/base' import { Settings } from '../@types/settings' -const debug = createLogger('lnbits-payments-processor') +const logger = createLogger('lnbits-payments-processor') export class LNbitsInvoice implements Invoice { id: string @@ -46,7 +46,7 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { ) {} public async getInvoice(invoiceId: string): Promise { - debug('get invoice: %s', invoiceId) + logger('get invoice: %s', invoiceId) try { const response = await this.httpClient.get(`/api/v1/payments/${invoiceId}`, { maxRedirects: 1, @@ -69,14 +69,14 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { invoice.updatedAt = new Date() return invoice } catch (error) { - debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + logger.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } } public async createInvoice(request: CreateInvoiceRequest): Promise { - debug('create invoice: %o', request) + logger('create invoice: %o', request) const { amount: amountMsats, description, requestId: internalId } = request const callbackURL = new URL(this.settings().paymentsProcessors?.lnbits?.callbackBaseURL) @@ -97,12 +97,12 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { } try { - debug('request body: %o', body) + logger('request body: %o', body) const response = await this.httpClient.post('/api/v1/payments', body, { maxRedirects: 1, }) - debug('response: %o', response.data) + logger('response: %o', response.data) const invoiceResponse = await this.httpClient.get( `/api/v1/payments/${encodeURIComponent(response.data.payment_hash)}`, @@ -110,7 +110,7 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { maxRedirects: 1, }, ) - debug('invoice data response: %o', invoiceResponse.data) + logger('invoice data response: %o', invoiceResponse.data) const invoice = new LNbitsCreateInvoiceResponse() const data = invoiceResponse.data @@ -131,7 +131,7 @@ export class LNbitsPaymentsProcessor implements IPaymentsProcessor { return invoice } catch (error) { - debug.error('Unable to request invoice. Reason:', error.message) + logger.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/lnurl-payments-processor.ts b/src/payments-processors/lnurl-payments-processor.ts index 730e58ef..e2ceae28 100644 --- a/src/payments-processors/lnurl-payments-processor.ts +++ b/src/payments-processors/lnurl-payments-processor.ts @@ -7,7 +7,7 @@ import { createLogger } from '../factories/logger-factory' import { randomUUID } from 'crypto' import { Settings } from '../@types/settings' -const debug = createLogger('lnurl-payments-processor') +const logger = createLogger('lnurl-payments-processor') export class LnurlPaymentsProcessor implements IPaymentsProcessor { public constructor( @@ -16,7 +16,7 @@ export class LnurlPaymentsProcessor implements IPaymentsProcessor { ) {} public async getInvoice(invoice: LnurlInvoice): Promise { - debug('get invoice: %s', invoice.id) + logger('get invoice: %s', invoice.id) try { const response = await this.httpClient.get(invoice.verifyURL) @@ -27,14 +27,14 @@ export class LnurlPaymentsProcessor implements IPaymentsProcessor { status: response.data.settled ? InvoiceStatus.COMPLETED : InvoiceStatus.PENDING, } } catch (error) { - debug.error(`Unable to get invoice ${invoice.id}. Reason:`, error) + logger.error(`Unable to get invoice ${invoice.id}. Reason:`, error) throw error } } public async createInvoice(request: CreateInvoiceRequest): Promise { - debug('create invoice: %o', request) + logger('create invoice: %o', request) const { amount: amountMsats, description, requestId } = request try { @@ -56,11 +56,11 @@ export class LnurlPaymentsProcessor implements IPaymentsProcessor { verifyURL: response.data.verify, } - debug('result: %o', result) + logger('result: %o', result) return result } catch (error) { - debug.error('Unable to request invoice. Reason:', error.message) + logger.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/nodeless-payments-processor.ts b/src/payments-processors/nodeless-payments-processor.ts index 88e37eca..aea22435 100644 --- a/src/payments-processors/nodeless-payments-processor.ts +++ b/src/payments-processors/nodeless-payments-processor.ts @@ -6,7 +6,7 @@ import { createLogger } from '../factories/logger-factory' import { fromNodelessInvoice } from '../utils/transform' import { Settings } from '../@types/settings' -const debug = createLogger('nodeless-payments-processor') +const logger = createLogger('nodeless-payments-processor') export class NodelessPaymentsProcessor implements IPaymentsProcessor { public constructor( @@ -15,7 +15,7 @@ export class NodelessPaymentsProcessor implements IPaymentsProcessor { ) {} public async getInvoice(invoiceId: string): Promise { - debug('get invoice: %s', invoiceId) + logger('get invoice: %s', invoiceId) const { storeId } = this.settings().paymentsProcessors.nodeless @@ -26,14 +26,14 @@ export class NodelessPaymentsProcessor implements IPaymentsProcessor { return fromNodelessInvoice(response.data.data) } catch (error) { - debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + logger.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } } public async createInvoice(request: CreateInvoiceRequest): Promise { - debug('create invoice: %O', request) + logger('create invoice: %O', request) const { amount: amountMsats, description, requestId } = request const amountSats = Number(amountMsats / 1000n) @@ -52,21 +52,21 @@ export class NodelessPaymentsProcessor implements IPaymentsProcessor { const { storeId } = this.settings().paymentsProcessors.nodeless try { - debug('request body: %O', body) + logger('request body: %O', body) const response = await this.httpClient.post(`/api/v1/store/${storeId}/invoice`, body, { maxRedirects: 1, }) - debug('response headers: %O', response.headers) - debug('response data: %O', response.data) + logger('response headers: %O', response.headers) + logger('response data: %O', response.data) const result = fromNodelessInvoice(response.data.data) - debug('invoice: %O', result) + logger('invoice: %O', result) return result } catch (error) { - debug.error('Unable to request invoice. Reason:', error.message) + logger.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/opennode-payments-processor.ts b/src/payments-processors/opennode-payments-processor.ts index 559e4b29..1855ffea 100644 --- a/src/payments-processors/opennode-payments-processor.ts +++ b/src/payments-processors/opennode-payments-processor.ts @@ -6,7 +6,7 @@ import { createLogger } from '../factories/logger-factory' import { fromOpenNodeInvoice } from '../utils/transform' import { Settings } from '../@types/settings' -const debug = createLogger('opennode-payments-processor') +const logger = createLogger('opennode-payments-processor') export class OpenNodePaymentsProcessor implements IPaymentsProcessor { public constructor( @@ -15,7 +15,7 @@ export class OpenNodePaymentsProcessor implements IPaymentsProcessor { ) {} public async getInvoice(invoiceId: string): Promise { - debug('get invoice: %s', invoiceId) + logger('get invoice: %s', invoiceId) try { const response = await this.httpClient.get(`/v2/charge/${invoiceId}`, { @@ -24,14 +24,14 @@ export class OpenNodePaymentsProcessor implements IPaymentsProcessor { return fromOpenNodeInvoice(response.data.data) } catch (error) { - debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + logger.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } } public async createInvoice(request: CreateInvoiceRequest): Promise { - debug('create invoice: %o', request) + logger('create invoice: %o', request) const { amount: amountMsats, description, requestId } = request const amountSats = Number(amountMsats / 1000n) @@ -45,18 +45,18 @@ export class OpenNodePaymentsProcessor implements IPaymentsProcessor { } try { - debug('request body: %o', body) + logger('request body: %o', body) const response = await this.httpClient.post('/v1/charges', body, { maxRedirects: 1, }) const result = fromOpenNodeInvoice(response.data.data) - debug('result: %o', result) + logger('result: %o', result) return result } catch (error) { - debug.error('Unable to request invoice. Reason:', error.message) + logger.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/payments-processors/zebedee-payments-processor.ts b/src/payments-processors/zebedee-payments-processor.ts index 7a18349f..4fffa6e3 100644 --- a/src/payments-processors/zebedee-payments-processor.ts +++ b/src/payments-processors/zebedee-payments-processor.ts @@ -6,7 +6,7 @@ import { createLogger } from '../factories/logger-factory' import { fromZebedeeInvoice } from '../utils/transform' import { Settings } from '../@types/settings' -const debug = createLogger('zebedee-payments-processor') +const logger = createLogger('zebedee-payments-processor') export class ZebedeePaymentsProcessor implements IPaymentsProcessor { public constructor( @@ -15,7 +15,7 @@ export class ZebedeePaymentsProcessor implements IPaymentsProcessor { ) {} public async getInvoice(invoiceId: string): Promise { - debug('get invoice: %s', invoiceId) + logger('get invoice: %s', invoiceId) try { const response = await this.httpClient.get(`/v0/charges/${invoiceId}`, { @@ -24,14 +24,14 @@ export class ZebedeePaymentsProcessor implements IPaymentsProcessor { return fromZebedeeInvoice(response.data.data) } catch (error) { - debug.error(`Unable to get invoice ${invoiceId}. Reason:`, error) + logger.error(`Unable to get invoice ${invoiceId}. Reason:`, error) throw error } } public async createInvoice(request: CreateInvoiceRequest): Promise { - debug('create invoice: %o', request) + logger('create invoice: %o', request) const { amount: amountMsats, description, requestId } = request const body = { @@ -42,18 +42,18 @@ export class ZebedeePaymentsProcessor implements IPaymentsProcessor { } try { - debug('request body: %o', body) + logger('request body: %o', body) const response = await this.httpClient.post('/v0/charges', body, { maxRedirects: 1, }) const result = fromZebedeeInvoice(response.data.data) - debug('result: %o', result) + logger('result: %o', result) return result } catch (error) { - debug.error('Unable to request invoice. Reason:', error.message) + logger.error('Unable to request invoice. Reason:', error.message) throw error } diff --git a/src/repositories/event-repository.ts b/src/repositories/event-repository.ts index 312d98ad..dba91177 100644 --- a/src/repositories/event-repository.ts +++ b/src/repositories/event-repository.ts @@ -55,7 +55,7 @@ const groupByLengthSpec = groupBy( ), ) -const debug = createLogger('event-repository') +const logger = createLogger('event-repository') export class EventRepository implements IEventRepository { public constructor( @@ -64,7 +64,7 @@ export class EventRepository implements IEventRepository { ) {} public findByFilters(filters: SubscriptionFilter[]): IQueryResult { - debug('querying for %o', filters) + logger('querying for %o', filters) if (!Array.isArray(filters) || !filters.length) { throw new Error('Filters cannot be empty') } @@ -205,14 +205,14 @@ export class EventRepository implements IEventRepository { } private insert(event: Event) { - debug('inserting event: %o', event) + logger('inserting event: %o', event) const row = this.toInsertRow(event) return this.masterDbClient('events').insert(row).onConflict().ignore() } public upsert(event: Event): Promise { - debug('upserting event: %o', event) + logger('upserting event: %o', event) const row = this.toUpsertRow(event) @@ -300,7 +300,7 @@ export class EventRepository implements IEventRepository { } public deleteByPubkeyAndIds(pubkey: string, eventIdsToDelete: EventId[]): Promise { - debug('deleting events from %s: %o', pubkey, eventIdsToDelete) + logger('deleting events from %s: %o', pubkey, eventIdsToDelete) return this.masterDbClient('events') .where('event_pubkey', toBuffer(pubkey)) @@ -313,7 +313,7 @@ export class EventRepository implements IEventRepository { } public deleteByPubkeyExceptKinds(pubkey: string, excludedKinds: number[]): Promise { - debug('deleting events from %s except kinds %o', pubkey, excludedKinds) + logger('deleting events from %s except kinds %o', pubkey, excludedKinds) return this.masterDbClient('events') .where('event_pubkey', toBuffer(pubkey)) @@ -340,7 +340,7 @@ export class EventRepository implements IEventRepository { const maxDays = options?.maxDays if (typeof maxDays !== 'number' || isNaN(maxDays) || maxDays <= 0) { - debug('skipping purge: retention.maxDays is not a positive number') + logger('skipping purge: retention.maxDays is not a positive number') return Promise.resolve({ deleted: 0, expired: 0, @@ -351,7 +351,7 @@ export class EventRepository implements IEventRepository { const retentionLimit = now - maxDays * 86400 const batchSize = 1000 - debug( + logger( 'deleting expired and retained events (retentionLimit: %d, now: %d, batchSize: %d)', retentionLimit, now, diff --git a/src/repositories/invoice-repository.ts b/src/repositories/invoice-repository.ts index 3969ee08..eda5baa4 100644 --- a/src/repositories/invoice-repository.ts +++ b/src/repositories/invoice-repository.ts @@ -7,7 +7,7 @@ import { DatabaseClient } from '../@types/base' import { IInvoiceRepository } from '../@types/repositories' import { randomUUID } from 'crypto' -const debug = createLogger('invoice-repository') +const logger = createLogger('invoice-repository') export class InvoiceRepository implements IInvoiceRepository { public constructor(private readonly dbClient: DatabaseClient) {} @@ -18,12 +18,12 @@ export class InvoiceRepository implements IInvoiceRepository { confirmedAt: Date, client: DatabaseClient = this.dbClient, ): Promise { - debug('confirming invoice %s at %s: %s', invoiceId, confirmedAt, amountPaid) + logger('confirming invoice %s at %s: %s', invoiceId, confirmedAt, amountPaid) try { await client.raw('select confirm_invoice(?, ?, ?)', [invoiceId, amountPaid.toString(), confirmedAt.toISOString()]) } catch (error) { - debug.error('Unable to confirm invoice. Reason:', error.message) + logger.error('Unable to confirm invoice. Reason:', error.message) throw error } @@ -50,7 +50,7 @@ export class InvoiceRepository implements IInvoiceRepository { } public updateStatus(invoice: Invoice, client: DatabaseClient = this.dbClient): Promise { - debug('updating invoice status: %o', invoice) + logger('updating invoice status: %o', invoice) const query = client('invoices') .update({ @@ -72,7 +72,7 @@ export class InvoiceRepository implements IInvoiceRepository { } public upsert(invoice: Invoice, client: DatabaseClient = this.dbClient): Promise { - debug('upserting invoice: %o', invoice) + logger('upserting invoice: %o', invoice) const row = applySpec({ id: ifElse(propSatisfies(is(String), 'id'), prop('id'), always(randomUUID())), @@ -90,7 +90,7 @@ export class InvoiceRepository implements IInvoiceRepository { verify_url: prop('verifyURL'), })(invoice) - debug('row: %o', row) + logger('row: %o', row) const query = client('invoices') .insert(row) diff --git a/src/repositories/nip05-verification-repository.ts b/src/repositories/nip05-verification-repository.ts index f8f8678d..bf266558 100644 --- a/src/repositories/nip05-verification-repository.ts +++ b/src/repositories/nip05-verification-repository.ts @@ -6,7 +6,7 @@ import { fromBuffer, toBuffer } from '../utils/transform' import { createLogger } from '../factories/logger-factory' import { INip05VerificationRepository } from '../@types/repositories' -const debug = createLogger('nip05-verification-repository') +const logger = createLogger('nip05-verification-repository') const fromDBNip05Verification = applySpec({ pubkey: pipe(prop('pubkey') as () => Buffer, fromBuffer), @@ -24,7 +24,7 @@ export class Nip05VerificationRepository implements INip05VerificationRepository public constructor(private readonly dbClient: DatabaseClient) {} public async findByPubkey(pubkey: Pubkey): Promise { - debug('find by pubkey: %s', pubkey) + logger('find by pubkey: %s', pubkey) const [row] = await this.dbClient('nip05_verifications') .where('pubkey', toBuffer(pubkey)) @@ -38,7 +38,7 @@ export class Nip05VerificationRepository implements INip05VerificationRepository } public async upsert(verification: Nip05Verification): Promise { - debug('upsert: %s (%s)', verification.pubkey, verification.nip05) + logger('upsert: %s (%s)', verification.pubkey, verification.nip05) const now = new Date() @@ -79,7 +79,7 @@ export class Nip05VerificationRepository implements INip05VerificationRepository maxFailures: number, limit: number, ): Promise { - debug('find pending verifications (frequency: %dms, maxFailures: %d)', updateFrequencyMs, maxFailures) + logger('find pending verifications (frequency: %dms, maxFailures: %d)', updateFrequencyMs, maxFailures) const cutoff = new Date(Date.now() - updateFrequencyMs) @@ -93,7 +93,7 @@ export class Nip05VerificationRepository implements INip05VerificationRepository } public async deleteByPubkey(pubkey: Pubkey): Promise { - debug('delete by pubkey: %s', pubkey) + logger('delete by pubkey: %s', pubkey) return this.dbClient('nip05_verifications').where('pubkey', toBuffer(pubkey)).delete() } diff --git a/src/repositories/user-repository.ts b/src/repositories/user-repository.ts index 9f117b7a..8d5d9986 100644 --- a/src/repositories/user-repository.ts +++ b/src/repositories/user-repository.ts @@ -5,7 +5,7 @@ import { fromDBUser, toBuffer } from '../utils/transform' import { IEventRepository, IUserRepository } from '../@types/repositories' import { createLogger } from '../factories/logger-factory' -const debug = createLogger('user-repository') +const logger = createLogger('user-repository') export class UserRepository implements IUserRepository { public constructor( @@ -14,7 +14,7 @@ export class UserRepository implements IUserRepository { ) {} public async findByPubkey(pubkey: Pubkey, client: DatabaseClient = this.dbClient): Promise { - debug('find by pubkey: %s', pubkey) + logger('find by pubkey: %s', pubkey) const [dbuser] = await client('users').where('pubkey', toBuffer(pubkey)).select() if (!dbuser) { @@ -25,7 +25,7 @@ export class UserRepository implements IUserRepository { } public async upsert(user: Partial, client: DatabaseClient = this.dbClient): Promise { - debug('upsert: %o', user) + logger('upsert: %o', user) const date = new Date() @@ -73,7 +73,7 @@ export class UserRepository implements IUserRepository { } private upsertVanishState(pubkey: Pubkey, isVanished: boolean, client: DatabaseClient): Promise { - debug('upsert vanish state for %s: %o', pubkey, isVanished) + logger('upsert vanish state for %s: %o', pubkey, isVanished) const date = new Date() const query = client('users') @@ -102,7 +102,7 @@ export class UserRepository implements IUserRepository { } public async getBalanceByPubkey(pubkey: Pubkey, client: DatabaseClient = this.dbClient): Promise { - debug('get balance for pubkey: %s', pubkey) + logger('get balance for pubkey: %s', pubkey) const [user] = await client('users').select('balance').where('pubkey', toBuffer(pubkey)).limit(1) @@ -114,12 +114,12 @@ export class UserRepository implements IUserRepository { } public async admitUser(pubkey: Pubkey, admittedAt: Date, client: DatabaseClient = this.dbClient): Promise { - debug('admit user: %s at %s', pubkey, admittedAt) + logger('admit user: %s at %s', pubkey, admittedAt) try { await client.raw('select admit_user(?, ?)', [toBuffer(pubkey), admittedAt.toISOString()]) } catch (error) { - console.error('Unable to admit user. Reason:', error.message) + logger.error('Unable to admit user. Reason:', error) throw error } diff --git a/src/services/maintenance-service.ts b/src/services/maintenance-service.ts index c873a821..f0fa03d6 100644 --- a/src/services/maintenance-service.ts +++ b/src/services/maintenance-service.ts @@ -3,7 +3,7 @@ import { IEventRepository } from '../@types/repositories' import { IMaintenanceService } from '../@types/services' import { Settings } from '../@types/settings' -const debug = createLogger('maintenance-service') +const logger = createLogger('maintenance-service') export class MaintenanceService implements IMaintenanceService { public constructor( @@ -21,7 +21,7 @@ export class MaintenanceService implements IMaintenanceService { } try { - debug('purging deleted, expired and old events') + logger('purging deleted, expired and old events') const deletedCounts = await this.eventRepository.deleteExpiredAndRetained({ maxDays, kindWhitelist: retention?.kind?.whitelist, @@ -29,12 +29,12 @@ export class MaintenanceService implements IMaintenanceService { }) const totalDeleted = deletedCounts.deleted + deletedCounts.expired + deletedCounts.retained if (totalDeleted > 0) { - console.info( + logger.info( `[Maintenance] Deleted events: deleted=${deletedCounts.deleted}, expired=${deletedCounts.expired}, retained=${deletedCounts.retained}.`, ) } } catch (error) { - console.error('Unable to purge events. Reason:', error) + logger.error('Unable to purge events. Reason:', error) } } } diff --git a/src/services/payments-service.ts b/src/services/payments-service.ts index 573a0cc5..d13d7b34 100644 --- a/src/services/payments-service.ts +++ b/src/services/payments-service.ts @@ -12,7 +12,7 @@ import { IPaymentsProcessor } from '../@types/clients' import { IPaymentsService } from '../@types/services' import { Transaction } from '../database/transaction' -const debug = createLogger('payments-service') +const logger = createLogger('payments-service') export class PaymentsService implements IPaymentsService { public constructor( @@ -25,11 +25,11 @@ export class PaymentsService implements IPaymentsService { ) {} public async getPendingInvoices(): Promise { - debug('get pending invoices') + logger('get pending invoices') try { return await this.invoiceRepository.findPendingInvoices(0, 10) } catch (error) { - debug.info('Unable to get pending invoices.', error) + logger.info('Unable to get pending invoices.', error) throw error } @@ -41,14 +41,14 @@ export class PaymentsService implements IPaymentsService { typeof invoice === 'string' || invoice?.verifyURL ? invoice : invoice.id, ) } catch (error) { - debug.info('Unable to get invoice from payments processor. Reason:', error) + logger.info('Unable to get invoice from payments processor. Reason:', error) throw error } } public async createInvoice(pubkey: Pubkey, amount: bigint, description: string): Promise { - debug('create invoice for %s for %s: %s', pubkey, amount.toString(), description) + logger('create invoice for %s for %s: %s', pubkey, amount.toString(), description) const transaction = new Transaction(this.dbClient) try { @@ -98,37 +98,37 @@ export class PaymentsService implements IPaymentsService { } } catch (error) { await transaction.rollback() - debug.error('Unable to create invoice:', error) + logger.error('Unable to create invoice:', error) throw error } } public async updateInvoice(invoice: Partial): Promise { - debug('update invoice %s: %o', invoice.id, invoice) + logger('update invoice %s: %o', invoice.id, invoice) try { await this.invoiceRepository.updateStatus({ id: invoice.id, status: invoice.status, }) } catch (error) { - debug.error('Unable to update invoice. Reason:', error) + logger.error('Unable to update invoice. Reason:', error) throw error } } public async updateInvoiceStatus(invoice: Pick): Promise { - debug('update invoice %s: %o', invoice.id, invoice) + logger('update invoice %s: %o', invoice.id, invoice) try { return await this.invoiceRepository.updateStatus(invoice) } catch (error) { - debug.error('Unable to update invoice. Reason:', error) + logger.error('Unable to update invoice. Reason:', error) throw error } } public async confirmInvoice(invoice: Invoice): Promise { - debug('confirm invoice %s: %O', invoice.id, invoice) + logger('confirm invoice %s: %O', invoice.id, invoice) const transaction = new Transaction(this.dbClient) @@ -177,7 +177,7 @@ export class PaymentsService implements IPaymentsService { await transaction.commit() } catch (error) { - debug.error('Unable to confirm invoice. Reason:', error) + logger.error('Unable to confirm invoice. Reason:', error) await transaction.rollback() throw error @@ -185,7 +185,7 @@ export class PaymentsService implements IPaymentsService { } public async sendInvoiceUpdateNotification(invoice: Invoice): Promise { - debug('invoice updated notification %s: %o', invoice.id, invoice) + logger('invoice updated notification %s: %o', invoice.id, invoice) const currentSettings = this.settings() const { @@ -230,7 +230,7 @@ export class PaymentsService implements IPaymentsService { return event } - const logError = (error: Error) => debug.error('Unable to send notification', error) + const logError = (error: Error) => logger.error('Unable to send notification', error) await pipe( identifyEvent, diff --git a/src/tor/client.ts b/src/tor/client.ts index f2565fbb..84e2cdc7 100644 --- a/src/tor/client.ts +++ b/src/tor/client.ts @@ -6,7 +6,7 @@ import { join } from 'path' import { createLogger } from '../factories/logger-factory' import { TorConfig } from '../@types/tor' -const debug = createLogger('tor-client') +const logger = createLogger('tor-client') const getPrivateKeyFile = () => { return join(process.env.NOSTR_CONFIG_DIR ?? join(homedir(), '.nostr'), 'v3_onion_private_key') @@ -159,17 +159,17 @@ let client: TorClient | undefined export const getTorClient = async () => { if (!client) { const config = createTorConfig() - debug('config: %o', config) + logger('config: %o', config) if (config.host !== undefined) { - debug('connecting') + logger('connecting') client = new TorClient(config) try { await client.connect() } catch (_error) { client = undefined } - debug('connected') + logger('connected') } } @@ -187,24 +187,24 @@ export const addOnion = async (port: number, host?: string): Promise => const path = getPrivateKeyFile() try { - debug('reading private key from %s', path) + logger('reading private key from %s', path) const data = await readFile(path, 'utf8') if (data?.length) { privateKey = data - debug('privateKey: %o', privateKey) + logger('privateKey: %o', privateKey) } } catch (error) { - debug('error reading private key: %o', error) + logger('error reading private key: %o', error) } const client = await getTorClient() if (client) { const hiddenService = await client.addOnion(port, host, privateKey) - debug('hidden service: %s:%d', hiddenService.ServiceID, port) + logger('hidden service: %s:%d', hiddenService.ServiceID, port) if (hiddenService?.PrivateKey) { - debug.info('saving private key to %s', path) - debug('saving private key to %s', path) + logger.info('saving private key to %s', path) + logger('saving private key to %s', path) await writeFile(path, hiddenService.PrivateKey, 'utf8') return hiddenService.ServiceID! diff --git a/src/utils/http.ts b/src/utils/http.ts index 3987a633..92dc945a 100644 --- a/src/utils/http.ts +++ b/src/utils/http.ts @@ -3,13 +3,13 @@ import { IncomingMessage } from 'http' import { createLogger } from '../factories/logger-factory' import { Settings } from '../@types/settings' -const debug = createLogger('http-utils') +const logger = createLogger('http-utils') export const getRemoteAddress = (request: IncomingMessage, settings: Settings): string => { let header: string | undefined // TODO: Remove deprecation warning if ('network' in settings && 'remote_ip_header' in settings.network) { - debug.warn(`WARNING: Setting network.remote_ip_header is deprecated and will be removed in a future version. + logger.warn(`WARNING: Setting network.remote_ip_header is deprecated and will be removed in a future version. Use network.remoteIpHeader instead.`) header = settings.network['remote_ip_header'] as string } else { diff --git a/src/utils/nip05.ts b/src/utils/nip05.ts index 42986918..5baeff4d 100644 --- a/src/utils/nip05.ts +++ b/src/utils/nip05.ts @@ -6,7 +6,7 @@ import { Event } from '../@types/event' import { EventKinds } from '../constants/base' import { pubkeySchema } from '../schemas/base-schema' -const debug = createLogger('nip05') +const logger = createLogger('nip05') const VERIFICATION_TIMEOUT_MS = 10000 // NIP-05 responses are trivially small; cap hard to protect relay memory/bandwidth. @@ -87,7 +87,7 @@ export function extractNip05FromEvent(event: Event): string | undefined { return metadata.nip05 } } catch { - debug('failed to parse metadata content for event %s', event.id) + logger('failed to parse metadata content for event %s', event.id) } return undefined @@ -151,7 +151,7 @@ export async function verifyNip05Identifier(nip05: string, pubkey: string): Prom const url = `https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(localPart)}` try { - debug('verifying %s for pubkey %s via %s', nip05, pubkey, url) + logger('verifying %s for pubkey %s via %s', nip05, pubkey, url) const response = await axios.get(url, { timeout: VERIFICATION_TIMEOUT_MS, @@ -176,27 +176,27 @@ export async function verifyNip05Identifier(nip05: string, pubkey: string): Prom if (!parseResult.success) { const zodError = parseResult as z.SafeParseError> const reason = zodError.error.issues.map((i) => i.message).join('; ') - debug('malformed response from %s: %s', url, reason) + logger('malformed response from %s: %s', url, reason) return { status: 'invalid', reason: `malformed response: ${reason}` } } const registeredPubkey = parseResult.data.names[localPart] if (typeof registeredPubkey !== 'string') { - debug('name %s not found in response from %s', localPart, domain) + logger('name %s not found in response from %s', localPart, domain) return { status: 'mismatch' } } if (registeredPubkey.toLowerCase() !== pubkey.toLowerCase()) { - debug('pubkey mismatch for %s (got %s)', nip05, registeredPubkey) + logger('pubkey mismatch for %s (got %s)', nip05, registeredPubkey) return { status: 'mismatch' } } - debug('verification succeeded for %s', nip05) + logger('verification succeeded for %s', nip05) return { status: 'verified' } } catch (error: unknown) { const axiosError = error as AxiosError const message = axiosError?.message ?? (error instanceof Error ? error.message : String(error)) - debug('verification request failed for %s: %s', nip05, message) + logger('verification request failed for %s: %s', nip05, message) return { status: 'error', reason: message } } } diff --git a/src/utils/settings.ts b/src/utils/settings.ts index c9c9fd7d..7cf90f6e 100644 --- a/src/utils/settings.ts +++ b/src/utils/settings.ts @@ -7,7 +7,7 @@ import { mergeDeepRight } from 'ramda' import { createLogger } from '../factories/logger-factory' import { Settings } from '../@types/settings' -const debug = createLogger('settings') +const logger = createLogger('settings') export enum SettingsFileTypes { yaml = 'yaml', @@ -49,11 +49,11 @@ export class SettingsStatic { } public static loadSettings(path: string, fileType: SettingsFileTypes) { - debug('loading settings from %s', path) + logger('loading settings from %s', path) switch (fileType) { case SettingsFileTypes.json: { - debug.warn('settings.json is deprecated, please use a yaml file based on resources/default-settings.yaml') + logger.warn('settings.json is deprecated, please use a yaml file based on resources/default-settings.yaml') return SettingsStatic.loadAndParseJsonFile(path) } case SettingsFileTypes.yaml: { @@ -69,7 +69,7 @@ export class SettingsStatic { if (SettingsStatic._settings) { return SettingsStatic._settings } - debug('creating settings') + logger('creating settings') const basePath = SettingsStatic.getSettingsFileBasePath() if (!fs.existsSync(basePath)) { @@ -95,14 +95,14 @@ export class SettingsStatic { return SettingsStatic._settings } catch (error) { - debug('error reading config file at %s: %o', settingsFilePath, error) + logger('error reading config file at %s: %o', settingsFilePath, error) return defaults } } public static saveSettings(path: string, settings: Settings) { - debug('saving settings to %s: %o', path, settings) + logger('saving settings to %s: %o', path, settings) return fs.writeFileSync(join(path, 'settings.yaml'), yaml.dump(settings), { encoding: 'utf-8' }) } @@ -113,7 +113,7 @@ export class SettingsStatic { const settingsFilePath = join(basePath, `settings.${fileType}`) const reload = () => { - debug('reloading settings') + logger('reloading settings') SettingsStatic._settings = undefined SettingsStatic.createSettings() } diff --git a/src/utils/sliding-window-rate-limiter.ts b/src/utils/sliding-window-rate-limiter.ts index f6d5d833..44d94432 100644 --- a/src/utils/sliding-window-rate-limiter.ts +++ b/src/utils/sliding-window-rate-limiter.ts @@ -2,7 +2,7 @@ import { IRateLimiter, IRateLimiterOptions } from '../@types/utils' import { createLogger } from '../factories/logger-factory' import { ICacheAdapter } from '../@types/adapters' -const debug = createLogger('sliding-window-rate-limiter') +const logger = createLogger('sliding-window-rate-limiter') export class SlidingWindowRateLimiter implements IRateLimiter { public constructor(private readonly cache: ICacheAdapter) {} @@ -20,7 +20,7 @@ export class SlidingWindowRateLimiter implements IRateLimiter { const hits = entries.reduce((acc, timestampAndStep) => acc + Number(timestampAndStep.split(':')[1]), 0) - debug('hit count on %s bucket: %d', key, hits) + logger('hit count on %s bucket: %d', key, hits) return hits > options.rate } diff --git a/test/unit/services/maintenance-service.spec.ts b/test/unit/services/maintenance-service.spec.ts index 61770ca9..33b9319e 100644 --- a/test/unit/services/maintenance-service.spec.ts +++ b/test/unit/services/maintenance-service.spec.ts @@ -32,7 +32,6 @@ describe('MaintenanceService', () => { describe('clearOldEvents', () => { it('purges events when retention.maxDays is a positive number', async () => { - const consoleInfoStub = sandbox.stub(console, 'info') const currentSettings: Settings = { limits: { event: { @@ -62,9 +61,6 @@ describe('MaintenanceService', () => { kindWhitelist: [62], pubkeyWhitelist: ['aabbcc'], }) - expect(consoleInfoStub).to.have.been.calledOnceWithExactly( - '[Maintenance] Deleted events: deleted=4, expired=3, retained=3.', - ) }) it('does not purge events when retention.maxDays is -1', async () => { @@ -109,7 +105,6 @@ describe('MaintenanceService', () => { } as any settings.returns(currentSettings) eventRepository.deleteExpiredAndRetained.rejects(new Error('DB Error')) - sandbox.stub(console, 'error') // Should not throw await service.clearOldEvents() From 7e6fa8a01c171a6a9bf840fe4e572af6d217e96a Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 03:08:21 +0530 Subject: [PATCH 7/8] test: add logger factory coverage --- test/unit/factories/logger-factory.spec.ts | 102 +++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 test/unit/factories/logger-factory.spec.ts diff --git a/test/unit/factories/logger-factory.spec.ts b/test/unit/factories/logger-factory.spec.ts new file mode 100644 index 00000000..ef47aede --- /dev/null +++ b/test/unit/factories/logger-factory.spec.ts @@ -0,0 +1,102 @@ +import { expect } from 'chai' +import Sinon from 'sinon' + +import { createLogger } from '../../../src/factories/logger-factory' +import { logger as baseLogger } from '../../../src/logger' + +type StubPinoLogger = { + level: string + debug: Sinon.SinonStub + info: Sinon.SinonStub + warn: Sinon.SinonStub + error: Sinon.SinonStub + fatal: Sinon.SinonStub + child: Sinon.SinonStub +} + +const createStubPinoLogger = (sandbox: Sinon.SinonSandbox): StubPinoLogger => ({ + level: 'info', + debug: sandbox.stub(), + info: sandbox.stub(), + warn: sandbox.stub(), + error: sandbox.stub(), + fatal: sandbox.stub(), + child: sandbox.stub(), +}) + +describe('createLogger', () => { + let sandbox: Sinon.SinonSandbox + let rootInstance: StubPinoLogger + let childInstance: StubPinoLogger + + beforeEach(() => { + sandbox = Sinon.createSandbox() + + rootInstance = createStubPinoLogger(sandbox) + childInstance = createStubPinoLogger(sandbox) + + sandbox.stub(baseLogger, 'child').returns(rootInstance as any) + rootInstance.child.callsFake(() => childInstance as any) + childInstance.child.callsFake(() => childInstance as any) + }) + + afterEach(() => { + sandbox.restore() + }) + + it('enables debug level when requested', () => { + createLogger('payments', { enabled: true }) + + expect(rootInstance.level).to.equal('debug') + }) + + it('logs formatted string messages', () => { + const logger = createLogger('payments') + + logger.info('invoice %s created', 'abc123') + + Sinon.assert.calledOnceWithExactly(rootInstance.info, 'invoice abc123 created') + }) + + it('logs Error instances under err key', () => { + const logger = createLogger('payments') + const error = new Error('boom') + + logger.error(error) + + Sinon.assert.calledOnceWithExactly(rootInstance.error, { err: error }) + }) + + it('logs mixed non-string messages safely', () => { + const logger = createLogger('payments') + + logger.warn({ id: 42 }, 'extra') + + Sinon.assert.calledOnce(rootInstance.warn) + expect(rootInstance.warn.firstCall.args[0]).to.contain('id: 42') + }) + + it('forwards plain objects when no extra args are provided', () => { + const logger = createLogger('payments') + const payload = { status: 'ok' } + + logger.fatal(payload) + + Sinon.assert.calledOnceWithExactly(rootInstance.fatal, payload) + }) + + it('supports child and extended loggers', () => { + const logger = createLogger('payments') + + const childLogger = logger.child({ requestId: 'req-1' }) + childLogger.info('child logger message') + + const extendedLogger = logger.extend('settlements') + extendedLogger.debug('extended logger message') + + Sinon.assert.calledWith(rootInstance.child, { requestId: 'req-1' }) + Sinon.assert.calledWith(rootInstance.child, Sinon.match.has('scope', Sinon.match.string)) + Sinon.assert.calledWithExactly(childInstance.info, 'child logger message') + Sinon.assert.calledWithExactly(childInstance.debug, 'extended logger message') + }) +}) From 4a7e41095964c71e278e0213ca2b1ef2a2f48de6 Mon Sep 17 00:00:00 2001 From: Priyanshubhartistm Date: Sun, 19 Apr 2026 19:11:19 +0530 Subject: [PATCH 8/8] fix: address review comments for logging and tests --- src/factories/logger-factory.ts | 22 ++++++++++++++++++++- src/repositories/invoice-repository.ts | 2 +- src/services/payments-service.ts | 4 ++-- src/tor/client.ts | 1 - test/unit/factories/logger-factory.spec.ts | 22 +++++++++++++++++++++ test/unit/services/payments-service.spec.ts | 2 +- test/unit/utils/settings.spec.ts | 6 +++++- 7 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/factories/logger-factory.ts b/src/factories/logger-factory.ts index 5cdb0273..99e8eae5 100644 --- a/src/factories/logger-factory.ts +++ b/src/factories/logger-factory.ts @@ -47,6 +47,26 @@ const logAtLevel = ( return } + const errorFromArgs = args.find((arg) => arg instanceof Error) as Error | undefined + + if (errorFromArgs) { + if (typeof message === 'string') { + instance[level]({ err: errorFromArgs }, safeFormat(message, args)) + return + } + + const data = [message, ...args].filter((arg) => !(arg instanceof Error)) + const formatted = data.map(stringifyForLog).join(' ') + + if (formatted) { + instance[level]({ err: errorFromArgs }, formatted) + } else { + instance[level]({ err: errorFromArgs }) + } + + return + } + if (typeof message === 'string') { instance[level](safeFormat(message, args)) return @@ -87,7 +107,7 @@ const createMessageLogger = (instance: PinoLogger, scope: string): MessageLogger export const createLogger = ( namespace: string, - options: { enabled?: boolean; stdout?: boolean } = { enabled: false, stdout: false }, + options: { enabled?: boolean } = { enabled: false }, ) => { const prefix = cluster.isWorker ? (process.env.WORKER_TYPE ?? 'worker') : 'primary' const scope = namespace ? `${prefix}:${namespace}` : prefix diff --git a/src/repositories/invoice-repository.ts b/src/repositories/invoice-repository.ts index eda5baa4..f912b4bd 100644 --- a/src/repositories/invoice-repository.ts +++ b/src/repositories/invoice-repository.ts @@ -23,7 +23,7 @@ export class InvoiceRepository implements IInvoiceRepository { try { await client.raw('select confirm_invoice(?, ?, ?)', [invoiceId, amountPaid.toString(), confirmedAt.toISOString()]) } catch (error) { - logger.error('Unable to confirm invoice. Reason:', error.message) + logger.error('Unable to confirm invoice. Reason:', error) throw error } diff --git a/src/services/payments-service.ts b/src/services/payments-service.ts index d13d7b34..3c3e98b6 100644 --- a/src/services/payments-service.ts +++ b/src/services/payments-service.ts @@ -29,7 +29,7 @@ export class PaymentsService implements IPaymentsService { try { return await this.invoiceRepository.findPendingInvoices(0, 10) } catch (error) { - logger.info('Unable to get pending invoices.', error) + logger.error('Unable to get pending invoices.', error) throw error } @@ -41,7 +41,7 @@ export class PaymentsService implements IPaymentsService { typeof invoice === 'string' || invoice?.verifyURL ? invoice : invoice.id, ) } catch (error) { - logger.info('Unable to get invoice from payments processor. Reason:', error) + logger.error('Unable to get invoice from payments processor. Reason:', error) throw error } diff --git a/src/tor/client.ts b/src/tor/client.ts index 84e2cdc7..dbd41527 100644 --- a/src/tor/client.ts +++ b/src/tor/client.ts @@ -204,7 +204,6 @@ export const addOnion = async (port: number, host?: string): Promise => if (hiddenService?.PrivateKey) { logger.info('saving private key to %s', path) - logger('saving private key to %s', path) await writeFile(path, hiddenService.PrivateKey, 'utf8') return hiddenService.ServiceID! diff --git a/test/unit/factories/logger-factory.spec.ts b/test/unit/factories/logger-factory.spec.ts index ef47aede..b28cde92 100644 --- a/test/unit/factories/logger-factory.spec.ts +++ b/test/unit/factories/logger-factory.spec.ts @@ -67,6 +67,28 @@ describe('createLogger', () => { Sinon.assert.calledOnceWithExactly(rootInstance.error, { err: error }) }) + it('logs structured err when Error is passed as an arg to string message', () => { + const logger = createLogger('payments') + const error = new Error('boom') + + logger.error('Unable to create invoice. Reason:', error) + + Sinon.assert.calledOnce(rootInstance.error) + Sinon.assert.calledWith(rootInstance.error, { err: error }) + expect(rootInstance.error.firstCall.args[1]).to.be.a('string').and.include('Unable to create invoice. Reason:') + }) + + it('keeps non-error args in message while logging structured err', () => { + const logger = createLogger('payments') + const error = new Error('boom') + + logger.error('error handling message', { kind: 1 }, error) + + Sinon.assert.calledOnce(rootInstance.error) + Sinon.assert.calledWith(rootInstance.error, { err: error }) + expect(rootInstance.error.firstCall.args[1]).to.be.a('string').and.include("kind: 1") + }) + it('logs mixed non-string messages safely', () => { const logger = createLogger('payments') diff --git a/test/unit/services/payments-service.spec.ts b/test/unit/services/payments-service.spec.ts index 7bac5a9f..512fb4a2 100644 --- a/test/unit/services/payments-service.spec.ts +++ b/test/unit/services/payments-service.spec.ts @@ -463,7 +463,7 @@ describe('PaymentsService', () => { expect(eventUtils.broadcastEvent as Sinon.SinonStub).to.have.been.calledOnce }) - it('calls logError and does not throw when the pipeline fails', async () => { + it('does not throw when the pipeline fails', async () => { ;(eventUtils.identifyEvent as Sinon.SinonStub).rejects(new Error('identify failed')) // otherwise() swallows the error — the method must resolve, not reject diff --git a/test/unit/utils/settings.spec.ts b/test/unit/utils/settings.spec.ts index fb0c7126..ca428bdf 100644 --- a/test/unit/utils/settings.spec.ts +++ b/test/unit/utils/settings.spec.ts @@ -198,7 +198,11 @@ describe('SettingsStatic', () => { expect(SettingsStatic.createSettings()).to.be.an('object') - expect(existsSyncStub).to.have.been.calledWithExactly('/some/path/settings.json') + const settingsPathExistsChecks = existsSyncStub.getCalls().filter((call) => { + return call.args.length === 1 && call.args[0] === '/some/path/settings.json' + }) + + expect(settingsPathExistsChecks).to.have.lengthOf(1) expect(getSettingsFileBasePathStub).to.have.been.calledOnce expect(saveSettingsStub).to.have.been.calledOnceWithExactly('/some/path/settings.json', Sinon.match.object) expect(loadSettingsStub).to.have.been.called