From 6c68613d0281234c2c828beadbc4b8bcce16a648 Mon Sep 17 00:00:00 2001 From: Hendrik Belitz Date: Mon, 23 Jun 2025 18:18:10 +0200 Subject: [PATCH] 44 Universal Logger for front- and backend. --- package-lock.json | 282 +++++++++++++++++++++++++++++++++- package.json | 147 +++++++++--------- src/hooks.server.ts | 39 +++++ src/lib/logger/index.ts | 112 ++++++++++++++ src/lib/logger/winston.ts | 32 ++++ src/routes/api/log/+server.ts | 36 +++++ 6 files changed, 571 insertions(+), 77 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/logger/index.ts create mode 100644 src/lib/logger/winston.ts create mode 100644 src/routes/api/log/+server.ts diff --git a/package-lock.json b/package-lock.json index d6c163c..b6af5c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,11 @@ "license": "AGPL-3.0", "dependencies": { "@sveltejs/adapter-node": "^5.2.12", - "@types/dotenv": "^6.1.1", "dotenv": "^16.5.0", "dotenv-expand": "^12.0.2", "drizzle-orm": "^0.40.0", - "postgres": "^3.4.5" + "postgres": "^3.4.5", + "winston": "^3.17.0" }, "devDependencies": { "@eslint/compat": "^1.2.5", @@ -27,6 +27,7 @@ "@tailwindcss/vite": "^4.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/svelte": "^5.2.4", + "@types/dotenv": "^6.1.1", "@types/node": "^22", "drizzle-kit": "^0.30.2", "eslint": "^9.18.0", @@ -118,6 +119,15 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", @@ -233,6 +243,17 @@ "node": ">=18" } }, + "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==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -2383,6 +2404,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", "integrity": "sha512-ftQl3DtBvqHl9L16tpqqzA4YzCSXZfi7g8cQceTz5rOlYtk/IZbFjAv3mLOQlNIgOaylCQWQoBdDQHPgEBJPHg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -2405,6 +2427,7 @@ "version": "22.15.32", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz", "integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2416,6 +2439,12 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.34.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", @@ -2889,6 +2918,12 @@ "node": ">=12" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -3032,6 +3067,16 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "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", @@ -3049,9 +3094,43 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -3385,6 +3464,12 @@ } } }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -3829,6 +3914,12 @@ } } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3893,6 +3984,12 @@ "dev": true, "license": "ISC" }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -4139,6 +4236,18 @@ "node": ">=8" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -4209,6 +4318,18 @@ "@types/estree": "^1.0.6" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4333,6 +4454,12 @@ "dev": true, "license": "MIT" }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4636,6 +4763,23 @@ "dev": true, "license": "MIT" }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/loupe": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", @@ -4824,6 +4968,15 @@ "dev": true, "license": "MIT" }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5324,6 +5477,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -5491,6 +5658,35 @@ "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" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -5573,6 +5769,15 @@ "dev": true, "license": "ISC" }, + "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==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sirv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", @@ -5617,6 +5822,15 @@ "source-map": "^0.6.0" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5631,6 +5845,15 @@ "dev": true, "license": "MIT" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -5836,6 +6059,12 @@ "node": ">=18" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5964,6 +6193,15 @@ "node": ">=18" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6031,6 +6269,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, "license": "MIT" }, "node_modules/uri-js": { @@ -6047,7 +6286,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, "license": "MIT" }, "node_modules/vite": { @@ -6753,6 +6991,42 @@ "node": ">=8" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 7b2d5be..cb7f562 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,77 @@ { - "name": "open-reception", - "private": true, - "version": "0.0.1", - "type": "module", - "description": "End-to-end encrypted appointment booking platform", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "vite preview", - "prepare": "svelte-kit sync || echo ''", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", - "lint": "prettier --check . && eslint .", - "test:unit": "vitest", - "test": "npm run test:unit -- --run && npm run test:e2e", - "test:e2e": "playwright test", - "db:push": "drizzle-kit push", - "db:migrate": "drizzle-kit migrate", - "db:studio": "drizzle-kit studio", - "docker:dev:up": "docker compose -f docker-compose.dev.yml up -d", - "docker:dev:down": "docker compose -f docker-compose.dev.yml down", - "docker:dev:logs": "docker compose -f docker-compose.dev.yml logs -f", - "docker:dev:clean": "docker compose -f docker-compose.dev.yml down -v --remove-orphans", - "docker:build": "docker build -t open-reception:latest .", - "docker:build:tag": "docker tag open-reception:latest open-reception:$npm_package_version", - "docker:push": "docker push open-reception:$npm_package_version && docker push open-reception:latest", - "docker:build-and-push": "npm run docker:build && npm run docker:build:tag && npm run docker:push", - "docker:prod:up": "docker compose -f docker-compose.prod.yml up -d", - "docker:prod:down": "docker compose -f docker-compose.prod.yml down", - "docker:prod:logs": "docker compose -f docker-compose.prod.yml logs -f", - "docker:prod:clean": "docker compose -f docker-compose.prod.yml down -v --remove-orphans" - }, - "devDependencies": { - "@eslint/compat": "^1.2.5", - "@eslint/js": "^9.18.0", - "@playwright/test": "^1.49.1", - "@sveltejs/adapter-auto": "^6.0.0", - "@sveltejs/kit": "^2.16.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "@tailwindcss/typography": "^0.5.15", - "@tailwindcss/vite": "^4.0.0", - "@testing-library/jest-dom": "^6.6.3", - "@testing-library/svelte": "^5.2.4", - "@types/node": "^22", + "name": "open-reception", + "private": true, + "version": "0.0.1", + "type": "module", + "description": "End-to-end encrypted appointment booking platform", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "format": "prettier --write .", + "lint": "prettier --check . && eslint .", + "test:unit": "vitest", + "test": "npm run test:unit -- --run && npm run test:e2e", + "test:e2e": "playwright test", + "db:push": "drizzle-kit push", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio", + "docker:dev:up": "docker compose -f docker-compose.dev.yml up -d", + "docker:dev:down": "docker compose -f docker-compose.dev.yml down", + "docker:dev:logs": "docker compose -f docker-compose.dev.yml logs -f", + "docker:dev:clean": "docker compose -f docker-compose.dev.yml down -v --remove-orphans", + "docker:build": "docker build -t open-reception:latest .", + "docker:build:tag": "docker tag open-reception:latest open-reception:$npm_package_version", + "docker:push": "docker push open-reception:$npm_package_version && docker push open-reception:latest", + "docker:build-and-push": "npm run docker:build && npm run docker:build:tag && npm run docker:push", + "docker:prod:up": "docker compose -f docker-compose.prod.yml up -d", + "docker:prod:down": "docker compose -f docker-compose.prod.yml down", + "docker:prod:logs": "docker compose -f docker-compose.prod.yml logs -f", + "docker:prod:clean": "docker compose -f docker-compose.prod.yml down -v --remove-orphans" + }, + "devDependencies": { + "@eslint/compat": "^1.2.5", + "@eslint/js": "^9.18.0", + "@playwright/test": "^1.49.1", + "@sveltejs/adapter-auto": "^6.0.0", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "^4.0.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/svelte": "^5.2.4", "@types/dotenv": "^6.1.1", - "drizzle-kit": "^0.30.2", - "eslint": "^9.18.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-svelte": "^3.0.0", - "globals": "^16.0.0", - "jsdom": "^26.0.0", - "prettier": "^3.4.2", - "prettier-plugin-svelte": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.11", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "tailwindcss": "^4.0.0", - "typescript": "^5.0.0", - "typescript-eslint": "^8.20.0", - "vite": "^6.2.6", - "vitest": "^3.2.3" - }, - "dependencies": { - "@sveltejs/adapter-node": "^5.2.12", - "dotenv": "^16.5.0", - "dotenv-expand": "^12.0.2", - "drizzle-orm": "^0.40.0", - "postgres": "^3.4.5" - }, - "engines": { - "node": ">=24.0.0" - }, - "license": "AGPL-3.0" + "@types/node": "^22", + "drizzle-kit": "^0.30.2", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-svelte": "^3.0.0", + "globals": "^16.0.0", + "jsdom": "^26.0.0", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.0.0", + "typescript-eslint": "^8.20.0", + "vite": "^6.2.6", + "vitest": "^3.2.3" + }, + "dependencies": { + "@sveltejs/adapter-node": "^5.2.12", + "dotenv": "^16.5.0", + "dotenv-expand": "^12.0.2", + "drizzle-orm": "^0.40.0", + "postgres": "^3.4.5", + "winston": "^3.17.0" + }, + "engines": { + "node": ">=24.0.0" + }, + "license": "AGPL-3.0" } diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..9a8a191 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,39 @@ +import { logger } from "$lib/logger"; + +export async function handle({ event, resolve }) { + const start = Date.now(); + const requestLogger = logger.setContext("REQUEST"); + + requestLogger.info(`Incoming ${event.request.method} ${event.url.pathname}`); + + const response = await resolve(event); + const responseTime = Date.now() - start; + + requestLogger.logRequest(event.request, responseTime, response.status); + + return response; +} + +type Error = { + message?: string; + stack?: string; +}; + +export async function handleError({ error, event, status, message }) { + const errorLogger = logger.setContext("ERROR_HANDLER"); + + errorLogger.error("Unhandled error occurred", { + error: (error as Error).message, + stack: (error as Error).stack, + status, + message, + url: event.url.pathname, + method: event.request.method, + userAgent: event.request.headers.get("user-agent"), + ip: event.getClientAddress() + }); + + return { + message: "Internal server error occurred" + }; +} diff --git a/src/lib/logger/index.ts b/src/lib/logger/index.ts new file mode 100644 index 0000000..199c11b --- /dev/null +++ b/src/lib/logger/index.ts @@ -0,0 +1,112 @@ +import { browser } from "$app/environment"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let winstonLogger: any = undefined; +if (!browser) { + const { default: winston } = await import("./winston"); + winstonLogger = winston; +} + +class UniversalLogger { + #context: string = ""; + + setContext(context: string) { + this.#context = context; + return this; + } + + #formatMessage(message: string, meta = {}) { + const contextPrefix = this.#context ? `[${this.#context}] ` : ""; + return { + message: `${contextPrefix}${message}`, + ...meta, + source: browser ? "client" : "server", + userAgent: browser ? navigator.userAgent : undefined, + timestamp: new Date().toISOString() + }; + } + + debug(message: string, meta = {}) { + if (browser) { + console.debug(`🐛 ${this.#context ? `[${this.#context}] ` : ""}${message}`, meta); + } else { + winstonLogger.debug(this.#formatMessage(message, meta)); + } + } + + info(message: string, meta = {}) { + if (browser) { + console.info(`â„šī¸ ${this.#context ? `[${this.#context}] ` : ""}${message}`, meta); + } else { + winstonLogger.info(this.#formatMessage(message, meta)); + } + } + + warn(message: string, meta = {}) { + if (browser) { + console.warn(`âš ī¸ ${this.#context ? `[${this.#context}] ` : ""}${message}`, meta); + } else { + winstonLogger.warn(this.#formatMessage(message, meta)); + } + } + + error(message: string, meta = {}) { + if (browser) { + console.error(`❌ ${this.#context ? `[${this.#context}] ` : ""}${message}`, meta); + + this.#sendErrorToServer(message, meta); + } else { + winstonLogger.error(this.#formatMessage(message, meta)); + } + } + + async #sendErrorToServer(message: string, meta = {}) { + try { + await fetch("/api/log", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + level: "error", + message, + meta: { + ...meta, + context: this.#context, + url: window.location.href, + userAgent: navigator.userAgent, + timestamp: new Date().toISOString() + } + }) + }); + } catch (err) { + console.error("Failed to send error to server:", err); + } + } + + logRequest(request: Request, responseTime: number, statusCode: number) { + const message = `${request.method} ${request.url} - ${statusCode} (${responseTime}ms)`; + + if (statusCode >= 400) { + this.error(message, { + method: request.method, + url: request.url, + statusCode, + responseTime + }); + } else { + this.info(message, { + method: request.method, + url: request.url, + statusCode, + responseTime + }); + } + } +} + +export const logger = new UniversalLogger(); + +export const createLogger = (context: string) => { + return new UniversalLogger().setContext(context); +}; + +export default logger; diff --git a/src/lib/logger/winston.ts b/src/lib/logger/winston.ts new file mode 100644 index 0000000..948e206 --- /dev/null +++ b/src/lib/logger/winston.ts @@ -0,0 +1,32 @@ +import winston from "winston"; +import { dev } from "$app/environment"; + +const customFormat = winston.format.combine( + winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + winston.format.errors({ stack: true }), + winston.format.colorize(), + winston.format.printf(({ timestamp, level, message, stack, ...meta }) => { + let log = `${timestamp} [${level}]: ${message}`; + if (stack) { + log += `\n${stack}`; + } + + if (Object.keys(meta).length > 0) { + log += `\n${JSON.stringify(meta, null, 2)}`; + } + + return log; + }) +); + +const logger = winston.createLogger({ + level: dev ? "debug" : "info", + format: customFormat, + transports: [ + new winston.transports.Console({ + format: winston.format.combine(customFormat) + }) + ] +}); + +export default logger; diff --git a/src/routes/api/log/+server.ts b/src/routes/api/log/+server.ts new file mode 100644 index 0000000..aafe0c3 --- /dev/null +++ b/src/routes/api/log/+server.ts @@ -0,0 +1,36 @@ +import { json } from "@sveltejs/kit"; +import { logger } from "$lib/logger"; + +export async function POST({ request }) { + try { + const { level, message, meta } = await request.json(); + + const clientLogger = logger.setContext("CLIENT"); + + switch (level) { + case "debug": + clientLogger.debug(message, meta); + break; + case "info": + clientLogger.info(message, meta); + break; + case "warn": + clientLogger.warn(message, meta); + break; + case "error": + clientLogger.error(message, meta); + break; + default: + clientLogger.info(message, meta); + } + + return json({ success: true }); + } catch (error: unknown) { + if (error instanceof Error) { + logger.error("Failed to process client log", { error: error?.message ?? "Unknown error" }); + } else { + logger.error("Unknown error on processing client error message"); + } + return json({ success: false }, { status: 500 }); + } +}