diff --git a/package-lock.json b/package-lock.json index 79a15605..dc94c1c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,8 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", + "@foxxmd/logging": "^0.1.11", "@foxxmd/string-sameness": "^0.4.0", - "@foxxmd/winston": "3.3.31", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", "@reduxjs/toolkit": "^1.9.5", @@ -66,13 +66,8 @@ "superagent": "^8.0.9", "tailwindcss": "^3.3.3", "toad-scheduler": "^3.0.0", - "triple-beam": "^1.3.0", "tsx": "^4.7.0", "vite-express": "^0.13.0", - "winston-daily-rotate-file": "^4.5.0", - "winston-duplex": "0.1.3", - "winston-null": "^2.0.0", - "winston-transport": "^4.4.0", "xml2js": "0.5.0", "youtube-music-ts-api": "^1.7.0" }, @@ -105,7 +100,6 @@ "@types/react-window": "^1.8.5", "@types/spotify-web-api-node": "^5.0.7", "@types/superagent": "^4.1.16", - "@types/triple-beam": "^1.3.2", "@types/xml2js": "^0.4.11", "@vitejs/plugin-react": "^4.2.1", "chai": "^4.3.6", @@ -129,6 +123,47 @@ "npm": ">=9.1.0" } }, + "../foxxmd/logging": { + "name": "@foxxmd/logging", + "version": "0.1.11", + "extraneous": true, + "license": "MIT", + "dependencies": { + "pino": "^8.19.0", + "pino-abstract-transport": "^1.1.0", + "pino-pretty": "^11.0.0", + "pino-roll": "^1.0.1", + "pump": "^3.0.0" + }, + "devDependencies": { + "@types/chai": "^4.3.0", + "@types/chai-as-promised": "^7.1.5", + "@types/dateformat": "^5.0.2", + "@types/mocha": "^9.1.0", + "@types/node": "^18.0.0", + "@types/pump": "^1.1.3", + "chai": "^4.3.6", + "chai-as-promised": "^7.1.1", + "dateformat": "^5.0.3", + "mocha": "^10.2.0", + "p-event": "^6.0.0", + "sinon": "^17.0.1", + "sinon-chai": "^3.7.0", + "ts-essentials": "^9.4.1", + "tshy": "^1.7.0", + "tsx": "^4.7.1", + "typedoc": "^0.25.11", + "typedoc-plugin-inline-sources": "^1.0.2", + "typedoc-plugin-missing-exports": "^2.2.0", + "typedoc-plugin-replace-text": "^3.3.0", + "typescript": "^5.3.3", + "with-local-tmp-dir": "^5.1.1" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.3.0" + } + }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -1010,14 +1045,6 @@ "statuses": "^2.0.1" } }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1040,16 +1067,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, "node_modules/@dbus-types/dbus": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@dbus-types/dbus/-/dbus-0.0.4.tgz", @@ -1608,6 +1625,22 @@ "zod-validation-error": "^2.1.0" } }, + "node_modules/@foxxmd/logging": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@foxxmd/logging/-/logging-0.1.11.tgz", + "integrity": "sha512-k0qdUFvtyzqlrCDxaXNJCjpKCzfa8jdspArhE/pXd5Lnv69jJvwNVFIwgbfm+Yhrr7rwY+y5/gxSpZKZc19reg==", + "dependencies": { + "pino": "^8.19.0", + "pino-abstract-transport": "^1.1.0", + "pino-pretty": "^11.0.0", + "pino-roll": "^1.0.1", + "pump": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.3.0" + } + }, "node_modules/@foxxmd/string-sameness": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@foxxmd/string-sameness/-/string-sameness-0.4.0.tgz", @@ -1621,26 +1654,6 @@ "npm": ">=9.3.0" } }, - "node_modules/@foxxmd/winston": { - "version": "3.3.31", - "resolved": "https://registry.npmjs.org/@foxxmd/winston/-/winston-3.3.31.tgz", - "integrity": "sha512-7Qz87xrYjYo9K8biVCYDFN7z2XTQEurlyVAcqGQBfko4CJCovTbBuNMXHQFiysF4zmB8w15thVR30wdpCXJHUA==", - "dependencies": { - "@dabh/diagnostics": "^2.0.2", - "async": "^3.1.0", - "is-stream": "^2.0.0", - "lodash.mergewith": "^4.6.2", - "logform": "^2.2.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, "node_modules/@homebridge/long": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/@homebridge/long/-/long-5.2.1.tgz", @@ -2877,11 +2890,6 @@ "@types/jest": "*" } }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -3244,6 +3252,17 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "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==", + "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", @@ -3510,11 +3529,6 @@ "node": ">=4" } }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3528,6 +3542,14 @@ "node": ">= 4.0.0" } }, + "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==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.17", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", @@ -3621,7 +3643,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -4176,15 +4197,6 @@ "node": ">=6" } }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -4201,44 +4213,10 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -4394,12 +4372,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/cycle": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", - "integrity": "sha512-TVF6svNzeQCOpjCqsy0/CSy8VgObG3wXusJ73xW2GbG5rGx7lC8zxDSURicsXI2UsGdi2L0QNRCi745/wUDvsA==", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "engines": { - "node": ">=0.4.0" + "node": "*" } }, "node_modules/dayjs": { @@ -4743,11 +4721,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -5121,6 +5094,22 @@ "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==", + "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==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -5278,6 +5267,11 @@ "node": "> 0.1.90" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5310,6 +5304,14 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "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==", + "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", @@ -5323,11 +5325,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -5364,14 +5361,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-stream-rotator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", - "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", - "dependencies": { - "moment": "^2.29.1" - } - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5497,11 +5486,6 @@ "node": ">=0.4.0" } }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, "node_modules/follow-redirects": { "version": "1.15.5", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", @@ -6018,6 +6002,11 @@ "integrity": "sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==", "dev": true }, + "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==" + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -6081,7 +6070,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -6261,11 +6249,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -6533,6 +6516,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "peer": true, "engines": { "node": ">=8" }, @@ -6933,6 +6918,14 @@ "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==", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7170,11 +7163,6 @@ "uuid": "^8.3.2" } }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, "node_modules/lastfm-node-client": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/lastfm-node-client/-/lastfm-node-client-2.2.0.tgz", @@ -7250,11 +7238,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -7271,30 +7254,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/logform": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", - "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/logform/node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "engines": { - "node": ">=10" - } - }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -7600,14 +7559,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, "node_modules/mopidy": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/mopidy/-/mopidy-1.3.0.tgz", @@ -8296,6 +8247,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "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", @@ -8323,14 +8282,6 @@ "wrappy": "1" } }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -8778,6 +8729,157 @@ "node": ">=6" } }, + "node_modules/pino": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.19.0.tgz", + "integrity": "sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.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.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "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/pino-pretty": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.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": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "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/pino-roll": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pino-roll/-/pino-roll-1.0.1.tgz", + "integrity": "sha512-9mMf0dn7KsqiY/Bs7gDRoBd5lsksSknPnbTEFKQVTeuGXi8RFuXS5UkLw4StIY/uG6PMrM7KJydB7ramoyVDzA==", + "dependencies": { + "sonic-boom": "^3.8.0" + } + }, + "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==" + }, + "node_modules/pino/node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -9040,6 +9142,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -9053,6 +9163,11 @@ "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==" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -9166,6 +9281,11 @@ } ] }, + "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==" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -9356,6 +9476,14 @@ "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==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/recast": { "version": "0.20.5", "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", @@ -9698,6 +9826,11 @@ "loose-envify": "^1.1.0" } }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -9886,14 +10019,6 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -9947,6 +10072,14 @@ "node": ">=6" } }, + "node_modules/sonic-boom": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz", + "integrity": "sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==", + "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", @@ -10008,6 +10141,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/spotify-web-api-node": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/spotify-web-api-node/-/spotify-web-api-node-5.0.2.tgz", @@ -10023,14 +10164,6 @@ "dev": true, "peer": true }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -10146,7 +10279,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, "engines": { "node": ">=8" }, @@ -10437,11 +10569,6 @@ "node": "*" } }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10467,6 +10594,14 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -10536,14 +10671,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/ts-api-utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", @@ -11194,141 +11321,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/winston": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", - "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", - "peer": true, - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-compat": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/winston-compat/-/winston-compat-0.1.5.tgz", - "integrity": "sha512-EPvPcHT604AV3Ji6d3+vX8ENKIml9VSxMRnPQ+cuK/FX6f3hvPP2hxyoeeCOCFvDrJEujalfcKWlWPvAnFyS9g==", - "dependencies": { - "cycle": "~1.0.3", - "logform": "^1.6.0", - "triple-beam": "^1.2.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, - "node_modules/winston-compat/node_modules/fecha": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", - "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" - }, - "node_modules/winston-compat/node_modules/logform": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", - "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", - "dependencies": { - "colors": "^1.2.1", - "fast-safe-stringify": "^2.0.4", - "fecha": "^2.3.3", - "ms": "^2.1.1", - "triple-beam": "^1.2.0" - } - }, - "node_modules/winston-daily-rotate-file": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-4.7.1.tgz", - "integrity": "sha512-7LGPiYGBPNyGHLn9z33i96zx/bd71pjBn9tqQzO3I4Tayv94WPmBNwKC7CO1wPHdP9uvu+Md/1nr6VSH9h0iaA==", - "dependencies": { - "file-stream-rotator": "^0.6.1", - "object-hash": "^2.0.1", - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "winston": "^3" - } - }, - "node_modules/winston-daily-rotate-file/node_modules/object-hash": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", - "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/winston-duplex": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/winston-duplex/-/winston-duplex-0.1.3.tgz", - "integrity": "sha512-zBMeRsuiVobqhpRQ1Jetk3i7cVR2Vm/VvWEq0pcnMb4abueW/WycomtKO1j94nLzi2+lgjZVrODSDgHWmRnPGA==", - "dependencies": { - "triple-beam": "^1.3.0", - "winston-transport": "^4.4.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/winston-null": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/winston-null/-/winston-null-2.0.0.tgz", - "integrity": "sha512-uS5tJB5OkLWOoc3I7/LsWUfTIa5Du38XSviHf/b0TINK659Np9368FJwTt15UoZQYUQVxLpM06lxk2dKET22Xw==", - "dependencies": { - "semver": "^5.6.0", - "winston-compat": "^0.1.4", - "winston-transport": "^4.2.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "winston": ">=2.0.0" - } - }, - "node_modules/winston-null/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston/node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", diff --git a/package.json b/package.json index bf0577a1..b9fe2b2e 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,8 @@ "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", "@foxxmd/chromecast-client": "^1.0.4", + "@foxxmd/logging": "^0.1.11", "@foxxmd/string-sameness": "^0.4.0", - "@foxxmd/winston": "3.3.31", "@kenyip/backoff-strategies": "^1.0.4", "@react-nano/use-event-source": "^0.13.0", "@reduxjs/toolkit": "^1.9.5", @@ -100,13 +100,8 @@ "superagent": "^8.0.9", "tailwindcss": "^3.3.3", "toad-scheduler": "^3.0.0", - "triple-beam": "^1.3.0", "tsx": "^4.7.0", "vite-express": "^0.13.0", - "winston-daily-rotate-file": "^4.5.0", - "winston-duplex": "0.1.3", - "winston-null": "^2.0.0", - "winston-transport": "^4.4.0", "xml2js": "0.5.0", "youtube-music-ts-api": "^1.7.0" }, @@ -139,7 +134,6 @@ "@types/react-window": "^1.8.5", "@types/spotify-web-api-node": "^5.0.7", "@types/superagent": "^4.1.16", - "@types/triple-beam": "^1.3.2", "@types/xml2js": "^0.4.11", "@vitejs/plugin-react": "^4.2.1", "chai": "^4.3.6", diff --git a/patches/winston-daily-rotate-file+4.7.1.patch b/patches/winston-daily-rotate-file+4.7.1.patch deleted file mode 100644 index 5d26eabe..00000000 --- a/patches/winston-daily-rotate-file+4.7.1.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/winston-daily-rotate-file/index.js b/node_modules/winston-daily-rotate-file/index.js -index c818660..a8d3040 100644 ---- a/node_modules/winston-daily-rotate-file/index.js -+++ b/node_modules/winston-daily-rotate-file/index.js -@@ -1,6 +1,6 @@ - 'use strict'; - --var winston = require('winston'); -+var winston = require('@foxxmd/winston'); - var DailyRotateFile = require('./daily-rotate-file'); - - winston.transports.DailyRotateFile = DailyRotateFile; diff --git a/patches/winston-null+2.0.0.patch b/patches/winston-null+2.0.0.patch deleted file mode 100644 index 90be862c..00000000 --- a/patches/winston-null+2.0.0.patch +++ /dev/null @@ -1,10 +0,0 @@ -diff --git a/node_modules/winston-null/index.js b/node_modules/winston-null/index.js -index 1a8a4ff..5088d6c 100644 ---- a/node_modules/winston-null/index.js -+++ b/node_modules/winston-null/index.js -@@ -1,4 +1,4 @@ --const winston = require('winston'); -+const winston = require('@foxxmd/winston'); - const compat = require('winston-compat'); - const semver = require('semver'); - diff --git a/src/backend/common/infrastructure/Atomic.ts b/src/backend/common/infrastructure/Atomic.ts index 0909f857..1b36c804 100644 --- a/src/backend/common/infrastructure/Atomic.ts +++ b/src/backend/common/infrastructure/Atomic.ts @@ -1,10 +1,10 @@ import {Dayjs} from "dayjs"; import {FixedSizeList} from 'fixed-size-list'; -import {Logger} from '@foxxmd/winston'; +import {Logger} from '@foxxmd/logging'; import TupleMap from "../TupleMap.js"; import {Request, Response} from "express"; import {NextFunction, ParamsDictionary, Query} from "express-serve-static-core"; -import { LogLevel, logLevels, PlayMeta, PlayObject } from "../../../core/Atomic.js"; +import {PlayMeta, PlayObject} from "../../../core/Atomic.js"; export type SourceType = 'spotify' | 'plex' | 'tautulli' | 'subsonic' | 'jellyfin' | 'lastfm' | 'deezer' | 'ytmusic' | 'mpris' | 'mopidy' | 'listenbrainz' | 'jriver' | 'kodi' | 'webscrobbler' | 'chromecast'; export const sourceTypes: SourceType[] = ['spotify', 'plex', 'tautulli', 'subsonic', 'jellyfin', 'lastfm', 'deezer', 'ytmusic', 'mpris', 'mopidy', 'listenbrainz', 'jriver', 'kodi', 'webscrobbler', 'chromecast']; @@ -105,45 +105,6 @@ export interface RemoteIdentityParts { agent: string | undefined } -export interface LogConfig { - level?: string - file?: string | false - stream?: string - console?: string | false -} - -export interface LogOptions { - /** - * Specify the minimum log level for all log outputs without their own level specified. - * - * Defaults to env `LOG_LEVEL` or `info` if not specified. - * - * @default 'info' - * */ - level?: LogLevel - /** - * Specify the minimum log level to output to rotating files. If `false` no log files will be created. - * */ - file?: LogLevel | false - /** - * Specify the minimum log level streamed to the UI - * */ - stream?: LogLevel - /** - * Specify the minimum log level streamed to the console (or docker container) - * */ - console?: LogLevel | false -} - -export const asLogOptions = (obj: LogConfig = {}): obj is LogOptions => { - return Object.entries(obj).every(([key, val]) => { - if(key !== 'file') { - return val === undefined || logLevels.includes(val.toLocaleLowerCase()); - } - return val === undefined || val === false || logLevels.includes(val.toLocaleLowerCase()); - }); -} - // https://stackoverflow.com/questions/40510611/typescript-interface-require-one-of-two-properties-to-exist#comment116238286_49725198 export type RequireAtLeastOne = Omit & { [ P in R ] : Required> & Partial> }[R]; @@ -232,3 +193,5 @@ export interface MdnsDeviceInfo { type: string addresses: string[] } + +export type AbstractApiOptions = Record & { logger: Logger } diff --git a/src/backend/common/infrastructure/config/aioConfig.ts b/src/backend/common/infrastructure/config/aioConfig.ts index 5e8dd863..1eb73bd2 100644 --- a/src/backend/common/infrastructure/config/aioConfig.ts +++ b/src/backend/common/infrastructure/config/aioConfig.ts @@ -3,7 +3,8 @@ import { RequestRetryOptions } from "./common.js"; import { SourceAIOConfig } from "./source/sources.js"; import { ClientAIOConfig } from "./client/clients.js"; import { WebhookConfig } from "./health/webhooks.js"; -import { LogOptions } from "../Atomic.js"; +import {LogOptions} from "@foxxmd/logging"; + export interface SourceDefaults extends SourceRetryOptions { /** diff --git a/src/backend/common/logging.ts b/src/backend/common/logging.ts index 346840cd..ec13c566 100644 --- a/src/backend/common/logging.ts +++ b/src/backend/common/logging.ts @@ -1,367 +1,48 @@ import path from "path"; import { projectDir } from "./index.js"; -import * as winstonNs from '@foxxmd/winston'; -import winstonDef from '@foxxmd/winston'; -import {DuplexTransport} from "winston-duplex"; -import { asLogOptions, LogConfig, LogOptions } from "./infrastructure/Atomic.js"; import process from "process"; -import { fileOrDirectoryIsWriteable, mergeArr, parseBool } from "../utils.js"; -import {ErrorWithCause, stackWithCauses} from "pony-cause"; -import {NullTransport} from 'winston-null'; -import DailyRotateFile from 'winston-daily-rotate-file'; -import dayjs from "dayjs"; -import stringify from 'safe-stable-stringify'; -import {SPLAT, LEVEL, MESSAGE} from 'triple-beam'; -import { LogInfo, LogLevel } from "../../core/Atomic.js"; -import TransportStream from "winston-transport"; -import {format} from 'logform'; - -const {combine, printf, timestamp, label, splat, errors} = format; - -//const {transports} = winstonNew; -const {loggers, transports} = winstonDef; +import { + parseLogOptions, + loggerAppRolling, + LogOptions as FoxLogOptions, + Logger as FoxLogger, + childLogger, +} from '@foxxmd/logging'; +import {PassThrough, Transform} from "node:stream"; +import {buildLogger, buildDestinationStdout, buildDestinationJsonPrettyStream} from "@foxxmd/logging/factory"; export let logPath = path.resolve(projectDir, `./logs`); if (typeof process.env.CONFIG_DIR === 'string') { logPath = path.resolve(process.env.CONFIG_DIR, './logs'); } -loggers.add('noop', {transports: [new NullTransport()]}); - -export const getLogger = (config: LogConfig = {}, name = 'app'): winstonNs.Logger => { - - if (!loggers.has(name)) { - const errors: (Error | string)[] = []; - - let options: LogOptions = {}; - if (asLogOptions(config)) { - options = config; - } else { - errors.push(`Logging levels were not valid. Must be one of: 'error', 'warn', 'info', 'verbose', 'debug' -- 'file' may be false.`); - } - - const {level: configLevel} = options; - const defaultLevel = process.env.LOG_LEVEL || (parseBool(process.env.DEBUG_MODE) ? 'debug' : 'info'); - let consoleLevel: string | boolean = process.env.CONSOLE_LEVEL || 'debug'; - if(consoleLevel === 'false') { - consoleLevel = false; - } - let fileLevel: string | boolean = process.env.FILE_LEVEL || defaultLevel; - if(fileLevel === 'false') { - fileLevel = false; - } - const { - level = configLevel || defaultLevel, - file = configLevel || fileLevel, - stream = configLevel || 'debug', - console = configLevel || consoleLevel - } = options; - - const myTransports: TransportStream[] = [ - new DuplexTransport({ - stream: { - transform: (chunk, e, cb) => { - cb(null, chunk); - }, - objectMode: true, - }, - name: 'duplex', - level: stream, - dump: false, - }) - ]; - - if(console !== false) { - myTransports.push(new transports.Console({ - level: console, - })); - } - - if (file !== false) { - const rotateTransport = new DailyRotateFile({ - dirname: logPath, - createSymlink: true, - symlinkName: 'scrobble-current.log', - filename: 'scrobble-%DATE%.log', - datePattern: 'YYYY-MM-DD', - maxSize: '5m', - level: file, - }); - - try { - fileOrDirectoryIsWriteable(logPath); - myTransports.push(rotateTransport); - } catch (e: any) { - const msg = 'WILL NOT write logs to rotating file due to an error while trying to access the specified logging directory'; - errors.push(new ErrorWithCause(msg, {cause: e})); - } - } - - const loggerOptions: winstonNs.LoggerOptions = { - level: level, - format: labelledFormat(), - transports: myTransports, - }; - - loggers.add(name, loggerOptions); - - const logger = loggers.get(name); - if (errors.length > 0) { - for (const e of errors) { - logger.error(e); - } - } - return logger; - } - return loggers.get(name); -} - -const breakSymbol = '
'; -export const formatLogToHtml = (chunk: any) => { - const line = chunk.toString().replace('\n', breakSymbol) - .replace(/(debug)\s/gi, '$1 ') - .replace(/(warn)\s/gi, '$1 ') - .replace(/(info)\s/gi, '$1 ') - .replace(/(verbose)\s/gi, '$1 ') - .replace(/(error)\s/gi, '$1 ') - .trim(); - if(line.slice(-6) !== breakSymbol) { - return `${line}${breakSymbol}`; - } - return line; +export const initLogger = (): [FoxLogger, Transform] => { + const opts = parseLogOptions({file: false, console: 'debug'}) + const stream = new PassThrough({objectMode: true}); + const logger = buildLogger('debug', [ + buildDestinationStdout(opts.console), + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) + ]); + return [logger, stream]; } -const levelSymbol = Symbol.for('level'); -const s = splat(); -//const errorsFormat = errors({stack: true}); -const CWD = process.cwd(); - -const causeKeys = ['name', 'cause', 'showStopper'] - -export const defaultFormat = (defaultLabel = 'App') => printf(({ - label, - [levelSymbol]: levelSym, - level, - message, - labels = [defaultLabel], - leaf, - timestamp, - durationMs, - [SPLAT]: splatObj, - stack, - id, - cause, - showStopper, - ...rest - }) => { - const keys = Object.keys(rest); - const stringifyValue = keys.length > 0 && !keys.every(x => causeKeys.some(y => y == x)) ? stringify.default(rest) : ''; - let msg = message; - let stackMsg = ''; - if (stack !== undefined) { - const stackArr = stack.split('\n'); - const stackTop = stackArr[0]; - const cleanedStack = stackArr - .slice(1) // don't need actual error message since we are showing it as msg - .map((x: string) => x.replace(CWD, 'CWD')) // replace file location up to cwd for user privacy - .join('\n'); // rejoin with newline to preserve formatting - stackMsg = `\n${cleanedStack}`; - if (msg === undefined || msg === null || typeof message === 'object') { - msg = stackTop; - } else { - stackMsg = `\n${stackTop}${stackMsg}` - } - } - - const nodes = Array.isArray(labels) ? labels : [labels]; - if (leaf !== null && leaf !== undefined && !nodes.includes(leaf)) { - nodes.push(leaf); - } - const labelContent = `${nodes.map((x: string) => `[${x}]`).join(' ')}`; - - return `${timestamp} ${level.padEnd(8)}: ${labelContent} ${msg}${stringifyValue !== '' ? ` ${stringifyValue}` : ''}${stackMsg}`; -}); - -// https://knowyourmeme.com/memes/cereal-guy -// this number will never overflow -let seqId: number = 0; -export const labelledFormat = (labelName = 'App') => { - const l = label({label: labelName, message: false}); - return combine( - timestamp( - { - format: () => dayjs().local().format(), - } - ), - { - transform: (info, opts) => { - info.id = seqId; - seqId++; - return info; - } - }, - l, - s, - errorAwareFormat, - defaultFormat(labelName), - ); +export const appLogger = async (config?: FoxLogOptions): Promise<[FoxLogger, PassThrough]> => { + const stream = new PassThrough({objectMode: true}); + const opts = parseLogOptions(config) + const logger = await loggerAppRolling(config, { + logBaseDir: typeof process.env.CONFIG_DIR === 'string' ? process.env.CONFIG_DIR : undefined, + destinations: [ + buildDestinationJsonPrettyStream(opts.console, {destination: stream, object: true, colorize: false}) + ] + }); + return [logger, stream]; } - -export const logLevels = { - error: 0, - warn: 1, - info: 2, - http: 3, - verbose: 4, - debug: 5, - trace: 5, - silly: 6 -}; - -export const LOG_LEVEL_REGEX: RegExp = /\s*(debug|warn|info|error|verbose)\s*:/i -export const isLogLineMinLevel = (log: string | LogInfo, minLevelText: LogLevel): boolean => { - const minLevel = logLevels[minLevelText]; - let level: number; - - if(typeof log === 'string') { - const lineLevelMatch = log.match(LOG_LEVEL_REGEX) - if (lineLevelMatch === null) { - return false; - } - level = logLevels[lineLevelMatch[1]]; - } else { - const lineLevelMatch = log.level; - level = logLevels[lineLevelMatch]; - } - return level <= minLevel; -} - -export const isLogLevelMinLevel = (levelStr: LogLevel, minLevelStr: LogLevel): boolean => logLevels[levelStr] <= logLevels[minLevelStr] - -const isProbablyError = (val: any, explicitErrorName?: string) => { - if(typeof val !== 'object' || val === null) { - return false; - } - const {name, stack} = val; - if(explicitErrorName !== undefined) { - if(name !== undefined && name.toLowerCase().includes(explicitErrorName)) { - return true; - } - if(stack !== undefined && stack.trim().toLowerCase().indexOf(explicitErrorName.toLowerCase()) === 0) { - return true; - } - return false; - } else if(stack !== undefined) { - return true; - } else if(name !== undefined && name.toLowerCase().includes('error')) { - return true; - } - - return false; -} - -const errorAwareFormat = { - transform: (einfo: any, {stack = true}: any = {}) => { - - // because winston logger.child() re-assigns its input to an object ALWAYS the object we recieve here will never actually be of type Error - const includeStack = stack && (!isProbablyError(einfo, 'simpleerror') && !isProbablyError(einfo.message, 'simpleerror')); - - if (!isProbablyError(einfo.message) && !isProbablyError(einfo)) { - return einfo; - } - - let info: any = {}; - - if (isProbablyError(einfo)) { - const tinfo = transformError(einfo); - info = Object.assign({}, tinfo, { - level: einfo.level, - [LEVEL]: einfo[LEVEL] || einfo.level, - message: tinfo.message, - - [MESSAGE]: tinfo[MESSAGE] || tinfo.message - }); - if(includeStack) { - // so we have to create a dummy error and re-assign all error properties from our info object to it so we can get a proper stack trace - const dummyErr = new ErrorWithCause(''); - const names = Object.getOwnPropertyNames(tinfo); - for(const k of names) { - // eslint-disable-next-line no-prototype-builtins - if(dummyErr.hasOwnProperty(k) || k === 'cause') { - dummyErr[k] = tinfo[k]; - } - } - info.stack = stackWithCauses(dummyErr); - } - } else { - const err = transformError(einfo.message); - info = Object.assign({}, einfo, err); - info.message = err.message; - info[MESSAGE] = err.message; - - if(includeStack) { - const dummyErr = new ErrorWithCause(''); - // Error properties are not enumerable - // https://stackoverflow.com/a/18278145/1469797 - const names = Object.getOwnPropertyNames(err); - for(const k of names) { - // eslint-disable-next-line no-prototype-builtins - if(dummyErr.hasOwnProperty(k) || k === 'cause') { - dummyErr[k] = err[k]; - } - } - info.stack = stackWithCauses(dummyErr); - } - } - - // remove redundant message from stack and make stack causes easier to read - if(info.stack !== undefined) { - let cleanedStack = info.stack.replace(info.message, ''); - cleanedStack = `${cleanedStack}`; - cleanedStack = cleanedStack.replaceAll('caused by:', '\ncaused by:'); - info.stack = cleanedStack; - } - - return info; - } -} - -export const transformError = (err: Error): any => _transformError(err, new Set()); - -const _transformError = (err: Error, seen: Set) => { - if (!err || !isProbablyError(err)) { - return ''; - } - if (seen.has(err)) { - return err; - } - - try { - - // @ts-expect-error type missing expected props - const mOpts = err.matchOptions ?? matchOptions; - - const cause = err.cause as unknown; - - if (cause !== undefined && cause instanceof Error) { - // @ts-expect-error type missing expected props - err.cause = _transformError(cause, seen, mOpts); - } - - return err; - } catch (e: any) { - // oops :( - // we're gonna swallow silently instead of reporting to avoid any infinite nesting and hopefully the original error looks funny enough to provide clues as to what to fix here - return err; - } -} - export class MaybeLogger { - logger?: winstonNs.Logger + logger?: FoxLogger - constructor(logger?: winstonNs.Logger, label?: string) { + constructor(logger?: FoxLogger, label?: string) { if (logger !== undefined && label !== undefined) { - this.logger = logger.child({labels: [label]}, mergeArr); + this.logger = childLogger(logger, label); } else { this.logger = logger; } diff --git a/src/backend/common/vendor/AbstractApiClient.ts b/src/backend/common/vendor/AbstractApiClient.ts index fba86e1c..8357e7d1 100644 --- a/src/backend/common/vendor/AbstractApiClient.ts +++ b/src/backend/common/vendor/AbstractApiClient.ts @@ -1,7 +1,5 @@ -import { mergeArr } from "../../utils.js"; -import {Logger} from '@foxxmd/winston'; -import { FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; -import winston from '@foxxmd/winston'; +import {childLogger, Logger} from "@foxxmd/logging"; +import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import { PlayObject } from "../../../core/Atomic.js"; import { capitalize } from "../../../core/StringUtils.js"; @@ -18,13 +16,17 @@ export default abstract class AbstractApiClient { workingCredsPath?: string; redirectUri?: string; - constructor(type: any, name: any, config = {}, options = {}) { + constructor(type: any, name: any, config = {}, options: AbstractApiOptions) { this.type = type; this.name = name; const identifier = `API - ${capitalize(this.type)} - ${name}`; - this.logger = winston.loggers.get('app').child({labels: [identifier]}, mergeArr); + const { + logger: parentLogger, + ...restOptions + } = options; + this.logger = childLogger(parentLogger, identifier); this.config = config; - this.options = options; + this.options = restOptions; } static formatPlayObj(obj: any, options: FormatPlayObjectOptions): PlayObject { diff --git a/src/backend/common/vendor/JRiverApiClient.ts b/src/backend/common/vendor/JRiverApiClient.ts index 848a5420..9868398d 100644 --- a/src/backend/common/vendor/JRiverApiClient.ts +++ b/src/backend/common/vendor/JRiverApiClient.ts @@ -3,7 +3,7 @@ import {JRiverData} from "../infrastructure/config/source/jriver.js"; import request, {Request, Response} from 'superagent'; import xml2js from 'xml2js'; import {ErrorWithCause} from "pony-cause"; -import {DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER} from "../infrastructure/Atomic.js"; const parser = new xml2js.Parser({'async': true}); @@ -97,7 +97,7 @@ export class JRiverApiClient extends AbstractApiClient { token?: string; - constructor(name: any, config: JRiverData, options = {}) { + constructor(name: any, config: JRiverData, options: AbstractApiOptions) { super('JRiver', name, config, options); const { url = 'http://localhost:52199/MCWS/v1/' diff --git a/src/backend/common/vendor/KodiApiClient.ts b/src/backend/common/vendor/KodiApiClient.ts index acde4e7e..4f61e6f6 100644 --- a/src/backend/common/vendor/KodiApiClient.ts +++ b/src/backend/common/vendor/KodiApiClient.ts @@ -5,7 +5,7 @@ import { KodiClient } from 'kodi-api' import normalizeUrl from "normalize-url"; import {URL} from "url"; import { RecentlyPlayedOptions } from "../../sources/AbstractSource.js"; -import { FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import dayjs from "dayjs"; import { PlayObject } from "../../../core/Atomic.js"; @@ -49,7 +49,7 @@ export class KodiApiClient extends AbstractApiClient { declare client: KodiClient; - constructor(name: any, config: KodiData, options = {}) { + constructor(name: any, config: KodiData, options: AbstractApiOptions) { super('Kodi', name, config, options); const { url = 'http://localhost:8080/jsonrpc' diff --git a/src/backend/common/vendor/LastfmApiClient.ts b/src/backend/common/vendor/LastfmApiClient.ts index 75b13c3a..d31d7c4c 100644 --- a/src/backend/common/vendor/LastfmApiClient.ts +++ b/src/backend/common/vendor/LastfmApiClient.ts @@ -8,7 +8,7 @@ import LastFm, { import AbstractApiClient from "./AbstractApiClient.js"; import dayjs from "dayjs"; import {readJson, removeUndefinedKeys, sleep, writeFile} from "../../utils.js"; -import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import { LastfmData } from "../infrastructure/config/client/lastfm.js"; import { PlayObject } from "../../../core/Atomic.js"; import {getNodeNetworkException, isNodeNetworkException} from "../errors/NodeErrors.js"; @@ -36,7 +36,7 @@ export default class LastfmApiClient extends AbstractApiClient { user?: string; declare config: LastfmData; - constructor(name: any, config: Partial & {configDir: string, localUrl: string}, options = {}) { + constructor(name: any, config: Partial & {configDir: string, localUrl: string}, options: AbstractApiOptions) { super('lastfm', name, config, options); const {redirectUri, apiKey, secret, session, configDir} = config; this.redirectUri = `${redirectUri ?? `${config.localUrl}/lastfm/callback`}?state=${name}`; diff --git a/src/backend/common/vendor/ListenbrainzApiClient.ts b/src/backend/common/vendor/ListenbrainzApiClient.ts index 65752229..417836d2 100644 --- a/src/backend/common/vendor/ListenbrainzApiClient.ts +++ b/src/backend/common/vendor/ListenbrainzApiClient.ts @@ -1,7 +1,7 @@ import AbstractApiClient from "./AbstractApiClient.js"; import request, {Request} from 'superagent'; import { ListenBrainzClientData } from "../infrastructure/config/client/listenbrainz.js"; -import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions } from "../infrastructure/Atomic.js"; +import {AbstractApiOptions, DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions} from "../infrastructure/Atomic.js"; import dayjs from "dayjs"; import { stringSameness } from '@foxxmd/string-sameness'; import { combinePartsToString } from "../../utils.js"; @@ -118,7 +118,7 @@ export class ListenbrainzApiClient extends AbstractApiClient { declare config: ListenBrainzClientData; url: string; - constructor(name: any, config: ListenBrainzClientData, options = {}) { + constructor(name: any, config: ListenBrainzClientData, options: AbstractApiOptions) { super('ListenBrainz', name, config, options); const { url = 'https://api.listenbrainz.org/' diff --git a/src/backend/common/vendor/chromecast/interfaces.ts b/src/backend/common/vendor/chromecast/interfaces.ts index dbb59595..41494d1c 100644 --- a/src/backend/common/vendor/chromecast/interfaces.ts +++ b/src/backend/common/vendor/chromecast/interfaces.ts @@ -1,7 +1,7 @@ import {createPlatform, MediaController, PersistentClient} from "@foxxmd/chromecast-client"; import { FormatPlayObjectOptions } from "../../infrastructure/Atomic.js"; import {Dayjs} from "dayjs"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; export type PlatformType = ReturnType; export interface PlatformApplication { diff --git a/src/backend/index.ts b/src/backend/index.ts index 58fdb3ff..559687fd 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,5 +1,5 @@ import 'dotenv/config'; -import {Logger} from '@foxxmd/winston'; +import {LogDataPretty, Logger} from "@foxxmd/logging"; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import isBetween from 'dayjs/plugin/isBetween.js'; @@ -12,15 +12,13 @@ import { projectDir } from "./common/index.js"; import SpotifySource from "./sources/SpotifySource.js"; import { AIOConfig } from "./common/infrastructure/config/aioConfig.js"; import { getRoot } from "./ioc.js"; -import { getLogger } from "./common/logging.js"; -import { LogInfo } from "../core/Atomic.js"; +import {appLogger, initLogger as getInitLogger} from "./common/logging.js"; import { initServer } from "./server/index.js"; import {SimpleIntervalJob, ToadScheduler} from "toad-scheduler"; import { createHeartbeatSourcesTask } from "./tasks/heartbeatSources.js"; import { createHeartbeatClientsTask } from "./tasks/heartbeatClients.js"; import {ErrorWithCause} from "pony-cause"; -import LastfmSource from "./sources/LastfmSource.js"; - +import {loggerDebug, childLogger, LogData, Logger as FoxLogger} from '@foxxmd/logging'; dayjs.extend(utc) dayjs.extend(isBetween); @@ -32,15 +30,16 @@ dayjs.extend(timezone); const scheduler = new ToadScheduler() -let output: LogInfo[] = [] +let output: LogDataPretty[] = [] -const initLogger = getLogger({file: false}, 'init'); -initLogger.stream().on('log', (log: LogInfo) => { - output.unshift(log); - output = output.slice(0, 301); +const [parentInitLogger, initLoggerStream] = getInitLogger(); +const initLogger = childLogger(parentInitLogger, 'Init'); +initLoggerStream.on('data', (log: LogDataPretty) => { +output.unshift(log); +output = output.slice(0, 301); }); -let logger: Logger; +let logger: FoxLogger; process.on('uncaughtExceptionMonitor', (err, origin) => { const appError = new ErrorWithCause(`Uncaught exception is crashing the app! :( Type: ${origin}`, {cause: err}); @@ -79,12 +78,13 @@ const configDir = process.env.CONFIG_DIR || path.resolve(projectDir, `./config`) process.env.DEBUG_MODE = b.toString(); } - const root = getRoot(config); - initLogger.info(`Version: ${root.get('version')}`); + const [aLogger, appLoggerStream] = await appLogger(logging) + logger = childLogger(aLogger, 'App'); - logger = getLogger(logging, 'app'); + const root = getRoot({...config, logger}); + initLogger.info(`Version: ${root.get('version')}`); - initServer(logger, output); + initServer(logger, appLoggerStream, output); if(process.env.IS_LOCAL === 'true') { logger.info('multi-scrobbler can be run as a background service! See: https://foxxmd.github.io/multi-scrobbler/docs/installation/service'); diff --git a/src/backend/ioc.ts b/src/backend/ioc.ts index 6ed2c918..31312f98 100644 --- a/src/backend/ioc.ts +++ b/src/backend/ioc.ts @@ -9,6 +9,7 @@ import { logPath } from "./common/logging.js"; import { WildcardEmitter } from "./common/WildcardEmitter.js"; import normalizeUrl from 'normalize-url'; import fs from 'fs'; +import {Logger} from "@foxxmd/logging"; let version = 'Unknown'; @@ -46,6 +47,7 @@ let root: ReturnType; export interface RootOptions { baseUrl?: string, port?: string | number + logger: Logger } const createRoot = (options?: RootOptions) => { @@ -71,9 +73,9 @@ const createRoot = (options?: RootOptions) => { localUrl = `${u.origin}:${items.mainPort}`; } return { - clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, localUrl, items.configDir), - sources: () => new ScrobbleSources(items.sourceEmitter, localUrl, items.configDir), - notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter), + clients: () => new ScrobbleClients(items.clientEmitter, items.sourceEmitter, localUrl, items.configDir, options.logger), + sources: () => new ScrobbleSources(items.sourceEmitter, localUrl, items.configDir, options.logger), + notifiers: () => new Notifiers(items.notifierEmitter, items.clientEmitter, items.sourceEmitter, options.logger), localUrl, hasDefinedBaseUrl: baseUrl !== undefined, isSubPath: u.pathname !== '/' && u.pathname.length > 0 diff --git a/src/backend/notifier/AbstractWebhookNotifier.ts b/src/backend/notifier/AbstractWebhookNotifier.ts index 4325baf1..bba6be35 100644 --- a/src/backend/notifier/AbstractWebhookNotifier.ts +++ b/src/backend/notifier/AbstractWebhookNotifier.ts @@ -1,5 +1,5 @@ import { GotifyConfig, NtfyConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; -import {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from "@foxxmd/logging"; import { mergeArr } from "../utils.js"; export abstract class AbstractWebhookNotifier { @@ -14,7 +14,7 @@ export abstract class AbstractWebhookNotifier { protected constructor(type: string, defaultName: string, config: GotifyConfig | NtfyConfig, logger: Logger) { this.config = config; const label = `${type} - ${config.name ?? defaultName}` - this.logger = logger.child({labels: [label]}, mergeArr); + this.logger = childLogger(logger, label); } initialize = async () => { diff --git a/src/backend/notifier/GotifyWebhookNotifier.ts b/src/backend/notifier/GotifyWebhookNotifier.ts index cbeca158..f6a4af28 100644 --- a/src/backend/notifier/GotifyWebhookNotifier.ts +++ b/src/backend/notifier/GotifyWebhookNotifier.ts @@ -3,7 +3,7 @@ import { GotifyConfig, PrioritiesConfig, WebhookPayload } from "../common/infras import {gotify} from 'gotify'; import request from 'superagent'; import {HTTPError} from "got"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; export class GotifyWebhookNotifier extends AbstractWebhookNotifier { diff --git a/src/backend/notifier/Notifiers.ts b/src/backend/notifier/Notifiers.ts index 72d39eae..5ddb6cca 100644 --- a/src/backend/notifier/Notifiers.ts +++ b/src/backend/notifier/Notifiers.ts @@ -1,5 +1,4 @@ -import winston, {config, format, Logger} from '@foxxmd/winston'; -import { mergeArr } from "../utils.js"; +import {childLogger, Logger} from '@foxxmd/logging'; import { GotifyConfig, NtfyConfig, WebhookConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; import { GotifyWebhookNotifier } from "./GotifyWebhookNotifier.js"; @@ -17,12 +16,12 @@ export class Notifiers { clientEmitter: EventEmitter; sourceEmitter: EventEmitter; - constructor(emitter: EventEmitter, clientEmitter: EventEmitter, sourceEmitter: EventEmitter) { + constructor(emitter: EventEmitter, clientEmitter: EventEmitter, sourceEmitter: EventEmitter, parentLogger: Logger) { this.emitter = emitter; this.clientEmitter = clientEmitter; this.sourceEmitter = sourceEmitter; - this.logger = winston.loggers.get('app').child({labels: ['Notifiers']}, mergeArr); + this.logger = childLogger(parentLogger, 'Notifiers'); // winston.loggers.get('app').child({labels: ['Notifiers']}, mergeArr); this.sourceEmitter.on('notify', async (payload: WebhookPayload) => { await this.notify(payload); diff --git a/src/backend/notifier/NtfyWebhookNotifier.ts b/src/backend/notifier/NtfyWebhookNotifier.ts index 1ed3a0fb..000cf741 100644 --- a/src/backend/notifier/NtfyWebhookNotifier.ts +++ b/src/backend/notifier/NtfyWebhookNotifier.ts @@ -2,7 +2,7 @@ import { AbstractWebhookNotifier } from "./AbstractWebhookNotifier.js"; import { NtfyConfig, PrioritiesConfig, WebhookPayload } from "../common/infrastructure/config/health/webhooks.js"; import {publish} from 'ntfy'; import request from "superagent"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import {Config} from "ntfy/interfaces.js"; export class NtfyWebhookNotifier extends AbstractWebhookNotifier { diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index 36a55c9c..c8e8d1ca 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -24,7 +24,7 @@ import { TIME_WEIGHT, TITLE_WEIGHT, } from "../common/infrastructure/Atomic.js"; -import {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from "@foxxmd/logging"; import { CommonClientConfig } from "../common/infrastructure/config/client/index.js"; import { Notifiers } from "../notifier/Notifiers.js"; import {FixedSizeList} from 'fixed-size-list'; @@ -95,7 +95,7 @@ export default abstract class AbstractScrobbleClient implements Authenticatable this.type = type; this.name = name; this.identifier = `${capitalize(this.type)} - ${name}`; - this.logger = logger.child({labels: [this.identifier]}, mergeArr); + this.logger = childLogger(logger, this.identifier); this.notifier = notifier; this.emitter = emitter; diff --git a/src/backend/scrobblers/LastfmScrobbler.ts b/src/backend/scrobblers/LastfmScrobbler.ts index 87538e4f..16465a68 100644 --- a/src/backend/scrobblers/LastfmScrobbler.ts +++ b/src/backend/scrobblers/LastfmScrobbler.ts @@ -13,7 +13,7 @@ import { FormatPlayObjectOptions, INITIALIZING, ScrobbledPlayObject } from "../c import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; import {TrackScrobblePayload, TrackScrobbleResponse, UserGetRecentTracksResponse} from "lastfm-node-client"; import { Notifiers } from "../notifier/Notifiers.js"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; import { buildTrackString, capitalize } from "../../core/StringUtils.js"; import EventEmitter from "events"; diff --git a/src/backend/scrobblers/ListenbrainzScrobbler.ts b/src/backend/scrobblers/ListenbrainzScrobbler.ts index d323e996..c6355421 100644 --- a/src/backend/scrobblers/ListenbrainzScrobbler.ts +++ b/src/backend/scrobblers/ListenbrainzScrobbler.ts @@ -3,7 +3,7 @@ import dayjs from 'dayjs'; import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; import { FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; import { Notifiers } from "../notifier/Notifiers.js"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; import { ListenbrainzApiClient, ListenPayload } from "../common/vendor/ListenbrainzApiClient.js"; import { PlayObject, TrackStringOptions } from "../../core/Atomic.js"; @@ -23,7 +23,7 @@ export default class ListenbrainzScrobbler extends AbstractScrobbleClient { constructor(name: any, config: ListenBrainzClientConfig, options = {}, notifier: Notifiers, emitter: EventEmitter, logger: Logger) { super('listenbrainz', name, config, notifier, emitter, logger); - this.api = new ListenbrainzApiClient(name, config.data); + this.api = new ListenbrainzApiClient(name, config.data, {logger: this.logger}); } formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => ListenbrainzApiClient.formatPlayObj(obj, options); diff --git a/src/backend/scrobblers/MalojaScrobbler.ts b/src/backend/scrobblers/MalojaScrobbler.ts index c0ead014..9cb27e6e 100644 --- a/src/backend/scrobblers/MalojaScrobbler.ts +++ b/src/backend/scrobblers/MalojaScrobbler.ts @@ -6,7 +6,7 @@ import { sleep, parseRetryAfterSecsFromObj } from "../utils.js"; import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, INITIALIZING } from "../common/infrastructure/Atomic.js"; import { MalojaClientConfig } from "../common/infrastructure/config/client/maloja.js"; import { Notifiers } from "../notifier/Notifiers.js"; -import {Logger} from '@foxxmd/winston'; +import {Logger} from "@foxxmd/logging"; import { getMalojaResponseError, isMalojaAPIErrorBody, diff --git a/src/backend/scrobblers/ScrobbleClients.ts b/src/backend/scrobblers/ScrobbleClients.ts index b597d984..42280b2c 100644 --- a/src/backend/scrobblers/ScrobbleClients.ts +++ b/src/backend/scrobblers/ScrobbleClients.ts @@ -1,11 +1,7 @@ /* eslint-disable no-case-declarations */ import dayjs, {Dayjs} from "dayjs"; import { - createAjvFactory, - mergeArr, - playObjDataMatch, readJson, - returnDuplicateStrings, validateJson, } from "../utils.js"; import MalojaScrobbler from "./MalojaScrobbler.js"; @@ -19,13 +15,10 @@ import { MalojaClientConfig } from "../common/infrastructure/config/client/maloj import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; import { Notifiers } from "../notifier/Notifiers.js"; import AbstractScrobbleClient from "./AbstractScrobbleClient.js"; -import {EventEmitter} from "events"; -import winston, {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from '@foxxmd/logging'; import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js"; import { ListenBrainzClientConfig } from "../common/infrastructure/config/client/listenbrainz.js"; -import {ErrorWithCause} from "pony-cause"; import { PlayObject } from "../../core/Atomic.js"; -import { buildTrackString } from "../../core/StringUtils.js"; import { WildcardEmitter } from "../common/WildcardEmitter.js"; type groupedNamedConfigs = {[key: string]: ParsedConfig[]}; @@ -44,12 +37,12 @@ export default class ScrobbleClients { sourceEmitter: WildcardEmitter; - constructor(emitter: WildcardEmitter, sourceEmitter: WildcardEmitter, localUrl: string, configDir: string) { + constructor(emitter: WildcardEmitter, sourceEmitter: WildcardEmitter, localUrl: string, configDir: string, parentLogger: Logger) { this.emitter = emitter; this.sourceEmitter = sourceEmitter; this.configDir = configDir; this.localUrl = localUrl; - this.logger = winston.loggers.get('app').child({labels: ['Scrobblers']}, mergeArr); + this.logger = childLogger(parentLogger, 'Scrobblers'); // winston.loggers.get('app').child({labels: ['Scrobblers']}, mergeArr); this.sourceEmitter.on('discoveredToScrobble', async (payload: { data: (PlayObject | PlayObject[]), options: { forceRefresh?: boolean, checkTime?: Dayjs, scrobbleTo?: string[], scrobbleFrom?: string } }) => { await this.scrobble(payload.data, payload.options); diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 27e64dfe..97fda5c2 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -4,19 +4,13 @@ import { makeClientCheckMiddle, makeSourceCheckMiddle } from "./middleware.js"; import AbstractSource from "../sources/AbstractSource.js"; import { ClientStatusData, - DeadLetterScrobble, - LogInfo, - LogInfoJson, - LogLevel, + DeadLetterScrobble, LeveledLogData, LogOutputConfig, PlayObject, SOURCE_SOT, SourceStatusData, } from "../../core/Atomic.js"; -import {Logger} from "@foxxmd/winston"; -import { formatLogToHtml, getLogger, isLogLevelMinLevel, isLogLineMinLevel } from "../common/logging.js"; -import {MESSAGE} from "triple-beam"; +import {LogDataPretty, Logger, LogLevel} from "@foxxmd/logging"; import {Transform} from "stream"; -//import {createSession} from "better-sse"; import * as bsse from 'better-sse'; import bsseDef from 'better-sse'; import { setupTautulliRoutes } from "./tautulliRoutes.js"; @@ -24,47 +18,42 @@ import { setupPlexRoutes } from "./plexRoutes.js"; import { setupJellyfinRoutes } from "./jellyfinRoutes.js"; import { setupDeezerRoutes } from "./deezerRoutes.js"; import { setupAuthRoutes } from "./auth.js"; -import { ExpressHandler, ScrobbledPlayObject } from "../common/infrastructure/Atomic.js"; +import {ExpressHandler} from "../common/infrastructure/Atomic.js"; import MemorySource from "../sources/MemorySource.js"; import { capitalize } from "../../core/StringUtils.js"; -import {source} from "common-tags"; import AbstractScrobbleClient from "../scrobblers/AbstractScrobbleClient.js"; import { sortByNewestPlayDate } from "../utils.js"; import bodyParser from "body-parser"; import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js"; import {FixedSizeList} from 'fixed-size-list'; +import {PassThrough} from "node:stream"; const maxBufferSize = 300; -const output: { - [key in LogLevel]: FixedSizeList -} = { - 'debug': new FixedSizeList(maxBufferSize), - 'verbose': new FixedSizeList(maxBufferSize), - 'info': new FixedSizeList(maxBufferSize), - 'warn': new FixedSizeList(maxBufferSize), - 'error': new FixedSizeList(maxBufferSize), -} +const output: Record> = {}; -const addToLogBuffer = (log: LogInfo) => { - output[log.level as LogLevel].add(log); +const createAddToLogBuffer = (levelMap: {[p: number]: string}) => (log: LogDataPretty) => { + output[log.level].add({...log, levelLabel: levelMap[log.level]}); } -const getLogs = (minLevel: LogLevel, limit: number = maxBufferSize, sort: 'asc' | 'desc' = 'desc'): LogInfo[] => { - const allLogs: LogInfo[][] = []; +const getLogs = (minLevel: number, limit: number = maxBufferSize, sort: 'asc' | 'desc' = 'desc'): LeveledLogData[] => { + const allLogs: LeveledLogData[][] = []; for(const level of Object.keys(output)) { - if(isLogLevelMinLevel(level as LogLevel, minLevel)) { + if(Number.parseInt(level) >= minLevel) { allLogs.push(output[level].data); } } if(sort === 'desc') { - return allLogs.flat(1).sort((a, b) => b.id - a.id).slice(0, limit); + return allLogs.flat(1).sort((a, b) => b.time - a.time).slice(0, limit); } - return allLogs.flat(1).sort((a, b) => a.id - b.id).slice(0, limit); + return allLogs.flat(1).sort((a, b) => a.time - b.time).slice(0, limit); } -const availableLevels: LogLevel[] = ['error', 'warn', 'info', 'verbose', 'debug']; +export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream: PassThrough, initialLogOutput: LogDataPretty[] = []) => { + for(const level of Object.keys(logger.levels.labels)) { + output[level] = new FixedSizeList(maxBufferSize); + } -export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput: LogInfo[] = []) => { + const addToLogBuffer = createAddToLogBuffer(logger.levels.labels); for(const log of initialLogOutput) { addToLogBuffer(log); } @@ -91,11 +80,10 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput console.log(e); } - const appLogger = getLogger({}, 'app'); - appLogger.stream().on('log', (log: LogInfo) => { + appLoggerStream.on('data', (log: LogDataPretty) => { addToLogBuffer(log); - if(isLogLineMinLevel(log, logConfig.level)) { - logObjectStream.write({message: log[MESSAGE], level: log.level}); + if(log.level >= logger.levels.values[logConfig.level]) { + logObjectStream.write({message: log.line, level: log.level, levelLabel: logger.levels.labels[log.level]}); } }); @@ -128,21 +116,19 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput }); app.get('/api/logs', setLogWebSettings, async (req, res) => { - const slicedLog = getLogs(logConfig.level, logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); - const jsonLogs: LogInfoJson[] = slicedLog.map(x => ({...x, formattedMessage: x[MESSAGE]})); - return res.json({data: jsonLogs, settings: logConfig}); + const slicedLog = getLogs(logger.levels.values[logConfig.level], logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); + return res.json({data: slicedLog, settings: logConfig}); }); app.put('/api/logs', async (req, res) => { logConfig.level = req.body.level as LogLevel | undefined ?? logConfig.level; logConfig.limit = req.body.limit ?? logConfig.limit; - const slicedLog = getLogs(logConfig.level, logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); + const slicedLog = getLogs(logger.levels.values[logConfig.level], logConfig.limit + 1, logConfig.sort === 'ascending' ? 'asc' : 'desc'); // @ts-expect-error logLevel not part of session req.session.logLevel = logConfig.level; // @ts-expect-error limit not part of session req.session.limit = logConfig.limit; - const jsonLogs: LogInfoJson[] = slicedLog.map(x => ({...x, formattedMessage: x[MESSAGE]})); - return res.json({data: jsonLogs, settings: logConfig}); + return res.json({data: slicedLog, settings: logConfig}); }); app.get('/api/events', async (req, res) => { diff --git a/src/backend/server/auth.ts b/src/backend/server/auth.ts index 724cb99b..ecca9f87 100644 --- a/src/backend/server/auth.ts +++ b/src/backend/server/auth.ts @@ -1,5 +1,5 @@ import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import passport from "passport"; import { ExpressHandler } from "../common/infrastructure/Atomic.js"; diff --git a/src/backend/server/deezerRoutes.ts b/src/backend/server/deezerRoutes.ts index d4b4ae81..5c960363 100644 --- a/src/backend/server/deezerRoutes.ts +++ b/src/backend/server/deezerRoutes.ts @@ -1,7 +1,7 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; import { mergeArr, parseBool, sleep } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; diff --git a/src/backend/server/index.ts b/src/backend/server/index.ts index 47a56742..eb6330a5 100644 --- a/src/backend/server/index.ts +++ b/src/backend/server/index.ts @@ -4,23 +4,21 @@ import ViteExpress from "vite-express"; import bodyParser from 'body-parser'; import passport from 'passport'; import session from 'express-session'; -import path from "path"; import { getRoot } from "../ioc.js"; -import {Logger} from "@foxxmd/winston"; -import { LogInfo } from "../../core/Atomic.js"; import { setupApi } from "./api.js"; import { getAddress, mergeArr, parseBool } from "../utils.js"; import {stripIndents} from "common-tags"; import {ErrorWithCause} from "pony-cause"; - -//const buildDir = path.join(process.cwd() + "/build"); +import {childLogger, LogData, LogDataPretty} from "@foxxmd/logging"; +import {PassThrough} from "node:stream"; +import {Logger} from '@foxxmd/logging'; const app = addAsync(express()); const router = Router(); -export const initServer = async (parentLogger: Logger, initialOutput: LogInfo[] = []) => { +export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = []) => { - const logger = parentLogger.child({labels: ['API']}, mergeArr); + const logger = childLogger(parentLogger, 'API'); // parentLogger.child({labels: ['API']}, mergeArr); try { app.use(router); @@ -44,7 +42,7 @@ export const initServer = async (parentLogger: Logger, initialOutput: LogInfo[] const local = root.get('localUrl'); const localDefined = root.get('hasDefinedBaseUrl'); - setupApi(app, logger, initialOutput); + setupApi(app, logger, appLoggerStream, initialOutput); const addy = getAddress(); const addresses: string[] = []; diff --git a/src/backend/server/jellyfinRoutes.ts b/src/backend/server/jellyfinRoutes.ts index 8ae3b81a..b356f950 100644 --- a/src/backend/server/jellyfinRoutes.ts +++ b/src/backend/server/jellyfinRoutes.ts @@ -1,6 +1,6 @@ import { parseBool, remoteHostIdentifiers } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import bodyParser from "body-parser"; import { JellyfinNotifier } from "../sources/ingressNotifiers/JellyfinNotifier.js"; @@ -17,7 +17,7 @@ export const setupJellyfinRoutes = (app: ExpressWithAsync, logger: Logger, scrob // req.rawBody = buf.toString(); // } }); - const jellyIngress = new JellyfinNotifier(); + const jellyIngress = new JellyfinNotifier(logger); app.postAsync('/jellyfin', async (req, res) => { res.redirect(307, '/api/jellyfin/ingress'); }); diff --git a/src/backend/server/middleware.ts b/src/backend/server/middleware.ts index 876b8ddf..8e0b58df 100644 --- a/src/backend/server/middleware.ts +++ b/src/backend/server/middleware.ts @@ -1,5 +1,5 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; export const makeSourceCheckMiddle = (sources: any) => (required: boolean ): ExpressHandler => (req: any, res: any, next: any) => { const { diff --git a/src/backend/server/plexRoutes.ts b/src/backend/server/plexRoutes.ts index 0ef26f99..c1341c84 100644 --- a/src/backend/server/plexRoutes.ts +++ b/src/backend/server/plexRoutes.ts @@ -1,16 +1,16 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; import { mergeArr, parseBool } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import PlexSource, { plexRequestMiddle } from "../sources/PlexSource.js"; import { PlexNotifier } from "../sources/ingressNotifiers/PlexNotifier.js"; export const setupPlexRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { - const plexMiddle = plexRequestMiddle(); - const plexLog = logger.child({labels: ['Plex Request']}, mergeArr); - const plexIngress = new PlexNotifier(); + const plexMiddle = plexRequestMiddle(logger); + const plexLog = childLogger(logger, 'Plex Request'); + const plexIngress = new PlexNotifier(logger); const plexIngressMiddle: ExpressHandler = async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening // (in the event body parsing does not work or request is not POST/PATCH) diff --git a/src/backend/server/tautulliRoutes.ts b/src/backend/server/tautulliRoutes.ts index 01d32e06..cac0ba10 100644 --- a/src/backend/server/tautulliRoutes.ts +++ b/src/backend/server/tautulliRoutes.ts @@ -3,12 +3,12 @@ import { ExpressHandler } from "../common/infrastructure/Atomic.js"; import TautulliSource from "../sources/TautulliSource.js"; import { parseBool } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; export const setupTautulliRoutes = (app: ExpressWithAsync, logger: Logger, scrobbleSources: ScrobbleSources) => { - const tauIngress = new TautulliNotifier(); + const tauIngress = new TautulliNotifier(logger); const tautulliIngressRoute: ExpressHandler = async function(this: any, req, res) { tauIngress.trackIngress(req, false); diff --git a/src/backend/server/webscrobblerRoutes.ts b/src/backend/server/webscrobblerRoutes.ts index 174a81c2..18b4956c 100644 --- a/src/backend/server/webscrobblerRoutes.ts +++ b/src/backend/server/webscrobblerRoutes.ts @@ -1,6 +1,6 @@ import { mergeArr, parseBool, remoteHostIdentifiers } from "../utils.js"; import {ExpressWithAsync} from "@awaitjs/express"; -import {Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from "@foxxmd/logging"; import ScrobbleSources from "../sources/ScrobbleSources.js"; import bodyParser from "body-parser"; import { WebScrobblerPayload } from "../common/vendor/webscrobbler/interfaces.js"; @@ -11,7 +11,7 @@ import path from "path"; export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Logger, scrobbleSources: ScrobbleSources) => { - const logger = parentLogger.child({labels: ['Ingress', 'WebScrobbler']}, mergeArr); + const logger = childLogger(parentLogger, ['Ingress', 'WebScrobbler']); const webScrobblerJsonParser = bodyParser.json({ type: ['text/*', 'application/json'], @@ -21,7 +21,7 @@ export const setupWebscrobblerRoutes = (app: ExpressWithAsync, parentLogger: Log // req.rawBody = buf.toString(); // } }); - const webhookIngress = new WebhookNotifier(); + const webhookIngress = new WebhookNotifier(logger); app.postAsync('/api/webscrobbler*', async (req, res, next) => { // track request before parsing body to ensure we at least log that something is happening diff --git a/src/backend/sources/AbstractSource.ts b/src/backend/sources/AbstractSource.ts index 032f40ee..0c9b837c 100644 --- a/src/backend/sources/AbstractSource.ts +++ b/src/backend/sources/AbstractSource.ts @@ -28,7 +28,7 @@ import { SINGLE_USER_PLATFORM_ID, SourceType, } from "../common/infrastructure/Atomic.js"; -import {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from '@foxxmd/logging'; import { SourceConfig } from "../common/infrastructure/config/source/sources.js"; import {EventEmitter} from "events"; import {FixedSizeList} from "fixed-size-list"; @@ -91,7 +91,7 @@ export default abstract class AbstractSource implements Authenticatable { this.type = type; this.name = name; this.identifier = `Source - ${capitalize(this.type)} - ${name}`; - this.logger = internal.logger.child({labels: [`${capitalize(this.type)} - ${name}`]}, mergeArr); + this.logger = childLogger(internal.logger, `${capitalize(this.type)} - ${name}`); this.config = config; this.clients = clients; this.instantiatedAt = dayjs(); diff --git a/src/backend/sources/ChromecastSource.ts b/src/backend/sources/ChromecastSource.ts index 620c25e6..c5cef8c6 100644 --- a/src/backend/sources/ChromecastSource.ts +++ b/src/backend/sources/ChromecastSource.ts @@ -24,13 +24,14 @@ import { getMediaStatus, genPlayHash, } from "../common/vendor/chromecast/ChromecastClientUtils.js"; -import {config, Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import {ContextualValidationError} from "@foxxmd/chromecast-client/dist/cjs/src/utils.js"; import { buildTrackString } from "../../core/StringUtils.js"; import { discoveryAvahi, discoveryNative } from "../utils/MDNSUtils.js"; import {MaybeLogger} from "../common/logging.js"; import e, {application} from "express"; import {NETWORK_ERROR_FAILURE_CODES} from "../common/errors/NodeErrors.js"; +import {childLogger} from "@foxxmd/logging"; interface ChromecastDeviceInfo { mdns: MdnsDeviceInfo @@ -236,7 +237,7 @@ export class ChromecastSource extends MemorySource { retries: 0, platform, applications, - logger: this.logger.child({labels: [device.name.substring(0, 25)]}, mergeArr), + logger: childLogger(this.logger, device.name.substring(0, 25)), }); } catch (e) { this.logger.error(e); @@ -392,7 +393,7 @@ export class ChromecastSource extends MemorySource { badData: false, validAppType: valid, playerId: genGroupIdStr([genDeviceId(k, a.displayName), NO_USER]), - logger: v.logger.child({labels: [`App ${a.displayName.substring(0, 25)}-${a.transportId.substring(0,4)}`]}, mergeArr) + logger: childLogger(v.logger, `App ${a.displayName.substring(0, 25)}-${a.transportId.substring(0,4)}`) } v.applications.set(a.transportId, storedApp); } else if(storedApp.stale === true) { diff --git a/src/backend/sources/JRiverSource.ts b/src/backend/sources/JRiverSource.ts index 39ea66cf..a0d720dd 100644 --- a/src/backend/sources/JRiverSource.ts +++ b/src/backend/sources/JRiverSource.ts @@ -32,7 +32,7 @@ export class JRiverSource extends MemorySource { } = {}, } = config; this.url = JRiverSource.parseConnectionUrl(url); - this.client = new JRiverApiClient(name, {...data, url: this.url.toString()}); + this.client = new JRiverApiClient(name, {...data, url: this.url.toString()}, {logger: this.logger}); this.requiresAuth = true; this.canPoll = true; this.multiPlatform = true; diff --git a/src/backend/sources/JellyfinSource.ts b/src/backend/sources/JellyfinSource.ts index 6b4c4562..840dead8 100644 --- a/src/backend/sources/JellyfinSource.ts +++ b/src/backend/sources/JellyfinSource.ts @@ -11,7 +11,7 @@ import { JellySourceConfig } from "../common/infrastructure/config/source/jellyf import { FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; import { PlayerStateOptions } from "./PlayerState/AbstractPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import { JellyfinPlayerState } from "./PlayerState/JellyfinPlayerState.js"; import { PlayObject, TA_CLOSE } from "../../core/Atomic.js"; import { buildTrackString, splitByFirstFound, truncateStringToLength } from "../../core/StringUtils.js"; diff --git a/src/backend/sources/KodiSource.ts b/src/backend/sources/KodiSource.ts index b63441ab..b6329f95 100644 --- a/src/backend/sources/KodiSource.ts +++ b/src/backend/sources/KodiSource.ts @@ -32,7 +32,7 @@ export class KodiSource extends MemorySource { url } = {} } = this.config; - this.client = new KodiApiClient(this.name, this.config.data); + this.client = new KodiApiClient(this.name, this.config.data, {logger: this.logger}); this.logger.debug(`Config URL: '${url ?? '(None Given)'}' => Normalized: '${this.client.url.toString()}'`) return true; } diff --git a/src/backend/sources/LastfmSource.ts b/src/backend/sources/LastfmSource.ts index d5e8daaa..81a0865a 100644 --- a/src/backend/sources/LastfmSource.ts +++ b/src/backend/sources/LastfmSource.ts @@ -33,7 +33,7 @@ export default class LastfmSource extends MemorySource { this.canBacklog = true; this.supportsUpstreamRecentlyPlayed = true; this.supportsUpstreamNowPlaying = true; - this.api = new LastfmApiClient(name, {...config.data, configDir: internal.configDir, localUrl: internal.localUrl}); + this.api = new LastfmApiClient(name, {...config.data, configDir: internal.configDir, localUrl: internal.localUrl}, {logger: this.logger}); this.playerSourceOfTruth = SOURCE_SOT.HISTORY; this.logger.info(`Note: The player for this source is an analogue for the 'Now Playing' status exposed by ${this.type} which is NOT used for scrobbling. Instead, the 'recently played' or 'history' information provided by this source is used for scrobbles.`) } diff --git a/src/backend/sources/ListenbrainzSource.ts b/src/backend/sources/ListenbrainzSource.ts index 94dc6528..9566c156 100644 --- a/src/backend/sources/ListenbrainzSource.ts +++ b/src/backend/sources/ListenbrainzSource.ts @@ -28,7 +28,7 @@ export default class ListenbrainzSource extends MemorySource { super('listenbrainz', name, {...config, data: {interval, maxInterval, ...restData}}, internal, emitter); this.canPoll = true; this.canBacklog = true; - this.api = new ListenbrainzApiClient(name, config.data); + this.api = new ListenbrainzApiClient(name, config.data, {logger: this.logger}); this.playerSourceOfTruth = SOURCE_SOT.HISTORY; this.supportsUpstreamRecentlyPlayed = true; this.logger.info(`Note: The player for this source is an analogue for the 'Now Playing' status exposed by ${this.type} which is NOT used for scrobbling. Instead, the 'recently played' or 'history' information provided by this source is used for scrobbles.`) diff --git a/src/backend/sources/MemorySource.ts b/src/backend/sources/MemorySource.ts index 1dc2a252..9c75dfbd 100644 --- a/src/backend/sources/MemorySource.ts +++ b/src/backend/sources/MemorySource.ts @@ -27,7 +27,7 @@ import { import TupleMap from "../common/TupleMap.js"; import {AbstractPlayerState, createPlayerOptions, PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js"; import { GenericPlayerState } from "./PlayerState/GenericPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import {PlayObject, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj} from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; import {SimpleIntervalJob, Task, ToadScheduler} from "toad-scheduler"; diff --git a/src/backend/sources/MopidySource.ts b/src/backend/sources/MopidySource.ts index 486f01e6..932e4d48 100644 --- a/src/backend/sources/MopidySource.ts +++ b/src/backend/sources/MopidySource.ts @@ -12,11 +12,11 @@ import {URL} from "url"; import normalizeUrl from 'normalize-url'; import {EventEmitter} from "events"; import pEvent from 'p-event'; -import winston from '@foxxmd/winston'; import { RecentlyPlayedOptions } from "./AbstractSource.js"; import { PlayObject } from "../../core/Atomic.js"; import { buildTrackString } from "../../core/StringUtils.js"; import {ErrorWithCause} from "pony-cause"; +import {loggerTest} from "@foxxmd/logging"; export class MopidySource extends MemorySource { declare config: MopidySourceConfig; @@ -58,7 +58,7 @@ export class MopidySource extends MemorySource { autoConnect: false, webSocketUrl: this.url.toString(), // @ts-expect-error logger satisfies but is missing types not used - console: winston.loggers.get('noop') + console: loggerTest }); this.client.on('state:offline', () => { this.logger.verbose('Lost connection to server'); diff --git a/src/backend/sources/PlayerState/AbstractPlayerState.ts b/src/backend/sources/PlayerState/AbstractPlayerState.ts index 3fb936ed..5db90442 100644 --- a/src/backend/sources/PlayerState/AbstractPlayerState.ts +++ b/src/backend/sources/PlayerState/AbstractPlayerState.ts @@ -7,7 +7,7 @@ import { } from "../../common/infrastructure/Atomic.js"; import dayjs, {Dayjs} from "dayjs"; import { formatNumber, genGroupIdStr, playObjDataMatch, progressBar } from "../../utils.js"; -import {Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from "@foxxmd/logging"; import { ListenProgress } from "./ListenProgress.js"; import {PlayObject, Second, SOURCE_SOT, SOURCE_SOT_TYPES, SourcePlayerObj} from "../../../core/Atomic.js"; import { buildTrackString } from "../../../core/StringUtils.js"; @@ -60,7 +60,7 @@ export abstract class AbstractPlayerState { protected constructor(logger: Logger, platformId: PlayPlatformId, opts: PlayerStateOptions = DefaultPlayerStateOptions) { this.platformId = platformId; - this.logger = logger.child({labels: [`Player ${this.platformIdStr}`]}); + this.logger = childLogger(logger, `Player ${this.platformIdStr}`); const { staleInterval = 120, diff --git a/src/backend/sources/PlayerState/GenericPlayerState.ts b/src/backend/sources/PlayerState/GenericPlayerState.ts index caaa5229..3977de6a 100644 --- a/src/backend/sources/PlayerState/GenericPlayerState.ts +++ b/src/backend/sources/PlayerState/GenericPlayerState.ts @@ -1,5 +1,5 @@ import { AbstractPlayerState, PlayerStateOptions } from "./AbstractPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import { PlayPlatformId } from "../../common/infrastructure/Atomic.js"; export class GenericPlayerState extends AbstractPlayerState { diff --git a/src/backend/sources/PlayerState/JellyfinPlayerState.ts b/src/backend/sources/PlayerState/JellyfinPlayerState.ts index 93d28bfb..b7019cde 100644 --- a/src/backend/sources/PlayerState/JellyfinPlayerState.ts +++ b/src/backend/sources/PlayerState/JellyfinPlayerState.ts @@ -1,5 +1,5 @@ import { GenericPlayerState } from "./GenericPlayerState.js"; -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import { PlayPlatformId, ReportedPlayerStatus } from "../../common/infrastructure/Atomic.js"; import { PlayerStateOptions } from "./AbstractPlayerState.js"; import { PlayObject } from "../../../core/Atomic.js"; diff --git a/src/backend/sources/PlexSource.ts b/src/backend/sources/PlexSource.ts index 458788db..a5697305 100644 --- a/src/backend/sources/PlexSource.ts +++ b/src/backend/sources/PlexSource.ts @@ -1,14 +1,14 @@ import dayjs from "dayjs"; -import { combinePartsToString, mergeArr } from "../utils.js"; +import { combinePartsToString } from "../utils.js"; import AbstractSource from "./AbstractSource.js"; import formidable from 'formidable'; import concatStream from 'concat-stream'; import { PlexSourceConfig } from "../common/infrastructure/config/source/plex.js"; import { FormatPlayObjectOptions, InternalConfig, SourceType } from "../common/infrastructure/Atomic.js"; import EventEmitter from "events"; -import winston from '@foxxmd/winston'; import { PlayObject } from "../../core/Atomic.js"; import { truncateStringToLength } from "../../core/StringUtils.js"; +import {childLogger, Logger} from "@foxxmd/logging"; const shortDeviceId = truncateStringToLength(10, ''); @@ -231,9 +231,9 @@ export default class PlexSource extends AbstractSource { } } -export const plexRequestMiddle = () => { +export const plexRequestMiddle = (logger: Logger) => { - const plexLog = winston.loggers.get('app').child({labels: ['Plex Request']}, mergeArr); + const plexLog = childLogger(logger, 'Plex Request'); return async (req: any, res: any, next: any) => { diff --git a/src/backend/sources/ScrobbleSources.ts b/src/backend/sources/ScrobbleSources.ts index 84075278..13ed28ae 100644 --- a/src/backend/sources/ScrobbleSources.ts +++ b/src/backend/sources/ScrobbleSources.ts @@ -9,7 +9,7 @@ import LastfmSource from "./LastfmSource.js"; import DeezerSource from "./DeezerSource.js"; import { ConfigMeta, InternalConfig, SourceType, sourceTypes } from "../common/infrastructure/Atomic.js"; import { configDir as defaultConfigDir } from "../common/index.js"; -import winston, {Logger} from '@foxxmd/winston'; +import {childLogger, Logger} from '@foxxmd/logging'; import { SourceAIOConfig, SourceConfig } from "../common/infrastructure/config/source/sources.js"; import { DeezerData, DeezerSourceConfig } from "../common/infrastructure/config/source/deezer.js"; import { LastfmClientConfig } from "../common/infrastructure/config/client/lastfm.js"; @@ -55,11 +55,11 @@ export default class ScrobbleSources { emitter: WildcardEmitter; - constructor(emitter: EventEmitter, localUrl: string, configDir: string = defaultConfigDir) { + constructor(emitter: EventEmitter, localUrl: string, configDir: string = defaultConfigDir, parentLogger: Logger) { this.emitter = emitter; this.configDir = configDir; this.localUrl = localUrl; - this.logger = winston.loggers.get('app').child({labels: ['Sources']}, mergeArr); + this.logger = childLogger(parentLogger, 'Sources'); // winston.loggers.get('app').child({labels: ['Sources']}, mergeArr); } getByName = (name: any) => this.sources.find(x => x.name === name) diff --git a/src/backend/sources/ingressNotifiers/IngressNotifier.ts b/src/backend/sources/ingressNotifiers/IngressNotifier.ts index 3c7ce51a..e5b6df72 100644 --- a/src/backend/sources/ingressNotifiers/IngressNotifier.ts +++ b/src/backend/sources/ingressNotifiers/IngressNotifier.ts @@ -1,5 +1,5 @@ -import winston, {Logger} from '@foxxmd/winston'; -import { mergeArr, remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; +import {childLogger, Logger} from '@foxxmd/logging'; +import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; import {Request} from "express"; import { RemoteIdentityParts } from "../../common/infrastructure/Atomic.js"; @@ -11,9 +11,9 @@ export abstract class IngressNotifier { remotes: Record = {}; - protected constructor(name: string) { + protected constructor(name: string, logger: Logger) { this.identifier = name; - this.logger = winston.loggers.get('app').child({labels: ['Ingress', name]}, mergeArr); + this.logger = childLogger(logger, ['Ingress', name]); } public trackIngress(req: Request, isRaw: boolean) { diff --git a/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts b/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts index 8ded82f4..bc91ad69 100644 --- a/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts +++ b/src/backend/sources/ingressNotifiers/JellyfinNotifier.ts @@ -2,11 +2,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import JellyfinSource from "../JellyfinSource.js"; import { remoteHostIdentifiers, remoteHostStr } from "../../utils.js"; +import {Logger} from "@foxxmd/logging"; export class JellyfinNotifier extends IngressNotifier { - constructor() { - super('Jellyfin'); + constructor(logger: Logger) { + super('Jellyfin', logger); } seenServers: Record = {}; diff --git a/src/backend/sources/ingressNotifiers/PlexNotifier.ts b/src/backend/sources/ingressNotifiers/PlexNotifier.ts index b9910b3a..079e9c10 100644 --- a/src/backend/sources/ingressNotifiers/PlexNotifier.ts +++ b/src/backend/sources/ingressNotifiers/PlexNotifier.ts @@ -1,11 +1,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import PlexSource from "../PlexSource.js"; +import {Logger} from "@foxxmd/logging"; export class PlexNotifier extends IngressNotifier { - constructor() { - super('Plex'); + constructor(logger: Logger) { + super('Plex', logger); } seenServers: string[] = []; diff --git a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts index ef12b2de..5db110a2 100644 --- a/src/backend/sources/ingressNotifiers/TautulliNotifier.ts +++ b/src/backend/sources/ingressNotifiers/TautulliNotifier.ts @@ -2,11 +2,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import PlexSource from "../PlexSource.js"; import TautulliSource from "../TautulliSource.js"; +import {Logger} from "@foxxmd/logging"; export class TautulliNotifier extends IngressNotifier { - constructor() { - super('Tautulli'); + constructor(logger: Logger) { + super('Tautulli', logger); } seenServers: string[] = []; diff --git a/src/backend/sources/ingressNotifiers/WebhookNotifier.ts b/src/backend/sources/ingressNotifiers/WebhookNotifier.ts index d11dcde8..d5b22625 100644 --- a/src/backend/sources/ingressNotifiers/WebhookNotifier.ts +++ b/src/backend/sources/ingressNotifiers/WebhookNotifier.ts @@ -1,11 +1,12 @@ import { IngressNotifier } from "./IngressNotifier.js"; import {Request} from "express"; import path from "path"; +import {Logger} from "@foxxmd/logging"; export class WebhookNotifier extends IngressNotifier { - constructor() { - super('WebScrobbler'); + constructor(logger: Logger) { + super('WebScrobbler', logger); } seenSlugs: Record = {}; diff --git a/src/backend/tasks/heartbeatClients.ts b/src/backend/tasks/heartbeatClients.ts index fbe9a7ac..472fcd21 100644 --- a/src/backend/tasks/heartbeatClients.ts +++ b/src/backend/tasks/heartbeatClients.ts @@ -1,11 +1,11 @@ -import {config, Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from '@foxxmd/logging'; import { mergeArr } from "../utils.js"; import {AsyncTask} from "toad-scheduler"; import {PromisePool} from "@supercharge/promise-pool"; import ScrobbleClients from "../scrobblers/ScrobbleClients.js"; export const createHeartbeatClientsTask = (clients: ScrobbleClients, parentLogger: Logger) => { - const logger = parentLogger.child({labels: ['Heartbeat', 'Clients']}, mergeArr); + const logger = childLogger(parentLogger, ['Heartbeat', 'Clients']); return new AsyncTask( 'Heartbeat', diff --git a/src/backend/tasks/heartbeatSources.ts b/src/backend/tasks/heartbeatSources.ts index 8c624986..6d6efb9d 100644 --- a/src/backend/tasks/heartbeatSources.ts +++ b/src/backend/tasks/heartbeatSources.ts @@ -1,4 +1,4 @@ -import {config, Logger} from "@foxxmd/winston"; +import {childLogger, Logger} from '@foxxmd/logging'; import { mergeArr } from "../utils.js"; import {AsyncTask} from "toad-scheduler"; import {PromisePool} from "@supercharge/promise-pool"; @@ -6,7 +6,7 @@ import ScrobbleSources from "../sources/ScrobbleSources.js"; import { ChromecastSource } from "../sources/ChromecastSource.js"; export const createHeartbeatSourcesTask = (sources: ScrobbleSources, parentLogger: Logger) => { - const logger = parentLogger.child({labels: ['Heartbeat', 'Sources']}, mergeArr); + const logger = childLogger(parentLogger, ['Heartbeat', 'Sources']); return new AsyncTask( 'Heartbeat', diff --git a/src/backend/tests/jellyfin/jellyfin.test.ts b/src/backend/tests/jellyfin/jellyfin.test.ts index c704a28d..07770787 100644 --- a/src/backend/tests/jellyfin/jellyfin.test.ts +++ b/src/backend/tests/jellyfin/jellyfin.test.ts @@ -4,8 +4,8 @@ import samplePayload from './playbackProgressSample.json'; import JellyfinSource from "../../sources/JellyfinSource.js"; import EventEmitter from "events"; -import { getLogger } from "../../common/logging.js"; import { JsonPlayObject, PlayObject } from "../../../core/Atomic.js"; +import {loggerTest} from "@foxxmd/logging"; const dataAsFixture = (data: any): TestFixture => { return data as TestFixture; @@ -28,7 +28,7 @@ describe('Jellyfin Payload Parsing', function () { describe('Correctly detects events as valid/invalid', function () { - const jfSource = new JellyfinSource('Test', {data: {}}, {localUrl: 'test', configDir: 'test', logger: getLogger({}, 'noop')}, new EventEmitter()); + const jfSource = new JellyfinSource('Test', {data: {}}, {localUrl: 'test', configDir: 'test', logger: loggerTest}, new EventEmitter()); it('Should parse PlayProgress with Audio ItemType as valid event', async function () { const fixture = dataAsFixture(samplePayload[0]); diff --git a/src/backend/tests/listenbrainz/listenbrainz.test.ts b/src/backend/tests/listenbrainz/listenbrainz.test.ts index 1e0d4ded..8e35fe28 100644 --- a/src/backend/tests/listenbrainz/listenbrainz.test.ts +++ b/src/backend/tests/listenbrainz/listenbrainz.test.ts @@ -20,6 +20,7 @@ import { withRequestInterception } from "../utils/networking.js"; import {http, HttpResponse} from "msw"; import { UpstreamError } from "../../common/errors/UpstreamError.js"; import { ExpectedResults } from "../utils/interfaces.js"; +import {loggerTest} from "@foxxmd/logging"; interface LZTestFixture { data: ListenResponse @@ -112,7 +113,7 @@ describe('Listenbrainz Response Behavior', function() { { token: 'test', username: 'test' - }); + }, {logger: loggerTest}); it('Should recognize bad requests as non-showstopping',withRequestInterception( [ diff --git a/src/backend/tests/player/player.test.ts b/src/backend/tests/player/player.test.ts index c00cdb70..178e3203 100644 --- a/src/backend/tests/player/player.test.ts +++ b/src/backend/tests/player/player.test.ts @@ -2,13 +2,13 @@ import {describe, it} from 'mocha'; import {assert} from 'chai'; import { generatePlay } from "../utils/PlayTestUtils.js"; import { GenericPlayerState } from "../../sources/PlayerState/GenericPlayerState.js"; -import { getLogger } from "../../common/logging.js"; import { CALCULATED_PLAYER_STATUSES, NO_DEVICE, NO_USER, REPORTED_PLAYER_STATUSES } from "../../common/infrastructure/Atomic.js"; import { playObjDataMatch } from "../../utils.js"; import dayjs from "dayjs"; import clone from "clone"; +import {loggerTest} from "@foxxmd/logging"; -const logger = getLogger({}, 'noop'); +const logger = loggerTest; const newPlay = generatePlay({duration: 300}); diff --git a/src/backend/tests/scrobbler/TestScrobbler.ts b/src/backend/tests/scrobbler/TestScrobbler.ts index 355dae61..15787e98 100644 --- a/src/backend/tests/scrobbler/TestScrobbler.ts +++ b/src/backend/tests/scrobbler/TestScrobbler.ts @@ -1,15 +1,15 @@ import AbstractScrobbleClient from "../../scrobblers/AbstractScrobbleClient.js"; import { PlayObject } from "../../../core/Atomic.js"; -import { getLogger } from "../../common/logging.js"; import { Notifiers } from "../../notifier/Notifiers.js"; import EventEmitter from "events"; import request from "superagent"; +import {loggerTest} from "@foxxmd/logging"; export class TestScrobbler extends AbstractScrobbleClient { constructor() { - const logger = getLogger({}, 'noop'); - const notifier = new Notifiers(new EventEmitter(), new EventEmitter(), new EventEmitter()); + const logger = loggerTest; + const notifier = new Notifiers(new EventEmitter(), new EventEmitter(), new EventEmitter(), logger); super('test', 'Test', {name: 'test'}, notifier, new EventEmitter(), logger); } diff --git a/src/backend/utils.ts b/src/backend/utils.ts index a713408e..724298c4 100644 --- a/src/backend/utils.ts +++ b/src/backend/utils.ts @@ -1,7 +1,7 @@ import {accessSync, constants, promises} from "fs"; import dayjs, {Dayjs} from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; -import {Logger} from '@foxxmd/winston'; +import {Logger} from '@foxxmd/logging'; import JSON5 from 'json5'; import {TimeoutError, WebapiError} from "spotify-web-api-node/src/response-error.js"; import {Schema} from 'ajv'; diff --git a/src/backend/utils/MDNSUtils.ts b/src/backend/utils/MDNSUtils.ts index 4f05e73a..6d25c332 100644 --- a/src/backend/utils/MDNSUtils.ts +++ b/src/backend/utils/MDNSUtils.ts @@ -1,4 +1,4 @@ -import {Logger} from "@foxxmd/winston"; +import {Logger} from "@foxxmd/logging"; import AvahiBrowser from 'avahi-browse'; import { MaybeLogger } from "../common/logging.js"; import { sleep } from "../utils.js"; diff --git a/src/client/logs/LogLine.tsx b/src/client/logs/LogLine.tsx index e2dc232f..8c2360bc 100644 --- a/src/client/logs/LogLine.tsx +++ b/src/client/logs/LogLine.tsx @@ -1,17 +1,6 @@ import React, {PropsWithChildren} from 'react'; import {parseLogLine} from "../utils/index"; -const breakSymbol = '
'; - -const replaceLevel = (chunk: string) => { - return chunk.toString().replace('\n', breakSymbol) - .replace(/(debug)\s/gi, '$1 ') - .replace(/(warn)\s/gi, '$1 ') - .replace(/(info)\s/gi, '$1 ') - .replace(/(verbose)\s/gi, '$1 ') - .replace(/(error)\s/gi, '$1 ') - .trim(); -} const getClass = (level: string) => { switch(level) { case 'debug': @@ -26,12 +15,11 @@ const getClass = (level: string) => { return 'error red'; } } -const LogLine = (props: PropsWithChildren<{level: string, message: string}>) => { +const LogLine = (props: PropsWithChildren<{level: number, levelLabel: string, message: string}>) => { const lineParts = parseLogLine(props.message); - const level = props.level ?? lineParts.level; - const levelClass = getClass(level); + const levelClass = getClass(props.levelLabel); return ( -
{lineParts.timestamp} {level.padEnd(7, ' ')} : {lineParts.message}
+
{lineParts.timestamp} {props.levelLabel.padEnd(7, ' ')} : {lineParts.message}
) }; diff --git a/src/client/logs/LogsSection.tsx b/src/client/logs/LogsSection.tsx index 10972f4c..5c02f7dc 100644 --- a/src/client/logs/LogsSection.tsx +++ b/src/client/logs/LogsSection.tsx @@ -3,17 +3,18 @@ import './LogsSection.css'; import {FixedSizeList} from "fixed-size-list"; import {useEventSource, useEventSourceListener} from "@react-nano/use-event-source"; import LogLine from "./LogLine"; -import {useGetLogsQuery, useLazySetLogSettingsQuery, logsApi} from "./logsApi"; +import {useGetLogsQuery, useLazySetLogSettingsQuery} from "./logsApi"; import {connect, ConnectedProps} from "react-redux"; import {RootState} from "../store"; import Loading from "../components/loading/Loading"; -let logBuffer: { message: string, id: string, level: string }[] = []; +const logBuffer: { message: string, id: string, level: number, levelLabel:string }[] = []; interface MinLogInfo { message: string, id: string, - level: string + level: number + levelLabel: string } const createFixedList = (size, initialList: MinLogInfo[] = []): FixedSizeList => { @@ -55,7 +56,7 @@ const LogsSection = (props: PropsFromRedux) => { useGetLogsQuery(undefined); useEffect(() => { - list = createFixedList(settings.limit, logs.map((x, index) => ({...x, message: x.formattedMessage, id: index.toString()}))); + list = createFixedList(settings.limit, logs.map((x, index) => ({...x, message: x.line, id: index.toString()}))); setLogList(Array.from(list.data)); setLogLevel(settings.level); }, [logs, settings, setLogList, setLogLevel]); @@ -73,7 +74,7 @@ const LogsSection = (props: PropsFromRedux) => { useEventSourceListener(eventSource, ['messsage', 'stream'], evt => { const data = JSON.parse(evt.data); // @ts-ignore - list.add({message: data.message, id: evt.lastEventId, level: data.level}); + list.add({message: data.message, id: evt.lastEventId, level: data.level, levelLabel: data.levelLabel}); setLogList(Array.from(list.data)); //console.log(evt); }, [setLogList]); @@ -101,7 +102,7 @@ const LogsSection = (props: PropsFromRedux) => {
{ - logList.map(x => ) + logList.map(x => ) }
diff --git a/src/client/logs/logDucks.ts b/src/client/logs/logDucks.ts index fa53f7c0..1d40f9a4 100644 --- a/src/client/logs/logDucks.ts +++ b/src/client/logs/logDucks.ts @@ -3,9 +3,10 @@ import { createSlice } from '@reduxjs/toolkit' import {logsApi} from "./logsApi"; -import {LogInfoJson, LogOutputConfig} from "../../core/Atomic"; +import {LogOutputConfig} from "../../core/Atomic"; +import {LogDataPretty} from "@foxxmd/logging"; export interface LogsState { - data: LogInfoJson[], + data: (LogDataPretty & {levelLabel: string})[], settings: LogOutputConfig } const initialState: LogsState = {data: [], settings: {level: 'debug', sort: 'asc', limit: 50}}; diff --git a/src/client/logs/logsApi.ts b/src/client/logs/logsApi.ts index 7efeaa94..b191049c 100644 --- a/src/client/logs/logsApi.ts +++ b/src/client/logs/logsApi.ts @@ -1,14 +1,14 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; -import {LogInfoJson, LogOutputConfig} from "../../core/Atomic"; +import {LeveledLogData, LogOutputConfig} from "../../core/Atomic"; export const logsApi = createApi({ reducerPath: 'logsApi', baseQuery: fetchBaseQuery({ baseUrl: './api/' }), endpoints: (builder) => ({ - getLogs: builder.query<{ data: LogInfoJson[], settings: LogOutputConfig }, undefined>({ + getLogs: builder.query<{ data: LeveledLogData[], settings: LogOutputConfig }, undefined>({ query: () => `logs`, }), - setLogSettings: builder.query<{ data: LogInfoJson[], settings: LogOutputConfig }, object>({ + setLogSettings: builder.query<{ data: LeveledLogData[], settings: LogOutputConfig }, object>({ query: (settings) => ({ url: '/logs', method: 'PUT', diff --git a/src/client/utils/index.tsx b/src/client/utils/index.tsx index 8f872ca3..06473dee 100644 --- a/src/client/utils/index.tsx +++ b/src/client/utils/index.tsx @@ -22,7 +22,7 @@ export const buildTrackStringReactOptions: TrackStringOptions = { } } -const LOG_LINE_REGEX = new RegExp(/(?\S+)\s+(?\w+)\s*:\s*(?(?:.|\n)*)/, 'm'); +const LOG_LINE_REGEX = new RegExp(/\[(?.+)]\s+(?\w+)\s*:\s*(?(?:.|\n)*)/m, 'm'); export const parseLogLine = (line: string) => { const match = line.match(LOG_LINE_REGEX); if (match === null) { diff --git a/src/core/Atomic.ts b/src/core/Atomic.ts index a9e724d6..45d3c649 100644 --- a/src/core/Atomic.ts +++ b/src/core/Atomic.ts @@ -1,6 +1,6 @@ import {Dayjs} from "dayjs"; -import {MESSAGE} from "triple-beam"; import {ListenProgress} from "../backend/sources/PlayerState/ListenProgress.js"; +import {LogDataPretty, LogLevel} from "@foxxmd/logging"; export interface SourceStatusData { status: string; @@ -158,29 +158,12 @@ export interface JsonPlayData extends PlayData { playDateCompleted?: Dayjs } -export type LogLevel = "error" | "warn" | "info" | "verbose" | "debug"; -export const logLevels = ['error', 'warn', 'info', 'verbose', 'debug']; - -export interface LogInfo { - id: number - message: string - [MESSAGE]: string, - level: string - timestamp: string - labels?: string[] - transport?: string[] -} - export interface LogOutputConfig { level: LogLevel, sort: string, limit: number } -export interface LogInfoJson extends LogInfo { - formattedMessage: string -} - export interface SourcePlayerObj { platformId: string, play: PlayObject, @@ -243,3 +226,7 @@ export const SOURCE_SOT = { PLAYER : 'player' as SOURCE_SOT_TYPES, HISTORY: 'history' as SOURCE_SOT_TYPES } + +export interface LeveledLogData extends LogDataPretty { + levelLabel: string +}