From f1b46b788d8deaed2e44dd67af8f123cc2f520b3 Mon Sep 17 00:00:00 2001 From: Alexandre Tuleu Date: Tue, 26 Sep 2023 13:32:46 +0200 Subject: [PATCH] Adds structured logging and fixes event reactivity. --- package-lock.json | 398 +++++++++++++++++++++++- package.json | 4 +- src/lib/server/config.ts | 12 +- src/lib/server/dataPolling.ts | 46 ++- src/lib/server/logger.ts | 8 + src/lib/services/pihole/+content.svelte | 2 +- src/lib/services/pihole/+service.ts | 29 +- src/lib/services/services.ts | 9 +- src/lib/services/sonarr/+service.ts | 6 +- src/routes/+page.server.ts | 9 +- src/routes/+page.svelte | 19 +- src/routes/api/updates/+server.ts | 20 +- 12 files changed, 505 insertions(+), 57 deletions(-) create mode 100644 src/lib/server/logger.ts diff --git a/package-lock.json b/package-lock.json index 23bc8f2..621ee66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "dotenv": "^16.3.1", "humanize-duration-ts": "^2.1.1", "ip-range-check": "^0.2.0", - "js-yaml": "^4.1.0" + "js-yaml": "^4.1.0", + "pino": "^8.15.1" }, "devDependencies": { "@modyfi/vite-plugin-yaml": "^1.0.4", @@ -29,6 +30,7 @@ "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.30.0", + "pino-pretty": "^10.2.0", "postcss": "^8.4.30", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", @@ -1321,6 +1323,17 @@ "url": "https://opencollective.com/vitest" } }, + "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/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1448,6 +1461,14 @@ "node": "*" } }, + "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.15", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", @@ -1500,6 +1521,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1563,6 +1603,29 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -1756,6 +1819,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -1825,6 +1894,15 @@ "node": ">=4" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1955,6 +2033,15 @@ "integrity": "sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==", "dev": true }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", @@ -2261,6 +2348,28 @@ "node": ">=0.10.0" } }, + "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/fast-copy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", + "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==", + "dev": true + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2307,6 +2416,20 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "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", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -2529,11 +2652,94 @@ "node": ">=8" } }, + "node_modules/help-me": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", + "dev": true, + "dependencies": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/help-me/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/help-me/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/help-me/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/help-me/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/humanize-duration-ts": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/humanize-duration-ts/-/humanize-duration-ts-2.1.1.tgz", "integrity": "sha512-TibNF2/fkypjAfHdGpWL/dmWUS0G6Qi+3mKyiB6LDCowbMy+PtzbgPTnFMNTOVAJXDau01jYrJ3tFoz5AJSqhA==" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2726,6 +2932,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3080,6 +3295,11 @@ "node": ">= 6" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3243,6 +3463,66 @@ "node": ">=0.10.0" } }, + "node_modules/pino": { + "version": "8.15.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.15.1.tgz", + "integrity": "sha512-Cp4QzUQrvWCRJaQ8Lzv0mJzXVk4z2jlq8JNKMGaixC2Pz5L4l2p95TkuRvYbrEbe85NQsDKrAd4zalf7Ml6WiA==", + "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": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.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-pretty": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.2.0.tgz", + "integrity": "sha512-tRvpyEmGtc2D+Lr3FulIZ+R1baggQ4S3xD2Ar93KixFEDx6SEAUP3W5aYuEw1C73d6ROrNcB2IXLteW8itlwhA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "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-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/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -3578,6 +3858,29 @@ "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-warning": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", + "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -3607,6 +3910,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/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -3622,6 +3930,21 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3634,6 +3957,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/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -3736,6 +4067,33 @@ "node": ">=6" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "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/sander": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", @@ -3779,6 +4137,12 @@ "node": ">=14.0.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==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -3850,6 +4214,14 @@ "node": ">=8" } }, + "node_modules/sonic-boom": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/sorcery": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", @@ -3874,6 +4246,14 @@ "node": ">=0.10.0" } }, + "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/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -3895,6 +4275,14 @@ "node": ">=10.0.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4295,6 +4683,14 @@ "node": ">=0.8" } }, + "node_modules/thread-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.0.tgz", + "integrity": "sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tinybench": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.0.tgz", diff --git a/package.json b/package.json index a1b852a..4398539 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "eslint": "^8.28.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-svelte": "^2.30.0", + "pino-pretty": "^10.2.0", "postcss": "^8.4.30", "prettier": "^2.8.0", "prettier-plugin-svelte": "^2.10.1", @@ -47,6 +48,7 @@ "dotenv": "^16.3.1", "humanize-duration-ts": "^2.1.1", "ip-range-check": "^0.2.0", - "js-yaml": "^4.1.0" + "js-yaml": "^4.1.0", + "pino": "^8.15.1" } } diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts index 76a6ad1..903d88f 100644 --- a/src/lib/server/config.ts +++ b/src/lib/server/config.ts @@ -13,6 +13,7 @@ import type { import * as ipRangeCheck from 'ip-range-check'; import { initializeServiceData } from './serviceDataPolling'; import { getServiceRecord } from '$lib/services/services'; +import { rootLogger } from './logger'; const requiredService: Required = { title: '', @@ -94,7 +95,6 @@ function injectDefaultServiceConfigs(config: Config) { continue; } const def = getServiceRecord(service.type).config; - console.warn(service, def); for (const [key, value] of Object.entries(def)) { const isUnknownKey = !(key in requiredService); const keyAlreadyDefined = key in service && service[key] != false; @@ -198,6 +198,8 @@ export function initConfig() { buildServerAndClientConfig(configData); } +const logger = rootLogger.child({ name: 'config' }); + export function watchDymamicConfig() { const __filepath = '/dynamic/config.yml'; @@ -205,8 +207,8 @@ export function watchDymamicConfig() { try { const dynamicConfig = yml.load(await readFile(__filepath, 'utf8')); buildServerAndClientConfig(dynamicConfig as SPOJO); - } catch (err) { - console.error('could not read or parse config: ' + err); + } catch (error) { + logger.error({ error }, 'could not read or parse config'); } }; @@ -219,8 +221,8 @@ export function watchDymamicConfig() { for await (const event of watcher) { reloadConfig(); } - } catch (err) { - console.error('could not watch config: ' + err); + } catch (error) { + logger.error({ error }, 'could not watch config'); return; } })(); diff --git a/src/lib/server/dataPolling.ts b/src/lib/server/dataPolling.ts index 8847be6..54b0271 100644 --- a/src/lib/server/dataPolling.ts +++ b/src/lib/server/dataPolling.ts @@ -2,14 +2,18 @@ import type { ServiceData, ServicePoller } from '$lib/services/service'; import { getServiceRecord } from '$lib/services/services'; import { readable } from 'svelte/store'; import { serverConfig } from './config'; +import { rootLogger } from './logger'; -export type MayAsyncServiceData = ServiceData | Promise; +export type MayAsyncServiceData = Partial | Promise>; -export function getCurrentServiceData(): MayAsyncServiceData[][] { +const logger = rootLogger.child({ name: 'ServiceData' }); + +export function getCurrentServiceData(): Array> { + logger.trace('getCurrentServiceData()'); if (serviceData.length != 0) { return serviceData; } - + logger.debug('polling for initial state'); return pollAllServices(); } @@ -20,11 +24,16 @@ export interface ServiceDataEvent { } export const serviceDataEvents = readable(undefined, (set) => { - console.debug('starting ServiceData polling loop every ' + pollInterval / 1000 + 's'); + logger.debug({ interval: pollInterval }, 'starting polling loop'); + pushServiceData = set; - const timer = setInterval(pollAllServices, pollInterval); + + const timer = setInterval(() => { + logger.debug('polling for service update'); + pollAllServices(); + }, pollInterval); return () => { - console.debug('stopping ServiceData polling loop'); + logger.debug('stopping polling loop and clearing data'); clearInterval(timer); serviceData = []; pushServiceData = () => {}; @@ -33,11 +42,12 @@ export const serviceDataEvents = readable(undefined, (set) => let pushServiceData: (value: ServiceDataEvent) => void = () => {}; -const pollInterval = 30000; +const pollInterval = 5000; -let serviceData: MayAsyncServiceData[][]; +let serviceData: Array> = []; -function pollAllServices(): MayAsyncServiceData[][] { +function pollAllServices(): Array> { + logger.trace('pollAllServices()'); const config = serverConfig(); for (const [idxGroup, group] of config.services.entries()) { @@ -47,12 +57,18 @@ function pollAllServices(): MayAsyncServiceData[][] { for (const [idxItem, service] of group.items.entries()) { const poller: ServicePoller = getServiceRecord(service.type || '').poll; - - serviceData[idxGroup][idxItem] = poller(service).then((data: ServiceData) => { - serviceData[idxGroup][idxItem] = data; - pushServiceData({ group: idxGroup, item: idxItem, data }); - return data; - }); + const serviceLogger = logger.child({ group: idxGroup, item: idxItem, service }); + serviceData[idxGroup][idxItem] = poller(service) + .then((data: ServiceData) => { + serviceLogger.trace({ data }, 'pollAllService:result()'); + serviceData[idxGroup][idxItem] = data; + pushServiceData({ group: idxGroup, item: idxItem, data }); + return data; + }) + .catch((error) => { + serviceLogger.warn({ error }, 'could not poll service'); + return { status: 'offline' }; + }); } } diff --git a/src/lib/server/logger.ts b/src/lib/server/logger.ts new file mode 100644 index 0000000..6df6a4a --- /dev/null +++ b/src/lib/server/logger.ts @@ -0,0 +1,8 @@ +import { dev } from '$app/environment'; +import pino from 'pino'; + +export const rootLogger = pino({ + level: dev ? 'debug' : 'info' +}); + +export const requestLogger = rootLogger.child({ name: 'request' }); diff --git a/src/lib/services/pihole/+content.svelte b/src/lib/services/pihole/+content.svelte index d2da6d2..178d8e3 100644 --- a/src/lib/services/pihole/+content.svelte +++ b/src/lib/services/pihole/+content.svelte @@ -33,6 +33,6 @@ - {data.data?.client || 'N.A.'} + {data.data?.clients || 'N.A.'} diff --git a/src/lib/services/pihole/+service.ts b/src/lib/services/pihole/+service.ts index 052df49..a7bc879 100644 --- a/src/lib/services/pihole/+service.ts +++ b/src/lib/services/pihole/+service.ts @@ -8,22 +8,15 @@ export const config: Partial = { }; export const poll: ServicePoller = async (config: ServiceConfig) => { - try { - const resp: Response = await fetch( - config.url + '/api.php?summaryRaw&auth=' + config.api_token - ); - const raw = await resp.json(); - return { - status: resp.ok ? 'online' : 'offline', - data: { - queries_today: raw.dns_queries_today, - ads_percentage: raw.ads_percentage_today, - status: raw.status, - client: raw.unique_clients - } - }; - } catch (error) { - console.warn('could not fetch pihole status: ' + error); - return { status: 'offline' }; - } + const resp: Response = await fetch(config.url + '/api.php?summaryRaw&auth=' + config.api_token); + const raw = await resp.json(); + return { + status: resp.ok ? 'online' : 'offline', + data: { + queries_today: raw.dns_queries_today, + ads_percentage: raw.ads_percentage_today, + status: raw.status, + clients: raw.unique_clients + } + }; }; diff --git a/src/lib/services/services.ts b/src/lib/services/services.ts index 0708b68..2e55276 100644 --- a/src/lib/services/services.ts +++ b/src/lib/services/services.ts @@ -12,13 +12,8 @@ const services: Record = {}; const components: Record = {}; async function pollURL(config: ServiceConfig): Promise { - try { - const resp = await fetch(config.url); - return { status: resp.ok ? 'online' : 'offline' }; - } catch (error) { - console.warn(`could not poll '${config.url}': ` + error); - return { status: 'offline' }; - } + const resp = await fetch(config.url); + return { status: resp.ok ? 'online' : 'offline' }; } function registerService(type: string, service: Partial) { diff --git a/src/lib/services/sonarr/+service.ts b/src/lib/services/sonarr/+service.ts index a19fa3e..6a5d272 100644 --- a/src/lib/services/sonarr/+service.ts +++ b/src/lib/services/sonarr/+service.ts @@ -74,15 +74,13 @@ export const poll: ServicePoller = async (config: ServiceConfig) => { const data: Record = {}; if (health.status != 'fulfilled') { - console.warn("Could not fetch '" + config.url + "' status: " + health.reason); - status = 'offline'; + throw new Error("could not fetch '" + config.url + "' health status: " + health.reason); } else if (health.value.ok == true) { data.health = buildStatus(await health.value.json()); } if (queue.status != 'fulfilled') { - console.warn("Could not fetch '" + config.url + "' queue: " + queue.reason); - data.status = 'offline'; + throw new Error("could not fetch '" + config.url + "' queue status: " + queue.reason); } else if (queue.value.ok == true) { data.queue = buildQueue(await queue.value.json()); } diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index e80cc64..5d98ede 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,11 +1,18 @@ import { clientAddressIsPrivate, clientConfig, serverConfig } from '$lib/server/config'; import type { PageServerLoad } from './$types.d'; import { serviceData } from '$lib/server/serviceDataPolling'; +import { getCurrentServiceData } from '$lib/server/dataPolling'; +import { request } from '@playwright/test'; +import { requestLogger } from '$lib/server/logger'; export const load: PageServerLoad = async ({ getClientAddress }) => { + const logger = requestLogger.child({ url: 'GET /', address: getClientAddress() }); + + logger.info('got request'); + return { config: clientConfig(), - serviceData: serviceData, + serviceData: getCurrentServiceData(), location: getClientAddress(), privateAccess: clientAddressIsPrivate(getClientAddress(), serverConfig()) }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 10167ef..8462cb0 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,10 +2,27 @@ import Header from '$lib/components/Header.svelte'; import ServiceGroup from '$lib/components/ServiceGroup.svelte'; import { layoutDirection, type LayoutDirection } from '$lib/stores/layout'; + import { onMount } from 'svelte'; import type { PageData } from './$types'; + import type { ServiceDataEvent } from '$lib/server/dataPolling'; export let data: PageData; + onMount(() => { + const sse = new EventSource('/api/updates'); + sse.onmessage = (e) => { + if (e.data == undefined || e.data === 'undefined') { + return; + } + const event = JSON.parse(e.data) as ServiceDataEvent; + data.serviceData[event.group][event.item] = event.data; + }; + + return () => { + sse.close(); + }; + }); + function renderClasses(columns: number, direction: LayoutDirection): string { if (direction === 'row') { switch (columns) { @@ -37,7 +54,7 @@
-
+
{#each data.config.services as group, i} {/each} diff --git a/src/routes/api/updates/+server.ts b/src/routes/api/updates/+server.ts index 66c9079..efdcf44 100644 --- a/src/routes/api/updates/+server.ts +++ b/src/routes/api/updates/+server.ts @@ -1,24 +1,38 @@ import { serviceDataEvents } from '$lib/server/dataPolling'; +import { requestLogger, rootLogger } from '$lib/server/logger'; import type { RequestHandler } from '@sveltejs/kit'; import type { Unsubscriber } from 'svelte/store'; -export const GET: RequestHandler = () => { +let id = 0; + +export const GET: RequestHandler = ({ getClientAddress }) => { + id++; + const logger = requestLogger.child({ + url: 'GET /api/updates', + address: getClientAddress(), + id: id + }); let unsubscribe: Unsubscriber | undefined = undefined; const stream = new ReadableStream({ start: (controller) => { - console.debug('ServiceDataEvent stream started'); + logger.info('new update request stream started'); + unsubscribe = serviceDataEvents.subscribe((event) => { + logger.trace({ event: event }, 'sending new event'); const data = `event:message\ndata:${JSON.stringify(event)}\n\n`; controller.enqueue(data); }); }, cancel: (reason) => { - console.debug('ServiceDataEvent stream canceled: ', reason); + logger.info({ reason }, 'stopping request stream'); if (unsubscribe != undefined) { unsubscribe(); } } }); + + logger.info('got request'); + return new Response(stream, { headers: { 'Content-Type': 'text/event-stream'