detached 2960188

feat: Implement structured logging with Pino across server routes

- Add Pino logger with configurable log levels
- Create centralized logging utility with context-aware logging
- Replace console.log/debug/error with structured logging
- Enhance log messages with additional context and metadata
- Configure log levels dynamically based on environment
commit
2960188b03ac2e37d64fa384070a6d5969e95f57
author
Kit · · 2025-02-15 09:05 UTC
parent
afa6c9d
signature
SSH UnverifiedSHA256:H91DFuEG4jpk5edzLA4aUKkpv4zv9Q339ipRmkph6Tk The key is unknown
19 files · +507 -146
M package.json +2 -0
@@ -54,6 +54,8 @@
5454 "@libsql/client": "^0.14.0",
5555 "dotenv": "^16.4.7",
5656 "drizzle-orm": "^0.39.3",
57+ "pino": "^9.6.0",
58+ "pino-pretty": "^13.0.0",
5759 "uuid": "^11.0.5",
5860 "xp.css": "^0.2.6"
5961 }
@@ -54,6 +54,8 @@
54 "@libsql/client": "^0.14.0",54 "@libsql/client": "^0.14.0",
55 "dotenv": "^16.4.7",55 "dotenv": "^16.4.7",
56 "drizzle-orm": "^0.39.3",56 "drizzle-orm": "^0.39.3",
57+ "pino": "^9.6.0",
58+ "pino-pretty": "^13.0.0",
57 "uuid": "^11.0.5",59 "uuid": "^11.0.5",
58 "xp.css": "^0.2.6"60 "xp.css": "^0.2.6"
59 }61 }
M pnpm-lock.yaml +184 -0
@@ -17,6 +17,12 @@ importers:
1717 drizzle-orm:
1818 specifier: ^0.39.3
1919 version: 0.39.3(@cloudflare/workers-types@4.20250204.0)(@libsql/client@0.14.0)
20+ pino:
21+ specifier: ^9.6.0
22+ version: 9.6.0
23+ pino-pretty:
24+ specifier: ^13.0.0
25+ version: 13.0.0
2026 uuid:
2127 specifier: ^11.0.5
2228 version: 11.0.5
@@ -1339,6 +1345,10 @@ packages:
13391345 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
13401346 engines: {node: '>=12'}
13411347
1348+ atomic-sleep@1.0.0:
1349+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
1350+ engines: {node: '>=8.0.0'}
1351+
13421352 autoprefixer@10.4.20:
13431353 resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
13441354 engines: {node: ^10 || ^12 || >=14}
@@ -1424,6 +1434,9 @@ packages:
14241434 color-name@1.1.4:
14251435 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
14261436
1437+ colorette@2.0.20:
1438+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
1439+
14271440 commander@2.20.3:
14281441 resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
14291442
@@ -1461,6 +1474,9 @@ packages:
14611474 resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
14621475 engines: {node: '>= 12'}
14631476
1477+ dateformat@4.6.3:
1478+ resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
1479+
14641480 debug@4.4.0:
14651481 resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
14661482 engines: {node: '>=6.0'}
@@ -1603,6 +1619,9 @@ packages:
16031619 emoji-regex@9.2.2:
16041620 resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
16051621
1622+ end-of-stream@1.4.4:
1623+ resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
1624+
16061625 es-module-lexer@1.6.0:
16071626 resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
16081627
@@ -1736,6 +1755,9 @@ packages:
17361755 resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==}
17371756 engines: {node: '>=12.0.0'}
17381757
1758+ fast-copy@3.0.2:
1759+ resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
1760+
17391761 fast-deep-equal@3.1.3:
17401762 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
17411763
@@ -1749,6 +1771,13 @@ packages:
17491771 fast-levenshtein@2.0.6:
17501772 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
17511773
1774+ fast-redact@3.5.0:
1775+ resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
1776+ engines: {node: '>=6'}
1777+
1778+ fast-safe-stringify@2.1.1:
1779+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
1780+
17521781 fastq@1.19.0:
17531782 resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
17541783
@@ -1842,6 +1871,9 @@ packages:
18421871 resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
18431872 engines: {node: '>= 0.4'}
18441873
1874+ help-me@5.0.0:
1875+ resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
1876+
18451877 ignore@5.3.2:
18461878 resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
18471879 engines: {node: '>= 4'}
@@ -1894,6 +1926,10 @@ packages:
18941926 resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
18951927 hasBin: true
18961928
1929+ joycon@3.1.1:
1930+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
1931+ engines: {node: '>=10'}
1932+
18971933 js-base64@3.7.7:
18981934 resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
18991935
@@ -1996,6 +2032,9 @@ packages:
19962032 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
19972033 engines: {node: '>=16 || 14 >=14.17'}
19982034
2035+ minimist@1.2.8:
2036+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
2037+
19992038 minipass@7.1.2:
20002039 resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
20012040 engines: {node: '>=16 || 14 >=14.17'}
@@ -2059,6 +2098,13 @@ packages:
20592098 ohash@1.1.4:
20602099 resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}
20612100
2101+ on-exit-leak-free@2.1.2:
2102+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
2103+ engines: {node: '>=14.0.0'}
2104+
2105+ once@1.4.0:
2106+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
2107+
20622108 optionator@0.9.4:
20632109 resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
20642110 engines: {node: '>= 0.8.0'}
@@ -2117,6 +2163,20 @@ packages:
21172163 resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
21182164 engines: {node: '>=0.10.0'}
21192165
2166+ pino-abstract-transport@2.0.0:
2167+ resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
2168+
2169+ pino-pretty@13.0.0:
2170+ resolution: {integrity: sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==}
2171+ hasBin: true
2172+
2173+ pino-std-serializers@7.0.0:
2174+ resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
2175+
2176+ pino@9.6.0:
2177+ resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==}
2178+ hasBin: true
2179+
21202180 pirates@4.0.6:
21212181 resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
21222182 engines: {node: '>= 6'}
@@ -2266,9 +2326,15 @@ packages:
22662326 printable-characters@1.0.42:
22672327 resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
22682328
2329+ process-warning@4.0.1:
2330+ resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
2331+
22692332 promise-limit@2.7.0:
22702333 resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}
22712334
2335+ pump@3.0.2:
2336+ resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
2337+
22722338 punycode@2.3.1:
22732339 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
22742340 engines: {node: '>=6'}
@@ -2276,6 +2342,9 @@ packages:
22762342 queue-microtask@1.2.3:
22772343 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
22782344
2345+ quick-format-unescaped@4.0.4:
2346+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
2347+
22792348 read-cache@1.0.0:
22802349 resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
22812350
@@ -2287,6 +2356,10 @@ packages:
22872356 resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==}
22882357 engines: {node: '>= 14.18.0'}
22892358
2359+ real-require@0.2.0:
2360+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
2361+ engines: {node: '>= 12.13.0'}
2362+
22902363 regexparam@3.0.0:
22912364 resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==}
22922365 engines: {node: '>=8'}
@@ -2329,6 +2402,13 @@ packages:
23292402 resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
23302403 engines: {node: '>=6'}
23312404
2405+ safe-stable-stringify@2.5.0:
2406+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
2407+ engines: {node: '>=10'}
2408+
2409+ secure-json-parse@2.7.0:
2410+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
2411+
23322412 semver@7.7.1:
23332413 resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
23342414 engines: {node: '>=10'}
@@ -2356,6 +2436,9 @@ packages:
23562436 resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==}
23572437 engines: {node: '>=18'}
23582438
2439+ sonic-boom@4.2.0:
2440+ resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
2441+
23592442 source-map-js@1.2.1:
23602443 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
23612444 engines: {node: '>=0.10.0'}
@@ -2371,6 +2454,10 @@ packages:
23712454 resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
23722455 deprecated: Please use @jridgewell/sourcemap-codec instead
23732456
2457+ split2@4.2.0:
2458+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
2459+ engines: {node: '>= 10.x'}
2460+
23742461 stackback@0.0.2:
23752462 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
23762463
@@ -2455,6 +2542,9 @@ packages:
24552542 thenify@3.3.1:
24562543 resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
24572544
2545+ thread-stream@3.1.0:
2546+ resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
2547+
24582548 tinybench@2.9.0:
24592549 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
24602550
@@ -2666,6 +2756,9 @@ packages:
26662756 resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
26672757 engines: {node: '>=12'}
26682758
2759+ wrappy@1.0.2:
2760+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
2761+
26692762 ws@8.18.0:
26702763 resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
26712764 engines: {node: '>=10.0.0'}
@@ -3585,6 +3678,8 @@ snapshots:
35853678
35863679 assertion-error@2.0.1: {}
35873680
3681+ atomic-sleep@1.0.0: {}
3682+
35883683 autoprefixer@10.4.20(postcss@8.5.2):
35893684 dependencies:
35903685 browserslist: 4.24.4
@@ -3672,6 +3767,8 @@ snapshots:
36723767
36733768 color-name@1.1.4: {}
36743769
3770+ colorette@2.0.20: {}
3771+
36753772 commander@2.20.3: {}
36763773
36773774 commander@4.1.1: {}
@@ -3696,6 +3793,8 @@ snapshots:
36963793
36973794 data-uri-to-buffer@4.0.1: {}
36983795
3796+ dateformat@4.6.3: {}
3797+
36993798 debug@4.4.0:
37003799 dependencies:
37013800 ms: 2.1.3
@@ -3740,6 +3839,10 @@ snapshots:
37403839
37413840 emoji-regex@9.2.2: {}
37423841
3842+ end-of-stream@1.4.4:
3843+ dependencies:
3844+ once: 1.4.0
3845+
37433846 es-module-lexer@1.6.0: {}
37443847
37453848 esbuild-register@3.6.0(esbuild@0.19.12):
@@ -4007,6 +4110,8 @@ snapshots:
40074110
40084111 expect-type@1.1.0: {}
40094112
4113+ fast-copy@3.0.2: {}
4114+
40104115 fast-deep-equal@3.1.3: {}
40114116
40124117 fast-glob@3.3.3:
@@ -4021,6 +4126,10 @@ snapshots:
40214126
40224127 fast-levenshtein@2.0.6: {}
40234128
4129+ fast-redact@3.5.0: {}
4130+
4131+ fast-safe-stringify@2.1.1: {}
4132+
40244133 fastq@1.19.0:
40254134 dependencies:
40264135 reusify: 1.0.4
@@ -4108,6 +4217,8 @@ snapshots:
41084217 dependencies:
41094218 function-bind: 1.1.2
41104219
4220+ help-me@5.0.0: {}
4221+
41114222 ignore@5.3.2: {}
41124223
41134224 import-fresh@3.3.1:
@@ -4151,6 +4262,8 @@ snapshots:
41514262
41524263 jiti@1.21.7: {}
41534264
4265+ joycon@3.1.1: {}
4266+
41544267 js-base64@3.7.7: {}
41554268
41564269 js-yaml@4.1.0:
@@ -4255,6 +4368,8 @@ snapshots:
42554368 dependencies:
42564369 brace-expansion: 2.0.1
42574370
4371+ minimist@1.2.8: {}
4372+
42584373 minipass@7.1.2: {}
42594374
42604375 mlly@1.7.4:
@@ -4302,6 +4417,12 @@ snapshots:
43024417
43034418 ohash@1.1.4: {}
43044419
4420+ on-exit-leak-free@2.1.2: {}
4421+
4422+ once@1.4.0:
4423+ dependencies:
4424+ wrappy: 1.0.2
4425+
43054426 optionator@0.9.4:
43064427 dependencies:
43074428 deep-is: 0.1.4
@@ -4350,6 +4471,42 @@ snapshots:
43504471
43514472 pify@2.3.0: {}
43524473
4474+ pino-abstract-transport@2.0.0:
4475+ dependencies:
4476+ split2: 4.2.0
4477+
4478+ pino-pretty@13.0.0:
4479+ dependencies:
4480+ colorette: 2.0.20
4481+ dateformat: 4.6.3
4482+ fast-copy: 3.0.2
4483+ fast-safe-stringify: 2.1.1
4484+ help-me: 5.0.0
4485+ joycon: 3.1.1
4486+ minimist: 1.2.8
4487+ on-exit-leak-free: 2.1.2
4488+ pino-abstract-transport: 2.0.0
4489+ pump: 3.0.2
4490+ secure-json-parse: 2.7.0
4491+ sonic-boom: 4.2.0
4492+ strip-json-comments: 3.1.1
4493+
4494+ pino-std-serializers@7.0.0: {}
4495+
4496+ pino@9.6.0:
4497+ dependencies:
4498+ atomic-sleep: 1.0.0
4499+ fast-redact: 3.5.0
4500+ on-exit-leak-free: 2.1.2
4501+ pino-abstract-transport: 2.0.0
4502+ pino-std-serializers: 7.0.0
4503+ process-warning: 4.0.1
4504+ quick-format-unescaped: 4.0.4
4505+ real-require: 0.2.0
4506+ safe-stable-stringify: 2.5.0
4507+ sonic-boom: 4.2.0
4508+ thread-stream: 3.1.0
4509+
43534510 pirates@4.0.6: {}
43544511
43554512 pkg-types@1.3.1:
@@ -4432,12 +4589,21 @@ snapshots:
44324589
44334590 printable-characters@1.0.42: {}
44344591
4592+ process-warning@4.0.1: {}
4593+
44354594 promise-limit@2.7.0: {}
44364595
4596+ pump@3.0.2:
4597+ dependencies:
4598+ end-of-stream: 1.4.4
4599+ once: 1.4.0
4600+
44374601 punycode@2.3.1: {}
44384602
44394603 queue-microtask@1.2.3: {}
44404604
4605+ quick-format-unescaped@4.0.4: {}
4606+
44414607 read-cache@1.0.0:
44424608 dependencies:
44434609 pify: 2.3.0
@@ -4448,6 +4614,8 @@ snapshots:
44484614
44494615 readdirp@4.1.1: {}
44504616
4617+ real-require@0.2.0: {}
4618+
44514619 regexparam@3.0.0: {}
44524620
44534621 resolve-from@4.0.0: {}
@@ -4509,6 +4677,10 @@ snapshots:
45094677 dependencies:
45104678 mri: 1.2.0
45114679
4680+ safe-stable-stringify@2.5.0: {}
4681+
4682+ secure-json-parse@2.7.0: {}
4683+
45124684 semver@7.7.1: {}
45134685
45144686 set-cookie-parser@2.7.1: {}
@@ -4529,6 +4701,10 @@ snapshots:
45294701 mrmime: 2.0.0
45304702 totalist: 3.0.1
45314703
4704+ sonic-boom@4.2.0:
4705+ dependencies:
4706+ atomic-sleep: 1.0.0
4707+
45324708 source-map-js@1.2.1: {}
45334709
45344710 source-map-support@0.5.21:
@@ -4540,6 +4716,8 @@ snapshots:
45404716
45414717 sourcemap-codec@1.4.8: {}
45424718
4719+ split2@4.2.0: {}
4720+
45434721 stackback@0.0.2: {}
45444722
45454723 stacktracey@2.1.8:
@@ -4670,6 +4848,10 @@ snapshots:
46704848 dependencies:
46714849 any-promise: 1.3.0
46724850
4851+ thread-stream@3.1.0:
4852+ dependencies:
4853+ real-require: 0.2.0
4854+
46734855 tinybench@2.9.0: {}
46744856
46754857 tinyexec@0.3.2: {}
@@ -4877,6 +5059,8 @@ snapshots:
48775059 string-width: 5.1.2
48785060 strip-ansi: 7.1.0
48795061
5062+ wrappy@1.0.2: {}
5063+
48805064 ws@8.18.0: {}
48815065
48825066 xp.css@0.2.6: {}
@@ -17,6 +17,12 @@ importers:
17 drizzle-orm:17 drizzle-orm:
18 specifier: ^0.39.318 specifier: ^0.39.3
19 version: 0.39.3(@cloudflare/workers-types@4.20250204.0)(@libsql/client@0.14.0)19 version: 0.39.3(@cloudflare/workers-types@4.20250204.0)(@libsql/client@0.14.0)
20+ pino:
21+ specifier: ^9.6.0
22+ version: 9.6.0
23+ pino-pretty:
24+ specifier: ^13.0.0
25+ version: 13.0.0
20 uuid:26 uuid:
21 specifier: ^11.0.527 specifier: ^11.0.5
22 version: 11.0.528 version: 11.0.5
@@ -1339,6 +1345,10 @@ packages:
1339 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}1345 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
1340 engines: {node: '>=12'}1346 engines: {node: '>=12'}
1341 1347
1348+ atomic-sleep@1.0.0:
1349+ resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
1350+ engines: {node: '>=8.0.0'}
1351+
1342 autoprefixer@10.4.20:1352 autoprefixer@10.4.20:
1343 resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}1353 resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
1344 engines: {node: ^10 || ^12 || >=14}1354 engines: {node: ^10 || ^12 || >=14}
@@ -1424,6 +1434,9 @@ packages:
1424 color-name@1.1.4:1434 color-name@1.1.4:
1425 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}1435 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
1426 1436
1437+ colorette@2.0.20:
1438+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
1439+
1427 commander@2.20.3:1440 commander@2.20.3:
1428 resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}1441 resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
1429 1442
@@ -1461,6 +1474,9 @@ packages:
1461 resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}1474 resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
1462 engines: {node: '>= 12'}1475 engines: {node: '>= 12'}
1463 1476
1477+ dateformat@4.6.3:
1478+ resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
1479+
1464 debug@4.4.0:1480 debug@4.4.0:
1465 resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}1481 resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
1466 engines: {node: '>=6.0'}1482 engines: {node: '>=6.0'}
@@ -1603,6 +1619,9 @@ packages:
1603 emoji-regex@9.2.2:1619 emoji-regex@9.2.2:
1604 resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}1620 resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
1605 1621
1622+ end-of-stream@1.4.4:
1623+ resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
1624+
1606 es-module-lexer@1.6.0:1625 es-module-lexer@1.6.0:
1607 resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}1626 resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
1608 1627
@@ -1736,6 +1755,9 @@ packages:
1736 resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==}1755 resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==}
1737 engines: {node: '>=12.0.0'}1756 engines: {node: '>=12.0.0'}
1738 1757
1758+ fast-copy@3.0.2:
1759+ resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==}
1760+
1739 fast-deep-equal@3.1.3:1761 fast-deep-equal@3.1.3:
1740 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}1762 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
1741 1763
@@ -1749,6 +1771,13 @@ packages:
1749 fast-levenshtein@2.0.6:1771 fast-levenshtein@2.0.6:
1750 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}1772 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
1751 1773
1774+ fast-redact@3.5.0:
1775+ resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
1776+ engines: {node: '>=6'}
1777+
1778+ fast-safe-stringify@2.1.1:
1779+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
1780+
1752 fastq@1.19.0:1781 fastq@1.19.0:
1753 resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}1782 resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
1754 1783
@@ -1842,6 +1871,9 @@ packages:
1842 resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}1871 resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
1843 engines: {node: '>= 0.4'}1872 engines: {node: '>= 0.4'}
1844 1873
1874+ help-me@5.0.0:
1875+ resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
1876+
1845 ignore@5.3.2:1877 ignore@5.3.2:
1846 resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}1878 resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
1847 engines: {node: '>= 4'}1879 engines: {node: '>= 4'}
@@ -1894,6 +1926,10 @@ packages:
1894 resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}1926 resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
1895 hasBin: true1927 hasBin: true
1896 1928
1929+ joycon@3.1.1:
1930+ resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
1931+ engines: {node: '>=10'}
1932+
1897 js-base64@3.7.7:1933 js-base64@3.7.7:
1898 resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}1934 resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
1899 1935
@@ -1996,6 +2032,9 @@ packages:
1996 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}2032 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
1997 engines: {node: '>=16 || 14 >=14.17'}2033 engines: {node: '>=16 || 14 >=14.17'}
1998 2034
2035+ minimist@1.2.8:
2036+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
2037+
1999 minipass@7.1.2:2038 minipass@7.1.2:
2000 resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}2039 resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
2001 engines: {node: '>=16 || 14 >=14.17'}2040 engines: {node: '>=16 || 14 >=14.17'}
@@ -2059,6 +2098,13 @@ packages:
2059 ohash@1.1.4:2098 ohash@1.1.4:
2060 resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}2099 resolution: {integrity: sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==}
2061 2100
2101+ on-exit-leak-free@2.1.2:
2102+ resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
2103+ engines: {node: '>=14.0.0'}
2104+
2105+ once@1.4.0:
2106+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
2107+
2062 optionator@0.9.4:2108 optionator@0.9.4:
2063 resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}2109 resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
2064 engines: {node: '>= 0.8.0'}2110 engines: {node: '>= 0.8.0'}
@@ -2117,6 +2163,20 @@ packages:
2117 resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}2163 resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
2118 engines: {node: '>=0.10.0'}2164 engines: {node: '>=0.10.0'}
2119 2165
2166+ pino-abstract-transport@2.0.0:
2167+ resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
2168+
2169+ pino-pretty@13.0.0:
2170+ resolution: {integrity: sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==}
2171+ hasBin: true
2172+
2173+ pino-std-serializers@7.0.0:
2174+ resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
2175+
2176+ pino@9.6.0:
2177+ resolution: {integrity: sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg==}
2178+ hasBin: true
2179+
2120 pirates@4.0.6:2180 pirates@4.0.6:
2121 resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}2181 resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
2122 engines: {node: '>= 6'}2182 engines: {node: '>= 6'}
@@ -2266,9 +2326,15 @@ packages:
2266 printable-characters@1.0.42:2326 printable-characters@1.0.42:
2267 resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}2327 resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
2268 2328
2329+ process-warning@4.0.1:
2330+ resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
2331+
2269 promise-limit@2.7.0:2332 promise-limit@2.7.0:
2270 resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}2333 resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}
2271 2334
2335+ pump@3.0.2:
2336+ resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
2337+
2272 punycode@2.3.1:2338 punycode@2.3.1:
2273 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}2339 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
2274 engines: {node: '>=6'}2340 engines: {node: '>=6'}
@@ -2276,6 +2342,9 @@ packages:
2276 queue-microtask@1.2.3:2342 queue-microtask@1.2.3:
2277 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}2343 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
2278 2344
2345+ quick-format-unescaped@4.0.4:
2346+ resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
2347+
2279 read-cache@1.0.0:2348 read-cache@1.0.0:
2280 resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}2349 resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
2281 2350
@@ -2287,6 +2356,10 @@ packages:
2287 resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==}2356 resolution: {integrity: sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==}
2288 engines: {node: '>= 14.18.0'}2357 engines: {node: '>= 14.18.0'}
2289 2358
2359+ real-require@0.2.0:
2360+ resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
2361+ engines: {node: '>= 12.13.0'}
2362+
2290 regexparam@3.0.0:2363 regexparam@3.0.0:
2291 resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==}2364 resolution: {integrity: sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==}
2292 engines: {node: '>=8'}2365 engines: {node: '>=8'}
@@ -2329,6 +2402,13 @@ packages:
2329 resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}2402 resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
2330 engines: {node: '>=6'}2403 engines: {node: '>=6'}
2331 2404
2405+ safe-stable-stringify@2.5.0:
2406+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
2407+ engines: {node: '>=10'}
2408+
2409+ secure-json-parse@2.7.0:
2410+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
2411+
2332 semver@7.7.1:2412 semver@7.7.1:
2333 resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}2413 resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
2334 engines: {node: '>=10'}2414 engines: {node: '>=10'}
@@ -2356,6 +2436,9 @@ packages:
2356 resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==}2436 resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==}
2357 engines: {node: '>=18'}2437 engines: {node: '>=18'}
2358 2438
2439+ sonic-boom@4.2.0:
2440+ resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
2441+
2359 source-map-js@1.2.1:2442 source-map-js@1.2.1:
2360 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}2443 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
2361 engines: {node: '>=0.10.0'}2444 engines: {node: '>=0.10.0'}
@@ -2371,6 +2454,10 @@ packages:
2371 resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}2454 resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
2372 deprecated: Please use @jridgewell/sourcemap-codec instead2455 deprecated: Please use @jridgewell/sourcemap-codec instead
2373 2456
2457+ split2@4.2.0:
2458+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
2459+ engines: {node: '>= 10.x'}
2460+
2374 stackback@0.0.2:2461 stackback@0.0.2:
2375 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}2462 resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
2376 2463
@@ -2455,6 +2542,9 @@ packages:
2455 thenify@3.3.1:2542 thenify@3.3.1:
2456 resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}2543 resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
2457 2544
2545+ thread-stream@3.1.0:
2546+ resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
2547+
2458 tinybench@2.9.0:2548 tinybench@2.9.0:
2459 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}2549 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
2460 2550
@@ -2666,6 +2756,9 @@ packages:
2666 resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}2756 resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
2667 engines: {node: '>=12'}2757 engines: {node: '>=12'}
2668 2758
2759+ wrappy@1.0.2:
2760+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
2761+
2669 ws@8.18.0:2762 ws@8.18.0:
2670 resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}2763 resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
2671 engines: {node: '>=10.0.0'}2764 engines: {node: '>=10.0.0'}
@@ -3585,6 +3678,8 @@ snapshots:
3585 3678
3586 assertion-error@2.0.1: {}3679 assertion-error@2.0.1: {}
3587 3680
3681+ atomic-sleep@1.0.0: {}
3682+
3588 autoprefixer@10.4.20(postcss@8.5.2):3683 autoprefixer@10.4.20(postcss@8.5.2):
3589 dependencies:3684 dependencies:
3590 browserslist: 4.24.43685 browserslist: 4.24.4
@@ -3672,6 +3767,8 @@ snapshots:
3672 3767
3673 color-name@1.1.4: {}3768 color-name@1.1.4: {}
3674 3769
3770+ colorette@2.0.20: {}
3771+
3675 commander@2.20.3: {}3772 commander@2.20.3: {}
3676 3773
3677 commander@4.1.1: {}3774 commander@4.1.1: {}
@@ -3696,6 +3793,8 @@ snapshots:
3696 3793
3697 data-uri-to-buffer@4.0.1: {}3794 data-uri-to-buffer@4.0.1: {}
3698 3795
3796+ dateformat@4.6.3: {}
3797+
3699 debug@4.4.0:3798 debug@4.4.0:
3700 dependencies:3799 dependencies:
3701 ms: 2.1.33800 ms: 2.1.3
@@ -3740,6 +3839,10 @@ snapshots:
3740 3839
3741 emoji-regex@9.2.2: {}3840 emoji-regex@9.2.2: {}
3742 3841
3842+ end-of-stream@1.4.4:
3843+ dependencies:
3844+ once: 1.4.0
3845+
3743 es-module-lexer@1.6.0: {}3846 es-module-lexer@1.6.0: {}
3744 3847
3745 esbuild-register@3.6.0(esbuild@0.19.12):3848 esbuild-register@3.6.0(esbuild@0.19.12):
@@ -4007,6 +4110,8 @@ snapshots:
4007 4110
4008 expect-type@1.1.0: {}4111 expect-type@1.1.0: {}
4009 4112
4113+ fast-copy@3.0.2: {}
4114+
4010 fast-deep-equal@3.1.3: {}4115 fast-deep-equal@3.1.3: {}
4011 4116
4012 fast-glob@3.3.3:4117 fast-glob@3.3.3:
@@ -4021,6 +4126,10 @@ snapshots:
4021 4126
4022 fast-levenshtein@2.0.6: {}4127 fast-levenshtein@2.0.6: {}
4023 4128
4129+ fast-redact@3.5.0: {}
4130+
4131+ fast-safe-stringify@2.1.1: {}
4132+
4024 fastq@1.19.0:4133 fastq@1.19.0:
4025 dependencies:4134 dependencies:
4026 reusify: 1.0.44135 reusify: 1.0.4
@@ -4108,6 +4217,8 @@ snapshots:
4108 dependencies:4217 dependencies:
4109 function-bind: 1.1.24218 function-bind: 1.1.2
4110 4219
4220+ help-me@5.0.0: {}
4221+
4111 ignore@5.3.2: {}4222 ignore@5.3.2: {}
4112 4223
4113 import-fresh@3.3.1:4224 import-fresh@3.3.1:
@@ -4151,6 +4262,8 @@ snapshots:
4151 4262
4152 jiti@1.21.7: {}4263 jiti@1.21.7: {}
4153 4264
4265+ joycon@3.1.1: {}
4266+
4154 js-base64@3.7.7: {}4267 js-base64@3.7.7: {}
4155 4268
4156 js-yaml@4.1.0:4269 js-yaml@4.1.0:
@@ -4255,6 +4368,8 @@ snapshots:
4255 dependencies:4368 dependencies:
4256 brace-expansion: 2.0.14369 brace-expansion: 2.0.1
4257 4370
4371+ minimist@1.2.8: {}
4372+
4258 minipass@7.1.2: {}4373 minipass@7.1.2: {}
4259 4374
4260 mlly@1.7.4:4375 mlly@1.7.4:
@@ -4302,6 +4417,12 @@ snapshots:
4302 4417
4303 ohash@1.1.4: {}4418 ohash@1.1.4: {}
4304 4419
4420+ on-exit-leak-free@2.1.2: {}
4421+
4422+ once@1.4.0:
4423+ dependencies:
4424+ wrappy: 1.0.2
4425+
4305 optionator@0.9.4:4426 optionator@0.9.4:
4306 dependencies:4427 dependencies:
4307 deep-is: 0.1.44428 deep-is: 0.1.4
@@ -4350,6 +4471,42 @@ snapshots:
4350 4471
4351 pify@2.3.0: {}4472 pify@2.3.0: {}
4352 4473
4474+ pino-abstract-transport@2.0.0:
4475+ dependencies:
4476+ split2: 4.2.0
4477+
4478+ pino-pretty@13.0.0:
4479+ dependencies:
4480+ colorette: 2.0.20
4481+ dateformat: 4.6.3
4482+ fast-copy: 3.0.2
4483+ fast-safe-stringify: 2.1.1
4484+ help-me: 5.0.0
4485+ joycon: 3.1.1
4486+ minimist: 1.2.8
4487+ on-exit-leak-free: 2.1.2
4488+ pino-abstract-transport: 2.0.0
4489+ pump: 3.0.2
4490+ secure-json-parse: 2.7.0
4491+ sonic-boom: 4.2.0
4492+ strip-json-comments: 3.1.1
4493+
4494+ pino-std-serializers@7.0.0: {}
4495+
4496+ pino@9.6.0:
4497+ dependencies:
4498+ atomic-sleep: 1.0.0
4499+ fast-redact: 3.5.0
4500+ on-exit-leak-free: 2.1.2
4501+ pino-abstract-transport: 2.0.0
4502+ pino-std-serializers: 7.0.0
4503+ process-warning: 4.0.1
4504+ quick-format-unescaped: 4.0.4
4505+ real-require: 0.2.0
4506+ safe-stable-stringify: 2.5.0
4507+ sonic-boom: 4.2.0
4508+ thread-stream: 3.1.0
4509+
4353 pirates@4.0.6: {}4510 pirates@4.0.6: {}
4354 4511
4355 pkg-types@1.3.1:4512 pkg-types@1.3.1:
@@ -4432,12 +4589,21 @@ snapshots:
4432 4589
4433 printable-characters@1.0.42: {}4590 printable-characters@1.0.42: {}
4434 4591
4592+ process-warning@4.0.1: {}
4593+
4435 promise-limit@2.7.0: {}4594 promise-limit@2.7.0: {}
4436 4595
4596+ pump@3.0.2:
4597+ dependencies:
4598+ end-of-stream: 1.4.4
4599+ once: 1.4.0
4600+
4437 punycode@2.3.1: {}4601 punycode@2.3.1: {}
4438 4602
4439 queue-microtask@1.2.3: {}4603 queue-microtask@1.2.3: {}
4440 4604
4605+ quick-format-unescaped@4.0.4: {}
4606+
4441 read-cache@1.0.0:4607 read-cache@1.0.0:
4442 dependencies:4608 dependencies:
4443 pify: 2.3.04609 pify: 2.3.0
@@ -4448,6 +4614,8 @@ snapshots:
4448 4614
4449 readdirp@4.1.1: {}4615 readdirp@4.1.1: {}
4450 4616
4617+ real-require@0.2.0: {}
4618+
4451 regexparam@3.0.0: {}4619 regexparam@3.0.0: {}
4452 4620
4453 resolve-from@4.0.0: {}4621 resolve-from@4.0.0: {}
@@ -4509,6 +4677,10 @@ snapshots:
4509 dependencies:4677 dependencies:
4510 mri: 1.2.04678 mri: 1.2.0
4511 4679
4680+ safe-stable-stringify@2.5.0: {}
4681+
4682+ secure-json-parse@2.7.0: {}
4683+
4512 semver@7.7.1: {}4684 semver@7.7.1: {}
4513 4685
4514 set-cookie-parser@2.7.1: {}4686 set-cookie-parser@2.7.1: {}
@@ -4529,6 +4701,10 @@ snapshots:
4529 mrmime: 2.0.04701 mrmime: 2.0.0
4530 totalist: 3.0.14702 totalist: 3.0.1
4531 4703
4704+ sonic-boom@4.2.0:
4705+ dependencies:
4706+ atomic-sleep: 1.0.0
4707+
4532 source-map-js@1.2.1: {}4708 source-map-js@1.2.1: {}
4533 4709
4534 source-map-support@0.5.21:4710 source-map-support@0.5.21:
@@ -4540,6 +4716,8 @@ snapshots:
4540 4716
4541 sourcemap-codec@1.4.8: {}4717 sourcemap-codec@1.4.8: {}
4542 4718
4719+ split2@4.2.0: {}
4720+
4543 stackback@0.0.2: {}4721 stackback@0.0.2: {}
4544 4722
4545 stacktracey@2.1.8:4723 stacktracey@2.1.8:
@@ -4670,6 +4848,10 @@ snapshots:
4670 dependencies:4848 dependencies:
4671 any-promise: 1.3.04849 any-promise: 1.3.0
4672 4850
4851+ thread-stream@3.1.0:
4852+ dependencies:
4853+ real-require: 0.2.0
4854+
4673 tinybench@2.9.0: {}4855 tinybench@2.9.0: {}
4674 4856
4675 tinyexec@0.3.2: {}4857 tinyexec@0.3.2: {}
@@ -4877,6 +5059,8 @@ snapshots:
4877 string-width: 5.1.25059 string-width: 5.1.2
4878 strip-ansi: 7.1.05060 strip-ansi: 7.1.0
4879 5061
5062+ wrappy@1.0.2: {}
5063+
4880 ws@8.18.0: {}5064 ws@8.18.0: {}
4881 5065
4882 xp.css@0.2.6: {}5066 xp.css@0.2.6: {}
M src/hooks.server.ts +16 -13
@@ -6,33 +6,36 @@ import db from "$lib/db/db.server";
66 import { handleRateLimit } from "$lib/api/rate-limiter";
77 import { users } from "$lib/db/schema";
88 import { initializeMessageCache } from "$lib/cache/initialize-cache";
9+import { createLogger } from "$lib/utils/logger.server";
10+
11+const log = createLogger('hooks-server');
912
1013 async function setAllUsersOffline() {
1114 try {
12- console.log("Setting all users to offline status...");
15+ log.info("Setting all users to offline status...");
1316 await db.update(users)
1417 .set({
1518 status: "offline"
1619 });
17- console.log("Successfully set all users to offline status");
20+ log.info("Successfully set all users to offline status");
1821 } catch (error) {
19- console.error("Failed to set users offline:", error);
22+ log.error("Failed to set users offline", { error });
2023 }
2124 }
2225
2326 // Initialize database when the server starts
24-console.debug("Initializing database on server start...");
27+log.debug("Initializing database on server start...");
2528 initializeDatabase()
2629 .then(async () => {
2730 await setAllUsersOffline();
2831 await initializeMessageCache();
2932 })
3033 .catch(error => {
31- console.error('Failed to initialize database:', error);
34+ log.error('Failed to initialize database', { error });
3235 });
3336
3437 export const handle: Handle = async ({ event, resolve }) => {
35- console.debug("Handling request:", event.url.pathname);
38+ log.debug("Handling request", { path: event.url.pathname });
3639
3740 // Identify public chat and room requests
3841 const isPublicChatRequest =
@@ -69,7 +72,7 @@ export const handle: Handle = async ({ event, resolve }) => {
6972
7073 // Handle public chat/room requests
7174 if (isPublicChatRequest || isPublicRoomRequest) {
72- console.debug("Public request accessed:", event.url.pathname);
75+ log.debug("Public request accessed", { path: event.url.pathname });
7376 event.locals.user = null;
7477 event.locals.session = null; // Public access doesn't require a session
7578 const response = await resolve(event);
@@ -78,11 +81,11 @@ export const handle: Handle = async ({ event, resolve }) => {
7881
7982 // Handle public routes: if token exists, validate and propagate
8083 if (publicRoutes.includes(event.url.pathname)) {
81- console.debug("Public route accessed:", event.url.pathname);
84+ log.debug("Public route accessed", { path: event.url.pathname });
8285 if (token) {
8386 const { session, user } = await validateSessionToken(token);
8487 if (session !== null) {
85- console.debug("Valid session found on public route for user:", user?.nickname);
88+ log.debug("Valid session found on public route", { user: user?.nickname });
8689 setSessionTokenCookie(event, token, session.expiresAt);
8790 event.locals.session = session;
8891 event.locals.user = user;
@@ -94,7 +97,7 @@ export const handle: Handle = async ({ event, resolve }) => {
9497
9598 // Protected routes: ensure authentication
9699 if (token === null) {
97- console.debug("No session token found");
100+ log.debug("No session token found");
98101 event.locals.user = null;
99102 event.locals.session = null;
100103 const response = new Response("Unauthorized", { status: 401 });
@@ -103,12 +106,12 @@ export const handle: Handle = async ({ event, resolve }) => {
103106
104107 const { session, user } = await validateSessionToken(token);
105108 if (session !== null) {
106- console.debug("Valid session found for user:", user?.nickname);
109+ log.debug("Valid session found", { user: user?.nickname });
107110 setSessionTokenCookie(event, token, session.expiresAt);
108111 event.locals.session = session;
109112 event.locals.user = user;
110113 } else {
111- console.debug("Invalid session token");
114+ log.debug("Invalid session token");
112115 deleteSessionTokenCookie(event);
113116 event.locals.session = null;
114117 event.locals.user = null;
@@ -118,7 +121,7 @@ export const handle: Handle = async ({ event, resolve }) => {
118121
119122 // After authentication, ensure SSE endpoint does not trigger rate limiting
120123 if (event.url.pathname.startsWith('/api/sse') && !event.locals.user) {
121- console.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");
124+ log.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");
122125 return addDebugHeaders(new Response("Unauthorized", { status: 401 }));
123126 }
124127
@@ -6,33 +6,36 @@ import db from "$lib/db/db.server";
6 import { handleRateLimit } from "$lib/api/rate-limiter";6 import { handleRateLimit } from "$lib/api/rate-limiter";
7 import { users } from "$lib/db/schema";7 import { users } from "$lib/db/schema";
8 import { initializeMessageCache } from "$lib/cache/initialize-cache";8 import { initializeMessageCache } from "$lib/cache/initialize-cache";
9+import { createLogger } from "$lib/utils/logger.server";
10+
11+const log = createLogger('hooks-server');
9 12
10 async function setAllUsersOffline() {13 async function setAllUsersOffline() {
11 try {14 try {
12- console.log("Setting all users to offline status...");15+ log.info("Setting all users to offline status...");
13 await db.update(users)16 await db.update(users)
14 .set({ 17 .set({
15 status: "offline"18 status: "offline"
16 });19 });
17- console.log("Successfully set all users to offline status");20+ log.info("Successfully set all users to offline status");
18 } catch (error) {21 } catch (error) {
19- console.error("Failed to set users offline:", error);22+ log.error("Failed to set users offline", { error });
20 }23 }
21 }24 }
22 25
23 // Initialize database when the server starts26 // Initialize database when the server starts
24-console.debug("Initializing database on server start...");27+log.debug("Initializing database on server start...");
25 initializeDatabase()28 initializeDatabase()
26 .then(async () => {29 .then(async () => {
27 await setAllUsersOffline();30 await setAllUsersOffline();
28 await initializeMessageCache();31 await initializeMessageCache();
29 })32 })
30 .catch(error => {33 .catch(error => {
31- console.error('Failed to initialize database:', error);34+ log.error('Failed to initialize database', { error });
32 });35 });
33 36
34 export const handle: Handle = async ({ event, resolve }) => {37 export const handle: Handle = async ({ event, resolve }) => {
35- console.debug("Handling request:", event.url.pathname);38+ log.debug("Handling request", { path: event.url.pathname });
36 39
37 // Identify public chat and room requests40 // Identify public chat and room requests
38 const isPublicChatRequest = 41 const isPublicChatRequest =
@@ -69,7 +72,7 @@ export const handle: Handle = async ({ event, resolve }) => {
69 72
70 // Handle public chat/room requests73 // Handle public chat/room requests
71 if (isPublicChatRequest || isPublicRoomRequest) {74 if (isPublicChatRequest || isPublicRoomRequest) {
72- console.debug("Public request accessed:", event.url.pathname);75+ log.debug("Public request accessed", { path: event.url.pathname });
73 event.locals.user = null;76 event.locals.user = null;
74 event.locals.session = null; // Public access doesn't require a session77 event.locals.session = null; // Public access doesn't require a session
75 const response = await resolve(event);78 const response = await resolve(event);
@@ -78,11 +81,11 @@ export const handle: Handle = async ({ event, resolve }) => {
78 81
79 // Handle public routes: if token exists, validate and propagate82 // Handle public routes: if token exists, validate and propagate
80 if (publicRoutes.includes(event.url.pathname)) {83 if (publicRoutes.includes(event.url.pathname)) {
81- console.debug("Public route accessed:", event.url.pathname);84+ log.debug("Public route accessed", { path: event.url.pathname });
82 if (token) {85 if (token) {
83 const { session, user } = await validateSessionToken(token);86 const { session, user } = await validateSessionToken(token);
84 if (session !== null) {87 if (session !== null) {
85- console.debug("Valid session found on public route for user:", user?.nickname);88+ log.debug("Valid session found on public route", { user: user?.nickname });
86 setSessionTokenCookie(event, token, session.expiresAt);89 setSessionTokenCookie(event, token, session.expiresAt);
87 event.locals.session = session;90 event.locals.session = session;
88 event.locals.user = user;91 event.locals.user = user;
@@ -94,7 +97,7 @@ export const handle: Handle = async ({ event, resolve }) => {
94 97
95 // Protected routes: ensure authentication98 // Protected routes: ensure authentication
96 if (token === null) {99 if (token === null) {
97- console.debug("No session token found");100+ log.debug("No session token found");
98 event.locals.user = null;101 event.locals.user = null;
99 event.locals.session = null;102 event.locals.session = null;
100 const response = new Response("Unauthorized", { status: 401 });103 const response = new Response("Unauthorized", { status: 401 });
@@ -103,12 +106,12 @@ export const handle: Handle = async ({ event, resolve }) => {
103 106
104 const { session, user } = await validateSessionToken(token);107 const { session, user } = await validateSessionToken(token);
105 if (session !== null) {108 if (session !== null) {
106- console.debug("Valid session found for user:", user?.nickname);109+ log.debug("Valid session found", { user: user?.nickname });
107 setSessionTokenCookie(event, token, session.expiresAt);110 setSessionTokenCookie(event, token, session.expiresAt);
108 event.locals.session = session;111 event.locals.session = session;
109 event.locals.user = user;112 event.locals.user = user;
110 } else {113 } else {
111- console.debug("Invalid session token");114+ log.debug("Invalid session token");
112 deleteSessionTokenCookie(event);115 deleteSessionTokenCookie(event);
113 event.locals.session = null;116 event.locals.session = null;
114 event.locals.user = null;117 event.locals.user = null;
@@ -118,7 +121,7 @@ export const handle: Handle = async ({ event, resolve }) => {
118 121
119 // After authentication, ensure SSE endpoint does not trigger rate limiting122 // After authentication, ensure SSE endpoint does not trigger rate limiting
120 if (event.url.pathname.startsWith('/api/sse') && !event.locals.user) {123 if (event.url.pathname.startsWith('/api/sse') && !event.locals.user) {
121- console.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");124+ log.debug("SSE endpoint accessed without valid session, returning 401 without rate limiting");
122 return addDebugHeaders(new Response("Unauthorized", { status: 401 }));125 return addDebugHeaders(new Response("Unauthorized", { status: 401 }));
123 }126 }
124 127
M src/lib/api/rate-limiter.ts +5 -2
@@ -1,4 +1,7 @@
11 import type { RequestEvent } from '@sveltejs/kit';
2+import { createLogger } from '$lib/utils/logger.server';
3+
4+const log = createLogger('rate-limiter');
25
36 const isDev = import.meta.env.DEV;
47
@@ -86,7 +89,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
8689
8790 // Check if we're over the limit
8891 if (validRequests.length >= points) {
89- console.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);
92+ log.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);
9093 const oldestRequest = validRequests[0];
9194 const retryAfter = durationMs - (now - oldestRequest.timestamp);
9295 return { limited: true, retryAfter };
@@ -104,7 +107,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
104107 const requests = ipRequests.get(ip) || [];
105108 const validRequests = requests.filter(req => now - req.timestamp < durationMs);
106109
107- console.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);
110+ log.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);
108111
109112 if (validRequests.length >= points) {
110113 const oldestRequest = validRequests[0];
@@ -1,4 +1,7 @@
1 import type { RequestEvent } from '@sveltejs/kit';1 import type { RequestEvent } from '@sveltejs/kit';
2+import { createLogger } from '$lib/utils/logger.server';
3+
4+const log = createLogger('rate-limiter');
2 5
3 const isDev = import.meta.env.DEV;6 const isDev = import.meta.env.DEV;
4 7
@@ -86,7 +89,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
86 89
87 // Check if we're over the limit90 // Check if we're over the limit
88 if (validRequests.length >= points) {91 if (validRequests.length >= points) {
89- console.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);92+ log.debug(`Rate limit exceeded for IP ${ip} on ${endpointType} endpoint (${isAuthenticated ? 'authenticated' : 'unauthenticated'})`);
90 const oldestRequest = validRequests[0];93 const oldestRequest = validRequests[0];
91 const retryAfter = durationMs - (now - oldestRequest.timestamp);94 const retryAfter = durationMs - (now - oldestRequest.timestamp);
92 return { limited: true, retryAfter };95 return { limited: true, retryAfter };
@@ -104,7 +107,7 @@ function isRateLimited(ip: string, endpointType: keyof typeof RATE_LIMITS, isAut
104 const requests = ipRequests.get(ip) || [];107 const requests = ipRequests.get(ip) || [];
105 const validRequests = requests.filter(req => now - req.timestamp < durationMs);108 const validRequests = requests.filter(req => now - req.timestamp < durationMs);
106 109
107- console.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);110+ log.debug(`Rate limit check for ${ip} on ${endpointType}: ${validRequests.length}/${points} requests in the last ${durationMs/1000}s`);
108 111
109 if (validRequests.length >= points) {112 if (validRequests.length >= points) {
110 const oldestRequest = validRequests[0];113 const oldestRequest = validRequests[0];
M src/lib/api/session.cookie.ts +10 -6
@@ -1,11 +1,15 @@
11 import type { Cookies } from "@sveltejs/kit";
2+import { createLogger } from "$lib/utils/logger.server";
3+
4+const log = createLogger('session-cookie');
25
36 // Helper function to determine cookie domain
47 function getCookieDomain(): string | undefined {
58 if (process.env.NODE_ENV === 'production') {
69 // Check for Cloudflare Pages domain
710 if (process.env.CF_PAGES_URL) {
8- console.debug("Cloudflare Pages URL:", process.env.CF_PAGES_URL);
11+ const url = process.env.CF_PAGES_URL;
12+ log.debug("Cloudflare Pages URL:", { url });
913 // For preview deployments, use host-only cookie
1014 if (process.env.CF_PAGES_BRANCH !== 'main') {
1115 return undefined;
@@ -20,10 +24,10 @@ function getCookieDomain(): string | undefined {
2024 }
2125
2226 export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token: string, expiresAt: number): void {
23- console.debug("Setting session cookie with expiry:", new Date(expiresAt));
27+ log.debug("Setting session cookie with expiry:", new Date(expiresAt));
2428
2529 const domain = getCookieDomain();
26- console.debug("Setting cookie for domain:", domain);
30+ log.debug("Setting cookie for domain:", { domain });
2731
2832 cookies.set("session", token, {
2933 httpOnly: true,
@@ -36,10 +40,10 @@ export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token:
3640 }
3741
3842 export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): void {
39- console.debug("Deleting session cookie");
43+ log.debug("Deleting session cookie");
4044
4145 const domain = getCookieDomain();
42- console.debug("Deleting cookie for domain:", domain);
46+ log.debug("Deleting cookie for domain:", { domain });
4347
4448 cookies.set("session", "", {
4549 httpOnly: true,
@@ -52,6 +56,6 @@ export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): voi
5256 }
5357
5458 export function getSessionTokenCookie({ cookies }: { cookies: Cookies }): string | undefined {
55- console.debug("Getting session cookie");
59+ log.debug("Getting session cookie");
5660 return cookies.get("session");
5761 } ⏎̸
@@ -1,11 +1,15 @@
1 import type { Cookies } from "@sveltejs/kit";1 import type { Cookies } from "@sveltejs/kit";
2+import { createLogger } from "$lib/utils/logger.server";
3+
4+const log = createLogger('session-cookie');
2 5
3 // Helper function to determine cookie domain6 // Helper function to determine cookie domain
4 function getCookieDomain(): string | undefined {7 function getCookieDomain(): string | undefined {
5 if (process.env.NODE_ENV === 'production') {8 if (process.env.NODE_ENV === 'production') {
6 // Check for Cloudflare Pages domain9 // Check for Cloudflare Pages domain
7 if (process.env.CF_PAGES_URL) {10 if (process.env.CF_PAGES_URL) {
8- console.debug("Cloudflare Pages URL:", process.env.CF_PAGES_URL);11+ const url = process.env.CF_PAGES_URL;
12+ log.debug("Cloudflare Pages URL:", { url });
9 // For preview deployments, use host-only cookie13 // For preview deployments, use host-only cookie
10 if (process.env.CF_PAGES_BRANCH !== 'main') {14 if (process.env.CF_PAGES_BRANCH !== 'main') {
11 return undefined;15 return undefined;
@@ -20,10 +24,10 @@ function getCookieDomain(): string | undefined {
20 }24 }
21 25
22 export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token: string, expiresAt: number): void {26 export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token: string, expiresAt: number): void {
23- console.debug("Setting session cookie with expiry:", new Date(expiresAt));27+ log.debug("Setting session cookie with expiry:", new Date(expiresAt));
24 28
25 const domain = getCookieDomain();29 const domain = getCookieDomain();
26- console.debug("Setting cookie for domain:", domain);30+ log.debug("Setting cookie for domain:", { domain });
27 31
28 cookies.set("session", token, {32 cookies.set("session", token, {
29 httpOnly: true,33 httpOnly: true,
@@ -36,10 +40,10 @@ export function setSessionTokenCookie({ cookies }: { cookies: Cookies }, token:
36 }40 }
37 41
38 export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): void {42 export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): void {
39- console.debug("Deleting session cookie");43+ log.debug("Deleting session cookie");
40 44
41 const domain = getCookieDomain();45 const domain = getCookieDomain();
42- console.debug("Deleting cookie for domain:", domain);46+ log.debug("Deleting cookie for domain:", { domain });
43 47
44 cookies.set("session", "", {48 cookies.set("session", "", {
45 httpOnly: true,49 httpOnly: true,
@@ -52,6 +56,6 @@ export function deleteSessionTokenCookie({ cookies }: { cookies: Cookies }): voi
52 }56 }
53 57
54 export function getSessionTokenCookie({ cookies }: { cookies: Cookies }): string | undefined {58 export function getSessionTokenCookie({ cookies }: { cookies: Cookies }): string | undefined {
55- console.debug("Getting session cookie");59+ log.debug("Getting session cookie");
56 return cookies.get("session");60 return cookies.get("session");
57 } ⏎̸61 } ⏎̸
M src/lib/api/session.server.ts +15 -12
@@ -2,6 +2,9 @@ import db from '../db/db.server';
22 import { sessions, users } from '../db/schema';
33 import { eq } from 'drizzle-orm';
44 import type { Session, User } from '../types/chat';
5+import { createLogger } from '../utils/logger.server';
6+
7+const log = createLogger('session-server');
58
69 // Helper: Compute SHA-256 hash of a message and encode it as a hex string.
710 export async function sha256(message: string): Promise<string> {
@@ -41,14 +44,14 @@ export function generateSessionToken(): string {
4144 const randomBytesArray = new Uint8Array(20);
4245 crypto.getRandomValues(randomBytesArray);
4346 const token = base32Encode(randomBytesArray);
44- console.debug("Generated session token:", token);
47+ log.debug("Generated session token", { token });
4548 return token;
4649 }
4750
4851 // Create a session record once a valid token is available.
4952 // The session ID is the SHA-256 hash of the token.
5053 export async function createSession(token: string, userId: string): Promise<Session> {
51- console.debug("Creating session for user:", userId);
54+ log.debug("Creating session", { userId });
5255 const sessionId = await sha256(token);
5356 const createdAt = Date.now();
5457 const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry
@@ -58,7 +61,7 @@ export async function createSession(token: string, userId: string): Promise<Sess
5861 createdAt,
5962 expiresAt,
6063 });
61- console.debug("Session created with id:", sessionId);
64+ log.debug("Session created", { sessionId });
6265 return { id: sessionId, userId, createdAt, expiresAt };
6366 }
6467
@@ -73,41 +76,41 @@ export type SessionValidationResult =
7376
7477 // Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.
7578 export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
76- console.debug("Validating session token:", token);
79+ log.debug("Validating session token");
7780 const sessionId = await sha256(token);
7881 const session = await db.query.sessions.findFirst({
7982 where: eq(sessions.id, sessionId)
8083 });
8184 if (!session) {
82- console.debug("Session not found for id:", sessionId);
85+ log.debug("Session not found", { sessionId });
8386 return { session: null, user: null };
8487 }
8588 const now = Date.now();
8689 if (session.expiresAt < now) {
87- console.debug("Session expired for id:", sessionId);
90+ log.debug("Session expired", { sessionId });
8891 return { session: null, user: null };
8992 }
9093 const user = await db.query.users.findFirst({
9194 where: eq(users.id, session.userId)
9295 });
9396 if (!user) {
94- console.debug("User not found for session id:", sessionId);
97+ log.debug("User not found for session", { sessionId });
9598 return { session: null, user: null };
9699 }
97- console.debug("Session validated for id:", sessionId);
100+ log.debug("Session validated", { sessionId, userId: user.id });
98101 return { session, user };
99102 }
100103
101104 // Invalidate a specific session by deleting it from the database.
102105 export async function invalidateSession(sessionId: string): Promise<void> {
103- console.debug("Invalidating session with id:", sessionId);
106+ log.debug("Invalidating session", { sessionId });
104107 await db.delete(sessions).where(eq(sessions.id, sessionId));
105- console.debug("Session invalidated:", sessionId);
108+ log.debug("Session invalidated", { sessionId });
106109 }
107110
108111 // Invalidate all sessions for a given user.
109112 export async function invalidateAllSessions(userId: string): Promise<void> {
110- console.debug("Invalidating all sessions for user:", userId);
113+ log.debug("Invalidating all sessions for user", { userId });
111114 await db.delete(sessions).where(eq(sessions.userId, userId));
112- console.debug("All sessions invalidated for user:", userId);
115+ log.debug("All sessions invalidated", { userId });
113116 } ⏎̸
@@ -2,6 +2,9 @@ import db from '../db/db.server';
2 import { sessions, users } from '../db/schema';2 import { sessions, users } from '../db/schema';
3 import { eq } from 'drizzle-orm';3 import { eq } from 'drizzle-orm';
4 import type { Session, User } from '../types/chat';4 import type { Session, User } from '../types/chat';
5+import { createLogger } from '../utils/logger.server';
6+
7+const log = createLogger('session-server');
5 8
6 // Helper: Compute SHA-256 hash of a message and encode it as a hex string.9 // Helper: Compute SHA-256 hash of a message and encode it as a hex string.
7 export async function sha256(message: string): Promise<string> {10 export async function sha256(message: string): Promise<string> {
@@ -41,14 +44,14 @@ export function generateSessionToken(): string {
41 const randomBytesArray = new Uint8Array(20);44 const randomBytesArray = new Uint8Array(20);
42 crypto.getRandomValues(randomBytesArray);45 crypto.getRandomValues(randomBytesArray);
43 const token = base32Encode(randomBytesArray);46 const token = base32Encode(randomBytesArray);
44- console.debug("Generated session token:", token);47+ log.debug("Generated session token", { token });
45 return token;48 return token;
46 }49 }
47 50
48 // Create a session record once a valid token is available.51 // Create a session record once a valid token is available.
49 // The session ID is the SHA-256 hash of the token.52 // The session ID is the SHA-256 hash of the token.
50 export async function createSession(token: string, userId: string): Promise<Session> {53 export async function createSession(token: string, userId: string): Promise<Session> {
51- console.debug("Creating session for user:", userId);54+ log.debug("Creating session", { userId });
52 const sessionId = await sha256(token);55 const sessionId = await sha256(token);
53 const createdAt = Date.now();56 const createdAt = Date.now();
54 const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry57 const expiresAt = createdAt + 24 * 60 * 60 * 1000; // 24 hours expiry
@@ -58,7 +61,7 @@ export async function createSession(token: string, userId: string): Promise<Sess
58 createdAt,61 createdAt,
59 expiresAt,62 expiresAt,
60 });63 });
61- console.debug("Session created with id:", sessionId);64+ log.debug("Session created", { sessionId });
62 return { id: sessionId, userId, createdAt, expiresAt };65 return { id: sessionId, userId, createdAt, expiresAt };
63 }66 }
64 67
@@ -73,41 +76,41 @@ export type SessionValidationResult =
73 76
74 // Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.77 // Validate a session token by converting it to its SHA‑256 hash, checking expiration, and fetching the user.
75 export async function validateSessionToken(token: string): Promise<SessionValidationResult> {78 export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
76- console.debug("Validating session token:", token);79+ log.debug("Validating session token");
77 const sessionId = await sha256(token);80 const sessionId = await sha256(token);
78 const session = await db.query.sessions.findFirst({81 const session = await db.query.sessions.findFirst({
79 where: eq(sessions.id, sessionId)82 where: eq(sessions.id, sessionId)
80 });83 });
81 if (!session) {84 if (!session) {
82- console.debug("Session not found for id:", sessionId);85+ log.debug("Session not found", { sessionId });
83 return { session: null, user: null };86 return { session: null, user: null };
84 }87 }
85 const now = Date.now();88 const now = Date.now();
86 if (session.expiresAt < now) {89 if (session.expiresAt < now) {
87- console.debug("Session expired for id:", sessionId);90+ log.debug("Session expired", { sessionId });
88 return { session: null, user: null };91 return { session: null, user: null };
89 }92 }
90 const user = await db.query.users.findFirst({93 const user = await db.query.users.findFirst({
91 where: eq(users.id, session.userId)94 where: eq(users.id, session.userId)
92 });95 });
93 if (!user) {96 if (!user) {
94- console.debug("User not found for session id:", sessionId);97+ log.debug("User not found for session", { sessionId });
95 return { session: null, user: null };98 return { session: null, user: null };
96 }99 }
97- console.debug("Session validated for id:", sessionId);100+ log.debug("Session validated", { sessionId, userId: user.id });
98 return { session, user };101 return { session, user };
99 }102 }
100 103
101 // Invalidate a specific session by deleting it from the database.104 // Invalidate a specific session by deleting it from the database.
102 export async function invalidateSession(sessionId: string): Promise<void> {105 export async function invalidateSession(sessionId: string): Promise<void> {
103- console.debug("Invalidating session with id:", sessionId);106+ log.debug("Invalidating session", { sessionId });
104 await db.delete(sessions).where(eq(sessions.id, sessionId));107 await db.delete(sessions).where(eq(sessions.id, sessionId));
105- console.debug("Session invalidated:", sessionId);108+ log.debug("Session invalidated", { sessionId });
106 }109 }
107 110
108 // Invalidate all sessions for a given user.111 // Invalidate all sessions for a given user.
109 export async function invalidateAllSessions(userId: string): Promise<void> {112 export async function invalidateAllSessions(userId: string): Promise<void> {
110- console.debug("Invalidating all sessions for user:", userId);113+ log.debug("Invalidating all sessions for user", { userId });
111 await db.delete(sessions).where(eq(sessions.userId, userId));114 await db.delete(sessions).where(eq(sessions.userId, userId));
112- console.debug("All sessions invalidated for user:", userId);115+ log.debug("All sessions invalidated", { userId });
113 } ⏎̸116 } ⏎̸
M src/lib/cache/initialize-cache.ts +7 -4
@@ -2,16 +2,19 @@ import { messageCache } from '$lib/cache/message-cache';
22 import db from '$lib/db/db.server';
33 import { messages } from '$lib/db/schema';
44 import { desc } from 'drizzle-orm';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('cache-initialize');
58
69 let isCacheInitialized = false;
710
811 export async function initializeMessageCache(): Promise<void> {
912 if (isCacheInitialized) {
10- console.debug('[Cache] Message cache already initialized.');
13+ log.debug('[Cache] Message cache already initialized.');
1114 return;
1215 }
1316 try {
14- console.debug('[Cache] Initializing message cache with pre-population.');
17+ log.debug('[Cache] Initializing message cache with pre-population.');
1518 const fetchedMessages = await db.select()
1619 .from(messages)
1720 .orderBy(desc(messages.timestamp))
@@ -20,11 +23,11 @@ export async function initializeMessageCache(): Promise<void> {
2023 await messageCache.initialize(fetchedMessages.reverse());
2124 isCacheInitialized = true;
2225 const stats = messageCache.getCacheStats();
23- console.debug('[Cache] Message cache initialized successfully:', {
26+ log.debug('[Cache] Message cache initialized successfully:', {
2427 roomCount: stats.roomCount,
2528 totalMessages: stats.totalMessages
2629 });
2730 } catch (error) {
28- console.error('[Cache] Failed to initialize message cache:', error);
31+ log.error('[Cache] Failed to initialize message cache:', { error: error instanceof Error ? error.message : 'Unknown error' });
2932 }
3033 } ⏎̸
@@ -2,16 +2,19 @@ import { messageCache } from '$lib/cache/message-cache';
2 import db from '$lib/db/db.server';2 import db from '$lib/db/db.server';
3 import { messages } from '$lib/db/schema';3 import { messages } from '$lib/db/schema';
4 import { desc } from 'drizzle-orm';4 import { desc } from 'drizzle-orm';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('cache-initialize');
5 8
6 let isCacheInitialized = false;9 let isCacheInitialized = false;
7 10
8 export async function initializeMessageCache(): Promise<void> {11 export async function initializeMessageCache(): Promise<void> {
9 if (isCacheInitialized) {12 if (isCacheInitialized) {
10- console.debug('[Cache] Message cache already initialized.');13+ log.debug('[Cache] Message cache already initialized.');
11 return;14 return;
12 }15 }
13 try {16 try {
14- console.debug('[Cache] Initializing message cache with pre-population.');17+ log.debug('[Cache] Initializing message cache with pre-population.');
15 const fetchedMessages = await db.select()18 const fetchedMessages = await db.select()
16 .from(messages)19 .from(messages)
17 .orderBy(desc(messages.timestamp))20 .orderBy(desc(messages.timestamp))
@@ -20,11 +23,11 @@ export async function initializeMessageCache(): Promise<void> {
20 await messageCache.initialize(fetchedMessages.reverse());23 await messageCache.initialize(fetchedMessages.reverse());
21 isCacheInitialized = true;24 isCacheInitialized = true;
22 const stats = messageCache.getCacheStats();25 const stats = messageCache.getCacheStats();
23- console.debug('[Cache] Message cache initialized successfully:', {26+ log.debug('[Cache] Message cache initialized successfully:', {
24 roomCount: stats.roomCount,27 roomCount: stats.roomCount,
25 totalMessages: stats.totalMessages28 totalMessages: stats.totalMessages
26 });29 });
27 } catch (error) {30 } catch (error) {
28- console.error('[Cache] Failed to initialize message cache:', error);31+ log.error('[Cache] Failed to initialize message cache:', { error: error instanceof Error ? error.message : 'Unknown error' });
29 }32 }
30 } ⏎̸33 } ⏎̸
M src/lib/db/db.server.ts +8 -5
@@ -2,30 +2,33 @@ import { drizzle } from 'drizzle-orm/libsql';
22 import { createClient } from '@libsql/client';
33 import { TURSO_AUTH_TOKEN, TURSO_DATABASE_URL } from '$env/static/private';
44 import schema, { ensureDefaultChatRoom } from './schema';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('db-server');
58
69 const client = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN });
710 const db = drizzle(client, { schema });
811
912 // Initialize database with required data
1013 export const initializeDatabase = async () => {
11- console.debug('Initializing database...');
14+ log.debug('Initializing database...');
1215 try {
1316 await ensureDefaultChatRoom(db);
14- console.debug('Database initialization complete');
17+ log.debug('Database initialization complete');
1518 } catch (error) {
16- console.error('Error initializing database:', error);
19+ log.error('Error initializing database:', { error: error instanceof Error ? error.message : 'Unknown error' });
1720 throw error;
1821 }
1922 };
2023
2124 // Call initialization when the module is loaded
2225 initializeDatabase().catch(error => {
23- console.error('Failed to initialize database:', error);
26+ log.error('Failed to initialize database:', { error: error instanceof Error ? error.message : 'Unknown error' });
2427 });
2528
2629 export const testConnection = async () => {
2730 const result = await db.query.users.findMany();
28- console.log(result);
31+ log.debug('Test connection result:', result);
2932 };
3033
3134 export default db;⏎̸
@@ -2,30 +2,33 @@ import { drizzle } from 'drizzle-orm/libsql';
2 import { createClient } from '@libsql/client';2 import { createClient } from '@libsql/client';
3 import { TURSO_AUTH_TOKEN, TURSO_DATABASE_URL } from '$env/static/private';3 import { TURSO_AUTH_TOKEN, TURSO_DATABASE_URL } from '$env/static/private';
4 import schema, { ensureDefaultChatRoom } from './schema';4 import schema, { ensureDefaultChatRoom } from './schema';
5+import { createLogger } from '$lib/utils/logger.server';
6+
7+const log = createLogger('db-server');
5 8
6 const client = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN });9 const client = createClient({ url: TURSO_DATABASE_URL, authToken: TURSO_AUTH_TOKEN });
7 const db = drizzle(client, { schema });10 const db = drizzle(client, { schema });
8 11
9 // Initialize database with required data12 // Initialize database with required data
10 export const initializeDatabase = async () => {13 export const initializeDatabase = async () => {
11- console.debug('Initializing database...');14+ log.debug('Initializing database...');
12 try {15 try {
13 await ensureDefaultChatRoom(db);16 await ensureDefaultChatRoom(db);
14- console.debug('Database initialization complete');17+ log.debug('Database initialization complete');
15 } catch (error) {18 } catch (error) {
16- console.error('Error initializing database:', error);19+ log.error('Error initializing database:', { error: error instanceof Error ? error.message : 'Unknown error' });
17 throw error;20 throw error;
18 }21 }
19 };22 };
20 23
21 // Call initialization when the module is loaded24 // Call initialization when the module is loaded
22 initializeDatabase().catch(error => {25 initializeDatabase().catch(error => {
23- console.error('Failed to initialize database:', error);26+ log.error('Failed to initialize database:', { error: error instanceof Error ? error.message : 'Unknown error' });
24 });27 });
25 28
26 export const testConnection = async () => {29 export const testConnection = async () => {
27 const result = await db.query.users.findMany();30 const result = await db.query.users.findMany();
28- console.log(result);31+ log.debug('Test connection result:', result);
29 };32 };
30 33
31 export default db;⏎̸34 export default db;⏎̸
M src/lib/db/schema.ts +9 -6
@@ -4,6 +4,9 @@ import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "
44 import { v4 as uuidv4 } from "uuid";
55 import { eq } from "drizzle-orm";
66 import type { LibSQLDatabase } from "drizzle-orm/libsql";
7+import { createLogger } from "$lib/utils/logger.server";
8+
9+const log = createLogger('schema');
710
811 // Default chat room ID - using a UUID to avoid collisions
912 export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';
@@ -120,7 +123,7 @@ const schema = {
120123
121124 // Function to ensure default chat room exists
122125 export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
123- console.debug('Ensuring default chat room exists...');
126+ log.debug('Ensuring default chat room exists...');
124127 try {
125128 // Use a transaction to handle concurrent creation attempts
126129 await db.transaction(async (tx) => {
@@ -130,25 +133,25 @@ export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
130133 .get();
131134
132135 if (!defaultRoom) {
133- console.debug('Creating default chat room...');
136+ log.debug('Creating default chat room...');
134137 await tx.insert(chatRooms).values({
135138 id: DEFAULT_CHAT_ROOM_ID,
136139 name: 'General',
137140 type: 'group',
138141 createdAt: Date.now()
139142 });
140- console.debug('Default chat room created successfully');
143+ log.debug('Default chat room created successfully');
141144 } else {
142- console.debug('Default chat room already exists');
145+ log.debug('Default chat room already exists');
143146 }
144147 });
145148 } catch (error: unknown) {
146149 // If the error is a unique constraint violation, another process probably created the room
147150 if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
148- console.debug('Default chat room was created by another process');
151+ log.debug('Default chat room was created by another process');
149152 return;
150153 }
151- console.error('Error ensuring default chat room:', error);
154+ log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
152155 throw error;
153156 }
154157 }
@@ -4,6 +4,9 @@ import type { User, ChatRoom, Message, UserStatus, MessageType, Session } from "
4 import { v4 as uuidv4 } from "uuid";4 import { v4 as uuidv4 } from "uuid";
5 import { eq } from "drizzle-orm";5 import { eq } from "drizzle-orm";
6 import type { LibSQLDatabase } from "drizzle-orm/libsql";6 import type { LibSQLDatabase } from "drizzle-orm/libsql";
7+import { createLogger } from "$lib/utils/logger.server";
8+
9+const log = createLogger('schema');
7 10
8 // Default chat room ID - using a UUID to avoid collisions11 // Default chat room ID - using a UUID to avoid collisions
9 export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';12 export const DEFAULT_CHAT_ROOM_ID = '00000000-0000-0000-0000-000000000001';
@@ -120,7 +123,7 @@ const schema = {
120 123
121 // Function to ensure default chat room exists124 // Function to ensure default chat room exists
122 export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {125 export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
123- console.debug('Ensuring default chat room exists...');126+ log.debug('Ensuring default chat room exists...');
124 try {127 try {
125 // Use a transaction to handle concurrent creation attempts128 // Use a transaction to handle concurrent creation attempts
126 await db.transaction(async (tx) => {129 await db.transaction(async (tx) => {
@@ -130,25 +133,25 @@ export async function ensureDefaultChatRoom(db: LibSQLDatabase<typeof schema>) {
130 .get();133 .get();
131 134
132 if (!defaultRoom) {135 if (!defaultRoom) {
133- console.debug('Creating default chat room...');136+ log.debug('Creating default chat room...');
134 await tx.insert(chatRooms).values({137 await tx.insert(chatRooms).values({
135 id: DEFAULT_CHAT_ROOM_ID,138 id: DEFAULT_CHAT_ROOM_ID,
136 name: 'General',139 name: 'General',
137 type: 'group',140 type: 'group',
138 createdAt: Date.now()141 createdAt: Date.now()
139 });142 });
140- console.debug('Default chat room created successfully');143+ log.debug('Default chat room created successfully');
141 } else {144 } else {
142- console.debug('Default chat room already exists');145+ log.debug('Default chat room already exists');
143 }146 }
144 });147 });
145 } catch (error: unknown) {148 } catch (error: unknown) {
146 // If the error is a unique constraint violation, another process probably created the room149 // If the error is a unique constraint violation, another process probably created the room
147 if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {150 if (isLibSQLError(error) && error.code === 'SQLITE_CONSTRAINT') {
148- console.debug('Default chat room was created by another process');151+ log.debug('Default chat room was created by another process');
149 return;152 return;
150 }153 }
151- console.error('Error ensuring default chat room:', error);154+ log.error('Error ensuring default chat room:', { error: error instanceof Error ? error.message : 'Unknown error' });
152 throw error;155 throw error;
153 }156 }
154 }157 }
A src/lib/utils/logger.server.ts +55 -0
@@ -0,0 +1,55 @@
1+import pino from 'pino';
2+
3+const logLevel = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
4+
5+class LoggerService {
6+ private static instance: LoggerService;
7+ private logger: pino.Logger;
8+ private moduleLoggers: Map<string, Logger>;
9+
10+ private constructor() {
11+ this.logger = pino({
12+ transport: {
13+ target: 'pino-pretty',
14+ options: {
15+ colorize: true,
16+ translateTime: 'HH:MM:ss',
17+ ignore: 'pid,hostname',
18+ },
19+ },
20+ level: logLevel,
21+ });
22+ this.moduleLoggers = new Map();
23+ }
24+
25+ public static getInstance(): LoggerService {
26+ if (!LoggerService.instance) {
27+ LoggerService.instance = new LoggerService();
28+ }
29+ return LoggerService.instance;
30+ }
31+
32+ public getLogger(module: string): Logger {
33+ if (!this.moduleLoggers.has(module)) {
34+ this.moduleLoggers.set(module, {
35+ debug: (msg: string, obj = {}) => this.logger.debug({ module, ...obj }, msg),
36+ info: (msg: string, obj = {}) => this.logger.info({ module, ...obj }, msg),
37+ warn: (msg: string, obj = {}) => this.logger.warn({ module, ...obj }, msg),
38+ error: (msg: string, obj = {}) => this.logger.error({ module, ...obj }, msg)
39+ });
40+ }
41+ return this.moduleLoggers.get(module)!;
42+ }
43+}
44+
45+export interface Logger {
46+ debug: (msg: string, obj?: object) => void;
47+ info: (msg: string, obj?: object) => void;
48+ warn: (msg: string, obj?: object) => void;
49+ error: (msg: string, obj?: object) => void;
50+}
51+
52+// Export a function to get a logger for a specific module
53+export const createLogger = (module: string): Logger => {
54+ return LoggerService.getInstance().getLogger(module);
55+}; ⏎̸
@@ -0,0 +1,55 @@
1+import pino from 'pino';
2+
3+const logLevel = process.env.NODE_ENV === 'development' ? 'debug' : 'info';
4+
5+class LoggerService {
6+ private static instance: LoggerService;
7+ private logger: pino.Logger;
8+ private moduleLoggers: Map<string, Logger>;
9+
10+ private constructor() {
11+ this.logger = pino({
12+ transport: {
13+ target: 'pino-pretty',
14+ options: {
15+ colorize: true,
16+ translateTime: 'HH:MM:ss',
17+ ignore: 'pid,hostname',
18+ },
19+ },
20+ level: logLevel,
21+ });
22+ this.moduleLoggers = new Map();
23+ }
24+
25+ public static getInstance(): LoggerService {
26+ if (!LoggerService.instance) {
27+ LoggerService.instance = new LoggerService();
28+ }
29+ return LoggerService.instance;
30+ }
31+
32+ public getLogger(module: string): Logger {
33+ if (!this.moduleLoggers.has(module)) {
34+ this.moduleLoggers.set(module, {
35+ debug: (msg: string, obj = {}) => this.logger.debug({ module, ...obj }, msg),
36+ info: (msg: string, obj = {}) => this.logger.info({ module, ...obj }, msg),
37+ warn: (msg: string, obj = {}) => this.logger.warn({ module, ...obj }, msg),
38+ error: (msg: string, obj = {}) => this.logger.error({ module, ...obj }, msg)
39+ });
40+ }
41+ return this.moduleLoggers.get(module)!;
42+ }
43+}
44+
45+export interface Logger {
46+ debug: (msg: string, obj?: object) => void;
47+ info: (msg: string, obj?: object) => void;
48+ warn: (msg: string, obj?: object) => void;
49+ error: (msg: string, obj?: object) => void;
50+}
51+
52+// Export a function to get a logger for a specific module
53+export const createLogger = (module: string): Logger => {
54+ return LoggerService.getInstance().getLogger(module);
55+}; ⏎̸
M src/routes/api/chat/messages/+server.ts +68 -46
@@ -15,6 +15,9 @@ import { chatRooms, DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
1515 import { users } from '$lib/db/schema';
1616 import { ensureDefaultChatRoom } from '$lib/db/schema';
1717 import { messageCache } from '$lib/cache/message-cache';
18+import { createLogger } from '$lib/utils/logger.server';
19+
20+const log = createLogger('chat-server');
1821
1922 // Rate limiting configuration
2023 const INITIAL_COOLDOWN = 1000; // 1 second
@@ -54,7 +57,7 @@ async function initializeCache() {
5457 if (isCacheInitialized) return;
5558
5659 try {
57- console.log('[Chat] Initializing message cache');
60+ log.info('Initializing message cache');
5861 // Fetch messages from all rooms, limited to last 100 per room
5962 const fetchedMessages = await db.select()
6063 .from(messages)
@@ -66,12 +69,12 @@ async function initializeCache() {
6669
6770 // Log cache stats after initialization
6871 const stats = messageCache.getCacheStats();
69- console.log('[Chat] Message cache initialized successfully:', {
72+ log.info('Message cache initialized successfully', {
7073 roomCount: stats.roomCount,
7174 totalMessages: stats.totalMessages
7275 });
7376 } catch (error) {
74- console.error('[Chat] Failed to initialize message cache:', error);
77+ log.error('Failed to initialize message cache', { error });
7578 // Don't set isCacheInitialized to true on error
7679 }
7780 }
@@ -79,47 +82,44 @@ async function initializeCache() {
7982 // GET endpoint: fetch messages from cache or DB
8083 export async function GET({ request, locals }) {
8184 const url = new URL(request.url);
82- const isPublic = url.searchParams.get('public') === 'true';
83- const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
8485 const beforeTimestamp = url.searchParams.get('before');
85-
86- if (!isPublic && !locals.user) {
87- throw error(401, 'Authentication required');
88- }
86+ const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
87+ let fetchedMessages: Message[] = [];
8988
9089 try {
9190 // Ensure default chat room exists
9291 await ensureDefaultChatRoom(db);
93-
94- let fetchedMessages: Message[];
95- if (isPublic) {
96- if (!isCacheInitialized) {
97- await initializeCache();
98- }
99-
100- if (beforeTimestamp) {
101- // For public users requesting older messages, fetch from DB with limit
102- console.log('[Chat] Fetching public messages from DB before timestamp:', beforeTimestamp);
103- const query = db.select()
104- .from(messages)
105- .where(and(
106- eq(messages.chatRoomId, roomId),
107- lt(messages.timestamp, parseInt(beforeTimestamp))
108- ))
109- .orderBy(desc(messages.timestamp))
110- .limit(10);
111- fetchedMessages = (await query).reverse();
112- } else {
113- console.log('[Chat] Fetching public messages from cache', { roomId });
114- fetchedMessages = messageCache.getMessages(roomId, 10);
92+
93+ // Initialize cache if needed
94+ if (!isCacheInitialized) {
95+ await initializeCache();
96+ }
97+
98+ // Try to get messages from cache first
99+ if (!beforeTimestamp) {
100+ log.debug('Attempting to fetch messages from cache', { roomId });
101+ const cachedMessages = messageCache.getMessages(roomId);
102+ if (cachedMessages && cachedMessages.length > 0) {
103+ log.debug('Messages found in cache', {
104+ roomId,
105+ messageCount: cachedMessages.length
106+ });
107+ fetchedMessages = cachedMessages;
115108 }
116- } else {
109+ }
110+
111+ // If no cached messages or pagination request, fetch from DB
112+ if (fetchedMessages.length === 0) {
117113 if (!locals.user) {
114+ log.warn('Authentication required');
118115 throw error(401, 'Authentication required');
119116 }
120117
121118 if (beforeTimestamp) {
122- console.log('[Chat] Fetching messages from DB before timestamp:', beforeTimestamp);
119+ log.debug('Fetching messages from DB before timestamp', {
120+ roomId,
121+ beforeTimestamp
122+ });
123123 const query = db.select()
124124 .from(messages)
125125 .where(and(
@@ -130,7 +130,7 @@ export async function GET({ request, locals }) {
130130 .limit(100);
131131 fetchedMessages = (await query).reverse();
132132 } else {
133- console.log('[Chat] Fetching messages from DB for instant chat', { roomId });
133+ log.debug('Fetching messages from DB for instant chat', { roomId });
134134 const query = db.select()
135135 .from(messages)
136136 .where(eq(messages.chatRoomId, roomId))
@@ -153,7 +153,10 @@ export async function GET({ request, locals }) {
153153 }
154154 );
155155 } catch (error) {
156- console.log('[Chat] Error fetching messages:', error);
156+ log.error('Error fetching messages', {
157+ error: error instanceof Error ? error.message : 'Unknown error',
158+ roomId
159+ });
157160 const errorResponse: GetMessagesResponse = {
158161 success: false,
159162 error: 'Failed to fetch messages'
@@ -171,16 +174,20 @@ export async function GET({ request, locals }) {
171174 // POST endpoint: receive a new message, save it in the DB, and broadcast via SSE
172175 export async function POST({ request, locals }: { request: Request, locals: App.Locals }) {
173176 if (!locals.user) {
177+ log.warn('Authentication required');
174178 throw error(401, 'Authentication required');
175179 }
176180
177- console.log('[Chat] New message received');
181+ log.debug('New message received');
178182 try {
179183 // Check rate limiting
180184 const { canSend, retryAfter } = updateUserCooldown(locals.user.id);
181185 if (!canSend) {
182186 const maskedUserId = `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`;
183- console.log('[Chat] Rate limited:', { userId: maskedUserId, retryAfter });
187+ log.warn('Rate limited', {
188+ userId: maskedUserId,
189+ retryAfter
190+ });
184191 const errorResponse: SendMessageResponse = {
185192 success: false,
186193 error: 'Please wait before sending another message',
@@ -201,7 +208,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
201208
202209 const data = await request.json() as SendMessageRequest;
203210 if (!data.content || !data.userId) {
204- console.log('[Chat] Invalid payload received');
211+ log.warn('Invalid payload received', {
212+ hasContent: Boolean(data.content),
213+ hasUserId: Boolean(data.userId)
214+ });
205215 const errorResponse: SendMessageResponse = {
206216 success: false,
207217 error: 'Invalid message payload'
@@ -210,6 +220,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
210220 }
211221
212222 if (data.userId !== locals.user.id) {
223+ log.warn('Unauthorized message attempt', {
224+ requestedUserId: data.userId,
225+ actualUserId: `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`
226+ });
213227 throw error(403, 'Cannot post messages as another user');
214228 }
215229
@@ -223,7 +237,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
223237 .get();
224238
225239 if (!chatRoom) {
226- console.log('[Chat] Chat room not found:', chatRoomId);
240+ log.warn('Chat room not found', { chatRoomId });
227241 const errorResponse: SendMessageResponse = {
228242 success: false,
229243 error: 'Chat room not found'
@@ -239,7 +253,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
239253
240254 if (!user) {
241255 const maskedUserId = `${data.userId.slice(0, 4)}...${data.userId.slice(-4)}`;
242- console.log('[Chat] User not found:', maskedUserId);
256+ log.warn('User not found', { maskedUserId });
243257 const errorResponse: SendMessageResponse = {
244258 success: false,
245259 error: 'User not found'
@@ -249,7 +263,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
249263
250264 const newMessage: Message = {
251265 id: uuidv4(),
252- chatRoomId: data.chatRoomId || DEFAULT_CHAT_ROOM_ID,
266+ chatRoomId,
253267 senderId: data.userId,
254268 content: data.content,
255269 type: data.type || 'chat',
@@ -258,7 +272,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
258272
259273 // Save to DB
260274 await db.insert(messages).values(newMessage);
261- console.log('[Chat] Message saved in DB', {
275+ log.debug('Message saved in DB', {
262276 messageId: newMessage.id,
263277 chatRoomId: newMessage.chatRoomId,
264278 type: newMessage.type,
@@ -267,7 +281,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
267281
268282 // Update message cache
269283 messageCache.addMessage(newMessage);
270- console.debug('[Chat] Message added to cache', {
284+ log.debug('Message added to cache', {
271285 messageId: newMessage.id,
272286 chatRoomId: newMessage.chatRoomId
273287 });
@@ -275,6 +289,12 @@ export async function POST({ request, locals }: { request: Request, locals: App.
275289 // Broadcast the new message via the unified SSE emitter
276290 sseEmitter.emit('sse', { type: 'chatMessage', data: newMessage });
277291
292+ log.debug('Message processed successfully', {
293+ messageId: newMessage.id,
294+ userId: `${newMessage.senderId.slice(0, 4)}...${newMessage.senderId.slice(-4)}`,
295+ roomId: newMessage.chatRoomId
296+ });
297+
278298 const successResponse: SendMessageResponse = {
279299 success: true,
280300 message: newMessage
@@ -287,8 +307,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
287307 headers: { 'Content-Type': 'application/json' }
288308 }
289309 );
290- } catch {
291- console.log('[Chat] Error processing message');
310+ } catch (error) {
311+ log.error('Error processing message', {
312+ error: error instanceof Error ? error.message : 'Unknown error'
313+ });
292314 const errorResponse: SendMessageResponse = {
293315 success: false,
294316 error: 'Failed to save message'
@@ -301,4 +323,4 @@ export async function POST({ request, locals }: { request: Request, locals: App.
301323 }
302324 );
303325 }
304-} ⏎̸
326+}⏎̸
@@ -15,6 +15,9 @@ import { chatRooms, DEFAULT_CHAT_ROOM_ID } from '$lib/db/schema';
15 import { users } from '$lib/db/schema';15 import { users } from '$lib/db/schema';
16 import { ensureDefaultChatRoom } from '$lib/db/schema';16 import { ensureDefaultChatRoom } from '$lib/db/schema';
17 import { messageCache } from '$lib/cache/message-cache';17 import { messageCache } from '$lib/cache/message-cache';
18+import { createLogger } from '$lib/utils/logger.server';
19+
20+const log = createLogger('chat-server');
18 21
19 // Rate limiting configuration22 // Rate limiting configuration
20 const INITIAL_COOLDOWN = 1000; // 1 second23 const INITIAL_COOLDOWN = 1000; // 1 second
@@ -54,7 +57,7 @@ async function initializeCache() {
54 if (isCacheInitialized) return;57 if (isCacheInitialized) return;
55 58
56 try {59 try {
57- console.log('[Chat] Initializing message cache');60+ log.info('Initializing message cache');
58 // Fetch messages from all rooms, limited to last 100 per room61 // Fetch messages from all rooms, limited to last 100 per room
59 const fetchedMessages = await db.select()62 const fetchedMessages = await db.select()
60 .from(messages)63 .from(messages)
@@ -66,12 +69,12 @@ async function initializeCache() {
66 69
67 // Log cache stats after initialization70 // Log cache stats after initialization
68 const stats = messageCache.getCacheStats();71 const stats = messageCache.getCacheStats();
69- console.log('[Chat] Message cache initialized successfully:', {72+ log.info('Message cache initialized successfully', {
70 roomCount: stats.roomCount,73 roomCount: stats.roomCount,
71 totalMessages: stats.totalMessages74 totalMessages: stats.totalMessages
72 });75 });
73 } catch (error) {76 } catch (error) {
74- console.error('[Chat] Failed to initialize message cache:', error);77+ log.error('Failed to initialize message cache', { error });
75 // Don't set isCacheInitialized to true on error78 // Don't set isCacheInitialized to true on error
76 }79 }
77 }80 }
@@ -79,47 +82,44 @@ async function initializeCache() {
79 // GET endpoint: fetch messages from cache or DB82 // GET endpoint: fetch messages from cache or DB
80 export async function GET({ request, locals }) {83 export async function GET({ request, locals }) {
81 const url = new URL(request.url);84 const url = new URL(request.url);
82- const isPublic = url.searchParams.get('public') === 'true';
83- const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
84 const beforeTimestamp = url.searchParams.get('before');85 const beforeTimestamp = url.searchParams.get('before');
85- 86+ const roomId = url.searchParams.get('roomId') || DEFAULT_CHAT_ROOM_ID;
86- if (!isPublic && !locals.user) {87+ let fetchedMessages: Message[] = [];
87- throw error(401, 'Authentication required');
88- }
89 88
90 try {89 try {
91 // Ensure default chat room exists90 // Ensure default chat room exists
92 await ensureDefaultChatRoom(db);91 await ensureDefaultChatRoom(db);
93- 92+
94- let fetchedMessages: Message[];93+ // Initialize cache if needed
95- if (isPublic) {94+ if (!isCacheInitialized) {
96- if (!isCacheInitialized) {95+ await initializeCache();
97- await initializeCache();96+ }
98- }97+
99- 98+ // Try to get messages from cache first
100- if (beforeTimestamp) {99+ if (!beforeTimestamp) {
101- // For public users requesting older messages, fetch from DB with limit100+ log.debug('Attempting to fetch messages from cache', { roomId });
102- console.log('[Chat] Fetching public messages from DB before timestamp:', beforeTimestamp);101+ const cachedMessages = messageCache.getMessages(roomId);
103- const query = db.select()102+ if (cachedMessages && cachedMessages.length > 0) {
104- .from(messages)103+ log.debug('Messages found in cache', {
105- .where(and(104+ roomId,
106- eq(messages.chatRoomId, roomId),105+ messageCount: cachedMessages.length
107- lt(messages.timestamp, parseInt(beforeTimestamp))106+ });
108- ))107+ fetchedMessages = cachedMessages;
109- .orderBy(desc(messages.timestamp))
110- .limit(10);
111- fetchedMessages = (await query).reverse();
112- } else {
113- console.log('[Chat] Fetching public messages from cache', { roomId });
114- fetchedMessages = messageCache.getMessages(roomId, 10);
115 }108 }
116- } else {109+ }
110+
111+ // If no cached messages or pagination request, fetch from DB
112+ if (fetchedMessages.length === 0) {
117 if (!locals.user) {113 if (!locals.user) {
114+ log.warn('Authentication required');
118 throw error(401, 'Authentication required');115 throw error(401, 'Authentication required');
119 }116 }
120 117
121 if (beforeTimestamp) {118 if (beforeTimestamp) {
122- console.log('[Chat] Fetching messages from DB before timestamp:', beforeTimestamp);119+ log.debug('Fetching messages from DB before timestamp', {
120+ roomId,
121+ beforeTimestamp
122+ });
123 const query = db.select()123 const query = db.select()
124 .from(messages)124 .from(messages)
125 .where(and(125 .where(and(
@@ -130,7 +130,7 @@ export async function GET({ request, locals }) {
130 .limit(100);130 .limit(100);
131 fetchedMessages = (await query).reverse();131 fetchedMessages = (await query).reverse();
132 } else {132 } else {
133- console.log('[Chat] Fetching messages from DB for instant chat', { roomId });133+ log.debug('Fetching messages from DB for instant chat', { roomId });
134 const query = db.select()134 const query = db.select()
135 .from(messages)135 .from(messages)
136 .where(eq(messages.chatRoomId, roomId))136 .where(eq(messages.chatRoomId, roomId))
@@ -153,7 +153,10 @@ export async function GET({ request, locals }) {
153 }153 }
154 );154 );
155 } catch (error) {155 } catch (error) {
156- console.log('[Chat] Error fetching messages:', error);156+ log.error('Error fetching messages', {
157+ error: error instanceof Error ? error.message : 'Unknown error',
158+ roomId
159+ });
157 const errorResponse: GetMessagesResponse = {160 const errorResponse: GetMessagesResponse = {
158 success: false,161 success: false,
159 error: 'Failed to fetch messages'162 error: 'Failed to fetch messages'
@@ -171,16 +174,20 @@ export async function GET({ request, locals }) {
171 // POST endpoint: receive a new message, save it in the DB, and broadcast via SSE174 // POST endpoint: receive a new message, save it in the DB, and broadcast via SSE
172 export async function POST({ request, locals }: { request: Request, locals: App.Locals }) {175 export async function POST({ request, locals }: { request: Request, locals: App.Locals }) {
173 if (!locals.user) {176 if (!locals.user) {
177+ log.warn('Authentication required');
174 throw error(401, 'Authentication required');178 throw error(401, 'Authentication required');
175 }179 }
176 180
177- console.log('[Chat] New message received');181+ log.debug('New message received');
178 try {182 try {
179 // Check rate limiting183 // Check rate limiting
180 const { canSend, retryAfter } = updateUserCooldown(locals.user.id);184 const { canSend, retryAfter } = updateUserCooldown(locals.user.id);
181 if (!canSend) {185 if (!canSend) {
182 const maskedUserId = `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`;186 const maskedUserId = `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`;
183- console.log('[Chat] Rate limited:', { userId: maskedUserId, retryAfter });187+ log.warn('Rate limited', {
188+ userId: maskedUserId,
189+ retryAfter
190+ });
184 const errorResponse: SendMessageResponse = {191 const errorResponse: SendMessageResponse = {
185 success: false,192 success: false,
186 error: 'Please wait before sending another message',193 error: 'Please wait before sending another message',
@@ -201,7 +208,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
201 208
202 const data = await request.json() as SendMessageRequest;209 const data = await request.json() as SendMessageRequest;
203 if (!data.content || !data.userId) {210 if (!data.content || !data.userId) {
204- console.log('[Chat] Invalid payload received');211+ log.warn('Invalid payload received', {
212+ hasContent: Boolean(data.content),
213+ hasUserId: Boolean(data.userId)
214+ });
205 const errorResponse: SendMessageResponse = {215 const errorResponse: SendMessageResponse = {
206 success: false,216 success: false,
207 error: 'Invalid message payload'217 error: 'Invalid message payload'
@@ -210,6 +220,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
210 }220 }
211 221
212 if (data.userId !== locals.user.id) {222 if (data.userId !== locals.user.id) {
223+ log.warn('Unauthorized message attempt', {
224+ requestedUserId: data.userId,
225+ actualUserId: `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}`
226+ });
213 throw error(403, 'Cannot post messages as another user');227 throw error(403, 'Cannot post messages as another user');
214 }228 }
215 229
@@ -223,7 +237,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
223 .get();237 .get();
224 238
225 if (!chatRoom) {239 if (!chatRoom) {
226- console.log('[Chat] Chat room not found:', chatRoomId);240+ log.warn('Chat room not found', { chatRoomId });
227 const errorResponse: SendMessageResponse = {241 const errorResponse: SendMessageResponse = {
228 success: false,242 success: false,
229 error: 'Chat room not found'243 error: 'Chat room not found'
@@ -239,7 +253,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
239 253
240 if (!user) {254 if (!user) {
241 const maskedUserId = `${data.userId.slice(0, 4)}...${data.userId.slice(-4)}`;255 const maskedUserId = `${data.userId.slice(0, 4)}...${data.userId.slice(-4)}`;
242- console.log('[Chat] User not found:', maskedUserId);256+ log.warn('User not found', { maskedUserId });
243 const errorResponse: SendMessageResponse = {257 const errorResponse: SendMessageResponse = {
244 success: false,258 success: false,
245 error: 'User not found'259 error: 'User not found'
@@ -249,7 +263,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
249 263
250 const newMessage: Message = {264 const newMessage: Message = {
251 id: uuidv4(),265 id: uuidv4(),
252- chatRoomId: data.chatRoomId || DEFAULT_CHAT_ROOM_ID,266+ chatRoomId,
253 senderId: data.userId,267 senderId: data.userId,
254 content: data.content,268 content: data.content,
255 type: data.type || 'chat',269 type: data.type || 'chat',
@@ -258,7 +272,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
258 272
259 // Save to DB273 // Save to DB
260 await db.insert(messages).values(newMessage);274 await db.insert(messages).values(newMessage);
261- console.log('[Chat] Message saved in DB', { 275+ log.debug('Message saved in DB', {
262 messageId: newMessage.id,276 messageId: newMessage.id,
263 chatRoomId: newMessage.chatRoomId,277 chatRoomId: newMessage.chatRoomId,
264 type: newMessage.type,278 type: newMessage.type,
@@ -267,7 +281,7 @@ export async function POST({ request, locals }: { request: Request, locals: App.
267 281
268 // Update message cache282 // Update message cache
269 messageCache.addMessage(newMessage);283 messageCache.addMessage(newMessage);
270- console.debug('[Chat] Message added to cache', {284+ log.debug('Message added to cache', {
271 messageId: newMessage.id,285 messageId: newMessage.id,
272 chatRoomId: newMessage.chatRoomId286 chatRoomId: newMessage.chatRoomId
273 });287 });
@@ -275,6 +289,12 @@ export async function POST({ request, locals }: { request: Request, locals: App.
275 // Broadcast the new message via the unified SSE emitter289 // Broadcast the new message via the unified SSE emitter
276 sseEmitter.emit('sse', { type: 'chatMessage', data: newMessage });290 sseEmitter.emit('sse', { type: 'chatMessage', data: newMessage });
277 291
292+ log.debug('Message processed successfully', {
293+ messageId: newMessage.id,
294+ userId: `${newMessage.senderId.slice(0, 4)}...${newMessage.senderId.slice(-4)}`,
295+ roomId: newMessage.chatRoomId
296+ });
297+
278 const successResponse: SendMessageResponse = {298 const successResponse: SendMessageResponse = {
279 success: true,299 success: true,
280 message: newMessage300 message: newMessage
@@ -287,8 +307,10 @@ export async function POST({ request, locals }: { request: Request, locals: App.
287 headers: { 'Content-Type': 'application/json' } 307 headers: { 'Content-Type': 'application/json' }
288 }308 }
289 );309 );
290- } catch {310+ } catch (error) {
291- console.log('[Chat] Error processing message');311+ log.error('Error processing message', {
312+ error: error instanceof Error ? error.message : 'Unknown error'
313+ });
292 const errorResponse: SendMessageResponse = {314 const errorResponse: SendMessageResponse = {
293 success: false,315 success: false,
294 error: 'Failed to save message'316 error: 'Failed to save message'
@@ -301,4 +323,4 @@ export async function POST({ request, locals }: { request: Request, locals: App.
301 }323 }
302 );324 );
303 }325 }
304-} ⏎̸326+}⏎̸
M src/routes/api/rooms/[roomId]/+server.ts +10 -4
@@ -4,7 +4,9 @@ import { messages, users } from '$lib/db/schema';
44 import type { PublicRoomResponse } from '$lib/types/payloads';
55 import { error } from '@sveltejs/kit';
66 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
78
9+const log = createLogger('rooms-server');
810
911 export async function GET({ params, url, locals }): Promise<Response> {
1012 const { roomId } = params;
@@ -12,11 +14,12 @@ export async function GET({ params, url, locals }): Promise<Response> {
1214
1315 // Check authentication for non-public requests
1416 if (!isPublic && !locals.user) {
17+ log.warn('Authentication required');
1518 throw error(401, 'Authentication required');
1619 }
1720
1821 try {
19- console.log('[Rooms] Fetching room data:', {
22+ log.debug('Fetching room data', {
2023 roomId,
2124 isPublic,
2225 requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'
@@ -47,7 +50,7 @@ export async function GET({ params, url, locals }): Promise<Response> {
4750 buddyList: sanitizedUsers
4851 };
4952
50- console.log('[Rooms] Successfully fetched room data:', {
53+ log.debug('Successfully fetched room data', {
5154 roomId,
5255 userCount: sanitizedUsers.length,
5356 isPublic
@@ -57,8 +60,11 @@ export async function GET({ params, url, locals }): Promise<Response> {
5760 status: 200,
5861 headers: { 'Content-Type': 'application/json' }
5962 });
60- } catch {
61- console.log('[Rooms] Error fetching room data:', { roomId });
63+ } catch (error) {
64+ log.error('Error fetching room data', {
65+ roomId,
66+ error: error instanceof Error ? error.message : 'Unknown error'
67+ });
6268 const errorResponse: PublicRoomResponse = {
6369 success: false,
6470 error: 'Failed to fetch room data'
@@ -4,7 +4,9 @@ import { messages, users } from '$lib/db/schema';
4 import type { PublicRoomResponse } from '$lib/types/payloads';4 import type { PublicRoomResponse } from '$lib/types/payloads';
5 import { error } from '@sveltejs/kit';5 import { error } from '@sveltejs/kit';
6 import { createSafeUser } from '$lib/types/chat';6 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
7 8
9+const log = createLogger('rooms-server');
8 10
9 export async function GET({ params, url, locals }): Promise<Response> {11 export async function GET({ params, url, locals }): Promise<Response> {
10 const { roomId } = params;12 const { roomId } = params;
@@ -12,11 +14,12 @@ export async function GET({ params, url, locals }): Promise<Response> {
12 14
13 // Check authentication for non-public requests15 // Check authentication for non-public requests
14 if (!isPublic && !locals.user) {16 if (!isPublic && !locals.user) {
17+ log.warn('Authentication required');
15 throw error(401, 'Authentication required');18 throw error(401, 'Authentication required');
16 }19 }
17 20
18 try {21 try {
19- console.log('[Rooms] Fetching room data:', { 22+ log.debug('Fetching room data', {
20 roomId,23 roomId,
21 isPublic,24 isPublic,
22 requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'25 requestedBy: locals.user ? `${locals.user.id.slice(0, 4)}...${locals.user.id.slice(-4)}` : 'public'
@@ -47,7 +50,7 @@ export async function GET({ params, url, locals }): Promise<Response> {
47 buddyList: sanitizedUsers50 buddyList: sanitizedUsers
48 };51 };
49 52
50- console.log('[Rooms] Successfully fetched room data:', { 53+ log.debug('Successfully fetched room data', {
51 roomId,54 roomId,
52 userCount: sanitizedUsers.length,55 userCount: sanitizedUsers.length,
53 isPublic56 isPublic
@@ -57,8 +60,11 @@ export async function GET({ params, url, locals }): Promise<Response> {
57 status: 200,60 status: 200,
58 headers: { 'Content-Type': 'application/json' }61 headers: { 'Content-Type': 'application/json' }
59 });62 });
60- } catch {63+ } catch (error) {
61- console.log('[Rooms] Error fetching room data:', { roomId });64+ log.error('Error fetching room data', {
65+ roomId,
66+ error: error instanceof Error ? error.message : 'Unknown error'
67+ });
62 const errorResponse: PublicRoomResponse = {68 const errorResponse: PublicRoomResponse = {
63 success: false,69 success: false,
64 error: 'Failed to fetch room data'70 error: 'Failed to fetch room data'
M src/routes/api/session/login/+server.ts +28 -8
@@ -7,6 +7,9 @@ import { verifyPassword } from '$lib/utils/password';
77 import { generateSessionToken, createSession } from '$lib/api/session.server';
88 import { setSessionTokenCookie } from '$lib/api/session.cookie';
99 import { createSafeUser } from '$lib/types/chat';
10+import { createLogger } from '$lib/utils/logger.server';
11+
12+const log = createLogger('login-server');
1013
1114 // In-memory map to track failed login attempts per IP
1215 const loginAttempts = new Map<string, { count: number, lastAttempt: number }>();
@@ -25,9 +28,10 @@ setInterval(() => {
2528 }, 60 * 60 * 1000);
2629
2730 export const POST: RequestHandler = async ({ request, cookies }) => {
28- console.log("[Login] New login attempt received");
31+ log.debug('New login attempt received');
2932
3033 if (request.method !== 'POST') {
34+ log.warn('Invalid method used', { method: request.method });
3135 return new Response(JSON.stringify({ error: 'Method Not Allowed' } as LoginResponseError), { status: 405 });
3236 }
3337
@@ -40,7 +44,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
4044 // If the user has failed three or more times, apply exponential backoff
4145 if (attemptData.count >= MAX_ATTEMPTS) {
4246 const delay = Math.pow(2, attemptData.count - MAX_ATTEMPTS + 1) * 1000; // delay in milliseconds
43- console.log(`[Login] Rate limit exceeded for IP ${maskedIp} - applying ${delay}ms delay`);
47+ log.warn('Rate limit exceeded', { maskedIp, delay });
4448 await sleep(delay);
4549 }
4650
@@ -48,14 +52,17 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
4852 try {
4953 body = await request.json();
5054 } catch {
51- console.log("[Login] Invalid JSON payload received");
55+ log.warn('Invalid JSON payload received');
5256 return new Response(JSON.stringify({ error: 'Invalid JSON' } as LoginResponseError), { status: 400 });
5357 }
5458
5559 const { username, password } = body as { username: string, password: string };
5660
5761 if (typeof username !== 'string' || typeof password !== 'string') {
58- console.log("Missing or invalid input fields", { username, password });
62+ log.warn('Missing or invalid input fields', {
63+ hasUsername: typeof username === 'string',
64+ hasPassword: typeof password === 'string'
65+ });
5966 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as LoginResponseError), { status: 400 });
6067 }
6168
@@ -68,7 +75,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
6875 attemptData.count++;
6976 attemptData.lastAttempt = now;
7077 loginAttempts.set(ip, attemptData);
71- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);
78+ log.warn('Login failed - user not found', {
79+ maskedIp,
80+ attemptCount: attemptData.count,
81+ username: username.trim()
82+ });
7283 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
7384 return new Response(JSON.stringify({
7485 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
@@ -82,14 +93,18 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
8293 attemptData.count++;
8394 attemptData.lastAttempt = now;
8495 loginAttempts.set(ip, attemptData);
85- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);
96+ log.warn('Login failed - invalid password', {
97+ maskedIp,
98+ attemptCount: attemptData.count,
99+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`
100+ });
86101 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
87102 return new Response(JSON.stringify({
88103 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
89104 } as LoginResponseError), { status: 401 });
90105 }
91- } catch {
92- console.log("[Login] Error verifying password");
106+ } catch (error) {
107+ log.error('Error verifying password', { error });
93108 return new Response(JSON.stringify({ error: 'Internal Server Error' } as LoginResponseError), { status: 500 });
94109 }
95110
@@ -111,6 +126,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
111126 // Set session cookie
112127 setSessionTokenCookie({ cookies }, token, session.expiresAt);
113128
129+ log.info('Login successful', {
130+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`,
131+ expiresAt: new Date(session.expiresAt).toISOString()
132+ });
133+
114134 return new Response(JSON.stringify({
115135 success: true,
116136 user: createSafeUser(user)
@@ -7,6 +7,9 @@ import { verifyPassword } from '$lib/utils/password';
7 import { generateSessionToken, createSession } from '$lib/api/session.server';7 import { generateSessionToken, createSession } from '$lib/api/session.server';
8 import { setSessionTokenCookie } from '$lib/api/session.cookie';8 import { setSessionTokenCookie } from '$lib/api/session.cookie';
9 import { createSafeUser } from '$lib/types/chat';9 import { createSafeUser } from '$lib/types/chat';
10+import { createLogger } from '$lib/utils/logger.server';
11+
12+const log = createLogger('login-server');
10 13
11 // In-memory map to track failed login attempts per IP14 // In-memory map to track failed login attempts per IP
12 const loginAttempts = new Map<string, { count: number, lastAttempt: number }>();15 const loginAttempts = new Map<string, { count: number, lastAttempt: number }>();
@@ -25,9 +28,10 @@ setInterval(() => {
25 }, 60 * 60 * 1000);28 }, 60 * 60 * 1000);
26 29
27 export const POST: RequestHandler = async ({ request, cookies }) => {30 export const POST: RequestHandler = async ({ request, cookies }) => {
28- console.log("[Login] New login attempt received");31+ log.debug('New login attempt received');
29 32
30 if (request.method !== 'POST') {33 if (request.method !== 'POST') {
34+ log.warn('Invalid method used', { method: request.method });
31 return new Response(JSON.stringify({ error: 'Method Not Allowed' } as LoginResponseError), { status: 405 });35 return new Response(JSON.stringify({ error: 'Method Not Allowed' } as LoginResponseError), { status: 405 });
32 }36 }
33 37
@@ -40,7 +44,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
40 // If the user has failed three or more times, apply exponential backoff44 // If the user has failed three or more times, apply exponential backoff
41 if (attemptData.count >= MAX_ATTEMPTS) {45 if (attemptData.count >= MAX_ATTEMPTS) {
42 const delay = Math.pow(2, attemptData.count - MAX_ATTEMPTS + 1) * 1000; // delay in milliseconds46 const delay = Math.pow(2, attemptData.count - MAX_ATTEMPTS + 1) * 1000; // delay in milliseconds
43- console.log(`[Login] Rate limit exceeded for IP ${maskedIp} - applying ${delay}ms delay`);47+ log.warn('Rate limit exceeded', { maskedIp, delay });
44 await sleep(delay);48 await sleep(delay);
45 }49 }
46 50
@@ -48,14 +52,17 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
48 try {52 try {
49 body = await request.json();53 body = await request.json();
50 } catch {54 } catch {
51- console.log("[Login] Invalid JSON payload received");55+ log.warn('Invalid JSON payload received');
52 return new Response(JSON.stringify({ error: 'Invalid JSON' } as LoginResponseError), { status: 400 });56 return new Response(JSON.stringify({ error: 'Invalid JSON' } as LoginResponseError), { status: 400 });
53 }57 }
54 58
55 const { username, password } = body as { username: string, password: string };59 const { username, password } = body as { username: string, password: string };
56 60
57 if (typeof username !== 'string' || typeof password !== 'string') {61 if (typeof username !== 'string' || typeof password !== 'string') {
58- console.log("Missing or invalid input fields", { username, password });62+ log.warn('Missing or invalid input fields', {
63+ hasUsername: typeof username === 'string',
64+ hasPassword: typeof password === 'string'
65+ });
59 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as LoginResponseError), { status: 400 });66 return new Response(JSON.stringify({ error: 'Missing or invalid input fields' } as LoginResponseError), { status: 400 });
60 }67 }
61 68
@@ -68,7 +75,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
68 attemptData.count++;75 attemptData.count++;
69 attemptData.lastAttempt = now;76 attemptData.lastAttempt = now;
70 loginAttempts.set(ip, attemptData);77 loginAttempts.set(ip, attemptData);
71- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);78+ log.warn('Login failed - user not found', {
79+ maskedIp,
80+ attemptCount: attemptData.count,
81+ username: username.trim()
82+ });
72 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);83 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
73 return new Response(JSON.stringify({ 84 return new Response(JSON.stringify({
74 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` 85 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
@@ -82,14 +93,18 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
82 attemptData.count++;93 attemptData.count++;
83 attemptData.lastAttempt = now;94 attemptData.lastAttempt = now;
84 loginAttempts.set(ip, attemptData);95 loginAttempts.set(ip, attemptData);
85- console.log(`[Login] Login failed for IP ${maskedIp}. Attempt count: ${attemptData.count}`);96+ log.warn('Login failed - invalid password', {
97+ maskedIp,
98+ attemptCount: attemptData.count,
99+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`
100+ });
86 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);101 const remaining = Math.max(0, MAX_ATTEMPTS - attemptData.count);
87 return new Response(JSON.stringify({ 102 return new Response(JSON.stringify({
88 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}` 103 error: `Invalid username or password. ${remaining > 0 ? remaining + " attempt(s) remaining." : "Please wait before trying again."}`
89 } as LoginResponseError), { status: 401 });104 } as LoginResponseError), { status: 401 });
90 }105 }
91- } catch {106+ } catch (error) {
92- console.log("[Login] Error verifying password");107+ log.error('Error verifying password', { error });
93 return new Response(JSON.stringify({ error: 'Internal Server Error' } as LoginResponseError), { status: 500 });108 return new Response(JSON.stringify({ error: 'Internal Server Error' } as LoginResponseError), { status: 500 });
94 }109 }
95 110
@@ -111,6 +126,11 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
111 // Set session cookie126 // Set session cookie
112 setSessionTokenCookie({ cookies }, token, session.expiresAt);127 setSessionTokenCookie({ cookies }, token, session.expiresAt);
113 128
129+ log.info('Login successful', {
130+ userId: `${user.id.slice(0, 4)}...${user.id.slice(-4)}`,
131+ expiresAt: new Date(session.expiresAt).toISOString()
132+ });
133+
114 return new Response(JSON.stringify({ 134 return new Response(JSON.stringify({
115 success: true,135 success: true,
116 user: createSafeUser(user)136 user: createSafeUser(user)
M src/routes/api/session/logout/+server.ts +6 -1
@@ -1,6 +1,9 @@
11 import { json } from '@sveltejs/kit';
22 import { invalidateSession } from '$lib/api/session.server';
33 import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id
4+import { createLogger } from '$lib/utils/logger.server';
5+
6+const log = createLogger('logout-server');
47
58 export async function POST({ cookies }) {
69 const token = cookies.get('session');
@@ -9,7 +12,9 @@ export async function POST({ cookies }) {
912 const sessionId = await sha256(token);
1013 await invalidateSession(sessionId);
1114 cookies.delete('session', { path: '/' });
12- console.debug("Logged out session with id:", sessionId);
15+ log.info('Session invalidated', { sessionId: `${sessionId.slice(0, 4)}...${sessionId.slice(-4)}` });
16+ } else {
17+ log.debug('No session token found in cookies');
1318 }
1419
1520 return json({ success: true });
@@ -1,6 +1,9 @@
1 import { json } from '@sveltejs/kit';1 import { json } from '@sveltejs/kit';
2 import { invalidateSession } from '$lib/api/session.server';2 import { invalidateSession } from '$lib/api/session.server';
3 import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id3 import { sha256 } from '$lib/api/session.server'; // re-use sha256 helper to compute session id
4+import { createLogger } from '$lib/utils/logger.server';
5+
6+const log = createLogger('logout-server');
4 7
5 export async function POST({ cookies }) {8 export async function POST({ cookies }) {
6 const token = cookies.get('session');9 const token = cookies.get('session');
@@ -9,7 +12,9 @@ export async function POST({ cookies }) {
9 const sessionId = await sha256(token);12 const sessionId = await sha256(token);
10 await invalidateSession(sessionId);13 await invalidateSession(sessionId);
11 cookies.delete('session', { path: '/' });14 cookies.delete('session', { path: '/' });
12- console.debug("Logged out session with id:", sessionId);15+ log.info('Session invalidated', { sessionId: `${sessionId.slice(0, 4)}...${sessionId.slice(-4)}` });
16+ } else {
17+ log.debug('No session token found in cookies');
13 }18 }
14 19
15 return json({ success: true });20 return json({ success: true });
M src/routes/api/session/validate/+server.ts +12 -2
@@ -1,17 +1,27 @@
11 import { json } from '@sveltejs/kit';
22 import { validateSessionToken } from '$lib/api/session.server';
3+import { createLogger } from '$lib/utils/logger.server';
4+
5+const log = createLogger('validate-server');
36
47 export async function GET({ cookies }) {
58 const token = cookies.get('session');
69
710 if (!token) {
8- console.debug("No session token found in cookies.");
11+ log.debug('No session token found in cookies');
912 return json({ session: null, user: null }, { status: 401 });
1013 }
1114
1215 const result = await validateSessionToken(token);
1316
14- console.debug("Validate endpoint result:", result);
17+ if (result.session && result.user) {
18+ log.debug('Session validated', {
19+ userId: `${result.user.id.slice(0, 4)}...${result.user.id.slice(-4)}`,
20+ sessionId: `${result.session.id.slice(0, 4)}...${result.session.id.slice(-4)}`
21+ });
22+ } else {
23+ log.debug('Invalid session token');
24+ }
1525
1626 return json(result);
1727 } ⏎̸
@@ -1,17 +1,27 @@
1 import { json } from '@sveltejs/kit';1 import { json } from '@sveltejs/kit';
2 import { validateSessionToken } from '$lib/api/session.server';2 import { validateSessionToken } from '$lib/api/session.server';
3+import { createLogger } from '$lib/utils/logger.server';
4+
5+const log = createLogger('validate-server');
3 6
4 export async function GET({ cookies }) {7 export async function GET({ cookies }) {
5 const token = cookies.get('session');8 const token = cookies.get('session');
6 9
7 if (!token) {10 if (!token) {
8- console.debug("No session token found in cookies.");11+ log.debug('No session token found in cookies');
9 return json({ session: null, user: null }, { status: 401 });12 return json({ session: null, user: null }, { status: 401 });
10 }13 }
11 14
12 const result = await validateSessionToken(token);15 const result = await validateSessionToken(token);
13 16
14- console.debug("Validate endpoint result:", result);17+ if (result.session && result.user) {
18+ log.debug('Session validated', {
19+ userId: `${result.user.id.slice(0, 4)}...${result.user.id.slice(-4)}`,
20+ sessionId: `${result.session.id.slice(0, 4)}...${result.session.id.slice(-4)}`
21+ });
22+ } else {
23+ log.debug('Invalid session token');
24+ }
15 25
16 return json(result);26 return json(result);
17 } ⏎̸27 } ⏎̸
M src/routes/api/sse/+server.ts +26 -26
@@ -10,6 +10,9 @@ import db from '$lib/db/db.server';
1010 import { users } from '$lib/db/schema';
1111 import { eq, and, lt } from 'drizzle-orm';
1212 import { createSafeUser } from '$lib/types/chat';
13+import { createLogger } from '$lib/utils/logger.server';
14+
15+const log = createLogger('sse-server');
1316
1417 console.log('[SSE] Server initialized');
1518
@@ -35,7 +38,7 @@ function startTimeoutCheck() {
3538
3639 timeoutInterval = setInterval(async () => {
3740 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;
38- console.log('[SSE] Checking for timed out users', {
41+ log.debug('Checking for timed out users', {
3942 checkTime: new Date().toISOString(),
4043 timeoutThreshold: new Date(timeoutThreshold).toISOString()
4144 });
@@ -60,13 +63,13 @@ function startTimeoutCheck() {
6063 });
6164
6265 if (result.length > 0) {
63- console.log('[SSE] Found timed out users:', result.map(user => ({
64- nickname: user.nickname,
65- lastSeen: new Date(user.lastSeen || 0).toISOString()
66- })));
66+ log.info('User status updated to offline', {
67+ usersUpdated: result.length,
68+ timeoutThreshold: new Date(timeoutThreshold).toISOString()
69+ });
6770 }
6871 } catch (error) {
69- console.error('[SSE] Error during timeout check:', error);
72+ log.error('Error during timeout check', { error });
7073 }
7174 }, CHECK_INTERVAL_MS);
7275 }
@@ -81,7 +84,7 @@ function startBuddyListUpdateInterval() {
8184 const now = Date.now();
8285 // Only fetch and broadcast if 10 seconds have passed since last update
8386 if (now - lastBuddyListUpdate >= BUDDY_LIST_BROADCAST_INTERVAL) {
84- console.log('[SSE] Broadcasting buddy list update...');
87+ log.debug('Broadcasting buddy list update...');
8588 // Query the complete buddy list from the database.
8689 const buddyList = await db.select().from(users);
8790 // Use createSafeUser to properly sanitize user data
@@ -98,7 +101,7 @@ function startBuddyListUpdateInterval() {
98101 lastBuddyListUpdate = now;
99102 }
100103 } catch (error) {
101- console.error('[SSE] Error fetching buddy list for update:', error);
104+ log.error('Error fetching buddy list for update', { error });
102105 }
103106 }, 1000); // Check every second but only broadcast every 10 seconds
104107 }
@@ -108,9 +111,9 @@ if (!globalThis.__sseIntervalsStarted) {
108111 startTimeoutCheck();
109112 startBuddyListUpdateInterval();
110113 globalThis.__sseIntervalsStarted = true;
111- console.log('[SSE] Started global intervals for timeout and buddy list updates.');
114+ log.info('Started global intervals for timeout and buddy list updates');
112115 } else {
113- console.log('[SSE] Global intervals already started, skipping initialization.');
116+ log.debug('Global intervals already started, skipping initialization');
114117 }
115118
116119 // Ensure the interval is cleared if the module is reloaded
@@ -134,17 +137,14 @@ if (typeof process !== 'undefined') {
134137 });
135138 }
136139
137-export const GET: RequestHandler = async ({ request, locals, cookies }) => {
138- console.log('[SSE] Connection attempt:', {
139- hasLocals: !!locals,
140- hasUser: !!locals.user,
141- userId: locals.user?.id,
142- hasSessionCookie: !!cookies.get('session'),
143- headers: Object.fromEntries(request.headers.entries())
140+export const GET: RequestHandler = async ({ request, locals, }) => {
141+ log.debug('Processing SSE request', {
142+ url: request.url,
143+ userId: locals.user?.id
144144 });
145145
146146 if (!locals.user) {
147- console.log('[SSE] Authentication failed - no user in locals');
147+ log.warn('Authentication failed - no user in locals');
148148 return new Response('Unauthorized', {
149149 status: 401,
150150 headers: {
@@ -168,7 +168,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
168168 })
169169 .where(eq(users.id, userId));
170170
171- console.log('[SSE] User status updated to online:', {
171+ log.info('User status updated to online', {
172172 userId: `${userId.slice(0, 4)}...${userId.slice(-4)}`,
173173 timestamp: new Date(now).toISOString()
174174 });
@@ -182,7 +182,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
182182 }
183183 });
184184 } catch (error) {
185- console.error('[SSE] Error updating user status:', error);
185+ log.error('Error updating user status', { error });
186186 }
187187
188188 const stream = new ReadableStream({
@@ -193,10 +193,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
193193 `data: ${JSON.stringify(event.data)}\n\n`;
194194 controller.enqueue(encoder.encode(payload));
195195 if (!['userStatusUpdate', 'chatMessage'].includes(event.type)) {
196- console.log('[SSE] Event sent:', { type: event.type });
196+ log.info('Event sent', { type: event.type });
197197 }
198- } catch {
199- console.log('[SSE] Error sending event');
198+ } catch (error) {
199+ log.error('Error sending event', { error });
200200 }
201201 };
202202
@@ -208,10 +208,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
208208 request.signal.addEventListener('abort', () => {
209209 if (onSSE) {
210210 sseEmitter.removeListener('sse', onSSE);
211- console.log('[SSE] Removed listener due to abort signal.');
211+ log.info('Removed listener due to abort signal');
212212 }
213213 });
214- console.log('[SSE] Added new listener');
214+ log.info('Added new listener');
215215
216216 controller.enqueue(encoder.encode('data: Connected\n\n'));
217217
@@ -223,7 +223,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
223223 if (keepAliveInterval) clearInterval(keepAliveInterval);
224224 if (onSSE) sseEmitter.removeListener('sse', onSSE);
225225 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
226- console.log('[SSE] Connection closed:', { userId: maskedUserId, reason });
226+ log.info('Connection closed', { userId: maskedUserId, reason });
227227
228228 const now = Date.now();
229229 await db.update(users)
@@ -10,6 +10,9 @@ import db from '$lib/db/db.server';
10 import { users } from '$lib/db/schema';10 import { users } from '$lib/db/schema';
11 import { eq, and, lt } from 'drizzle-orm';11 import { eq, and, lt } from 'drizzle-orm';
12 import { createSafeUser } from '$lib/types/chat';12 import { createSafeUser } from '$lib/types/chat';
13+import { createLogger } from '$lib/utils/logger.server';
14+
15+const log = createLogger('sse-server');
13 16
14 console.log('[SSE] Server initialized');17 console.log('[SSE] Server initialized');
15 18
@@ -35,7 +38,7 @@ function startTimeoutCheck() {
35 38
36 timeoutInterval = setInterval(async () => {39 timeoutInterval = setInterval(async () => {
37 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;40 const timeoutThreshold = Date.now() - ONLINE_TIMEOUT_MS;
38- console.log('[SSE] Checking for timed out users', { 41+ log.debug('Checking for timed out users', {
39 checkTime: new Date().toISOString(),42 checkTime: new Date().toISOString(),
40 timeoutThreshold: new Date(timeoutThreshold).toISOString()43 timeoutThreshold: new Date(timeoutThreshold).toISOString()
41 });44 });
@@ -60,13 +63,13 @@ function startTimeoutCheck() {
60 });63 });
61 64
62 if (result.length > 0) {65 if (result.length > 0) {
63- console.log('[SSE] Found timed out users:', result.map(user => ({66+ log.info('User status updated to offline', {
64- nickname: user.nickname,67+ usersUpdated: result.length,
65- lastSeen: new Date(user.lastSeen || 0).toISOString()68+ timeoutThreshold: new Date(timeoutThreshold).toISOString()
66- })));69+ });
67 }70 }
68 } catch (error) {71 } catch (error) {
69- console.error('[SSE] Error during timeout check:', error);72+ log.error('Error during timeout check', { error });
70 }73 }
71 }, CHECK_INTERVAL_MS);74 }, CHECK_INTERVAL_MS);
72 }75 }
@@ -81,7 +84,7 @@ function startBuddyListUpdateInterval() {
81 const now = Date.now();84 const now = Date.now();
82 // Only fetch and broadcast if 10 seconds have passed since last update85 // Only fetch and broadcast if 10 seconds have passed since last update
83 if (now - lastBuddyListUpdate >= BUDDY_LIST_BROADCAST_INTERVAL) {86 if (now - lastBuddyListUpdate >= BUDDY_LIST_BROADCAST_INTERVAL) {
84- console.log('[SSE] Broadcasting buddy list update...');87+ log.debug('Broadcasting buddy list update...');
85 // Query the complete buddy list from the database.88 // Query the complete buddy list from the database.
86 const buddyList = await db.select().from(users);89 const buddyList = await db.select().from(users);
87 // Use createSafeUser to properly sanitize user data90 // Use createSafeUser to properly sanitize user data
@@ -98,7 +101,7 @@ function startBuddyListUpdateInterval() {
98 lastBuddyListUpdate = now;101 lastBuddyListUpdate = now;
99 }102 }
100 } catch (error) {103 } catch (error) {
101- console.error('[SSE] Error fetching buddy list for update:', error);104+ log.error('Error fetching buddy list for update', { error });
102 }105 }
103 }, 1000); // Check every second but only broadcast every 10 seconds106 }, 1000); // Check every second but only broadcast every 10 seconds
104 }107 }
@@ -108,9 +111,9 @@ if (!globalThis.__sseIntervalsStarted) {
108 startTimeoutCheck();111 startTimeoutCheck();
109 startBuddyListUpdateInterval();112 startBuddyListUpdateInterval();
110 globalThis.__sseIntervalsStarted = true;113 globalThis.__sseIntervalsStarted = true;
111- console.log('[SSE] Started global intervals for timeout and buddy list updates.');114+ log.info('Started global intervals for timeout and buddy list updates');
112 } else {115 } else {
113- console.log('[SSE] Global intervals already started, skipping initialization.');116+ log.debug('Global intervals already started, skipping initialization');
114 }117 }
115 118
116 // Ensure the interval is cleared if the module is reloaded119 // Ensure the interval is cleared if the module is reloaded
@@ -134,17 +137,14 @@ if (typeof process !== 'undefined') {
134 });137 });
135 }138 }
136 139
137-export const GET: RequestHandler = async ({ request, locals, cookies }) => {140+export const GET: RequestHandler = async ({ request, locals, }) => {
138- console.log('[SSE] Connection attempt:', {141+ log.debug('Processing SSE request', {
139- hasLocals: !!locals,142+ url: request.url,
140- hasUser: !!locals.user,143+ userId: locals.user?.id
141- userId: locals.user?.id,
142- hasSessionCookie: !!cookies.get('session'),
143- headers: Object.fromEntries(request.headers.entries())
144 });144 });
145 145
146 if (!locals.user) {146 if (!locals.user) {
147- console.log('[SSE] Authentication failed - no user in locals');147+ log.warn('Authentication failed - no user in locals');
148 return new Response('Unauthorized', { 148 return new Response('Unauthorized', {
149 status: 401,149 status: 401,
150 headers: {150 headers: {
@@ -168,7 +168,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
168 })168 })
169 .where(eq(users.id, userId));169 .where(eq(users.id, userId));
170 170
171- console.log('[SSE] User status updated to online:', {171+ log.info('User status updated to online', {
172 userId: `${userId.slice(0, 4)}...${userId.slice(-4)}`,172 userId: `${userId.slice(0, 4)}...${userId.slice(-4)}`,
173 timestamp: new Date(now).toISOString()173 timestamp: new Date(now).toISOString()
174 });174 });
@@ -182,7 +182,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
182 }182 }
183 });183 });
184 } catch (error) {184 } catch (error) {
185- console.error('[SSE] Error updating user status:', error);185+ log.error('Error updating user status', { error });
186 }186 }
187 187
188 const stream = new ReadableStream({188 const stream = new ReadableStream({
@@ -193,10 +193,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
193 `data: ${JSON.stringify(event.data)}\n\n`;193 `data: ${JSON.stringify(event.data)}\n\n`;
194 controller.enqueue(encoder.encode(payload));194 controller.enqueue(encoder.encode(payload));
195 if (!['userStatusUpdate', 'chatMessage'].includes(event.type)) {195 if (!['userStatusUpdate', 'chatMessage'].includes(event.type)) {
196- console.log('[SSE] Event sent:', { type: event.type });196+ log.info('Event sent', { type: event.type });
197 }197 }
198- } catch {198+ } catch (error) {
199- console.log('[SSE] Error sending event');199+ log.error('Error sending event', { error });
200 }200 }
201 };201 };
202 202
@@ -208,10 +208,10 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
208 request.signal.addEventListener('abort', () => {208 request.signal.addEventListener('abort', () => {
209 if (onSSE) {209 if (onSSE) {
210 sseEmitter.removeListener('sse', onSSE);210 sseEmitter.removeListener('sse', onSSE);
211- console.log('[SSE] Removed listener due to abort signal.');211+ log.info('Removed listener due to abort signal');
212 }212 }
213 });213 });
214- console.log('[SSE] Added new listener');214+ log.info('Added new listener');
215 215
216 controller.enqueue(encoder.encode('data: Connected\n\n'));216 controller.enqueue(encoder.encode('data: Connected\n\n'));
217 217
@@ -223,7 +223,7 @@ export const GET: RequestHandler = async ({ request, locals, cookies }) => {
223 if (keepAliveInterval) clearInterval(keepAliveInterval);223 if (keepAliveInterval) clearInterval(keepAliveInterval);
224 if (onSSE) sseEmitter.removeListener('sse', onSSE);224 if (onSSE) sseEmitter.removeListener('sse', onSSE);
225 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;225 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
226- console.log('[SSE] Connection closed:', { userId: maskedUserId, reason });226+ log.info('Connection closed', { userId: maskedUserId, reason });
227 227
228 const now = Date.now();228 const now = Date.now();
229 await db.update(users)229 await db.update(users)
M src/routes/api/status/+server.ts +11 -3
@@ -5,21 +5,26 @@ import { eq } from 'drizzle-orm';
55 import { validateSessionToken, generateSessionToken, createSession } from '$lib/api/session.server';
66 import { setSessionTokenCookie } from '$lib/api/session.cookie';
77 import { createSafeUser } from '$lib/types/chat';
8+import { createLogger } from '$lib/utils/logger.server';
9+
10+const log = createLogger('status-server');
811
912 export async function POST({ request, cookies, locals }) {
1013 if (!locals.user) {
14+ log.warn('Authentication required');
1115 throw error(401, 'Authentication required');
1216 }
1317
1418 const { status } = await request.json();
1519 const validStatuses = ['online', 'away', 'busy', 'offline'];
1620 if (!validStatuses.includes(status)) {
21+ log.warn('Invalid status received', { status });
1722 throw error(400, 'Invalid status');
1823 }
1924
2025 const userId = locals.user.id;
2126 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
22- console.log('[Status] Updating user status:', { userId: maskedUserId, status });
27+ log.debug('Updating user status', { userId: maskedUserId, status });
2328
2429 const now = Date.now();
2530
@@ -33,7 +38,7 @@ export async function POST({ request, cookies, locals }) {
3338 .where(eq(users.id, userId))
3439 .returning()
3540 .get();
36- console.log('[Status] Database updated successfully');
41+ log.debug('Database updated successfully', { userId: maskedUserId, status });
3742
3843 // Renew session if status is online and session is near expiry
3944 if (status === 'online') {
@@ -47,7 +52,10 @@ export async function POST({ request, cookies, locals }) {
4752 const newToken = generateSessionToken();
4853 const newSession = await createSession(newToken, userId);
4954 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);
50- console.log('[Status] Session renewed:', { userId: maskedUserId, expiresAt: new Date(newSession.expiresAt).toISOString() });
55+ log.info('Session renewed', {
56+ userId: maskedUserId,
57+ expiresAt: new Date(newSession.expiresAt).toISOString()
58+ });
5159 }
5260 }
5361 }
@@ -5,21 +5,26 @@ import { eq } from 'drizzle-orm';
5 import { validateSessionToken, generateSessionToken, createSession } from '$lib/api/session.server';5 import { validateSessionToken, generateSessionToken, createSession } from '$lib/api/session.server';
6 import { setSessionTokenCookie } from '$lib/api/session.cookie';6 import { setSessionTokenCookie } from '$lib/api/session.cookie';
7 import { createSafeUser } from '$lib/types/chat';7 import { createSafeUser } from '$lib/types/chat';
8+import { createLogger } from '$lib/utils/logger.server';
9+
10+const log = createLogger('status-server');
8 11
9 export async function POST({ request, cookies, locals }) {12 export async function POST({ request, cookies, locals }) {
10 if (!locals.user) {13 if (!locals.user) {
14+ log.warn('Authentication required');
11 throw error(401, 'Authentication required');15 throw error(401, 'Authentication required');
12 }16 }
13 17
14 const { status } = await request.json();18 const { status } = await request.json();
15 const validStatuses = ['online', 'away', 'busy', 'offline'];19 const validStatuses = ['online', 'away', 'busy', 'offline'];
16 if (!validStatuses.includes(status)) {20 if (!validStatuses.includes(status)) {
21+ log.warn('Invalid status received', { status });
17 throw error(400, 'Invalid status');22 throw error(400, 'Invalid status');
18 }23 }
19 24
20 const userId = locals.user.id;25 const userId = locals.user.id;
21 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;26 const maskedUserId = `${userId.slice(0, 4)}...${userId.slice(-4)}`;
22- console.log('[Status] Updating user status:', { userId: maskedUserId, status });27+ log.debug('Updating user status', { userId: maskedUserId, status });
23 28
24 const now = Date.now();29 const now = Date.now();
25 30
@@ -33,7 +38,7 @@ export async function POST({ request, cookies, locals }) {
33 .where(eq(users.id, userId))38 .where(eq(users.id, userId))
34 .returning()39 .returning()
35 .get();40 .get();
36- console.log('[Status] Database updated successfully');41+ log.debug('Database updated successfully', { userId: maskedUserId, status });
37 42
38 // Renew session if status is online and session is near expiry43 // Renew session if status is online and session is near expiry
39 if (status === 'online') {44 if (status === 'online') {
@@ -47,7 +52,10 @@ export async function POST({ request, cookies, locals }) {
47 const newToken = generateSessionToken();52 const newToken = generateSessionToken();
48 const newSession = await createSession(newToken, userId);53 const newSession = await createSession(newToken, userId);
49 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);54 setSessionTokenCookie({ cookies }, newToken, newSession.expiresAt);
50- console.log('[Status] Session renewed:', { userId: maskedUserId, expiresAt: new Date(newSession.expiresAt).toISOString() });55+ log.info('Session renewed', {
56+ userId: maskedUserId,
57+ expiresAt: new Date(newSession.expiresAt).toISOString()
58+ });
51 }59 }
52 }60 }
53 }61 }
M src/routes/api/users/[userId]/+server.ts +12 -5
@@ -4,10 +4,13 @@ import { users } from '$lib/db/schema';
44 import { eq } from 'drizzle-orm';
55 import type { RequestHandler } from './$types';
66 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
8+
9+const log = createLogger('users-server');
710
811 export const GET: RequestHandler = async ({ params }) => {
912 const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;
10- console.log('[Users] Fetching user data:', { userId: maskedUserId });
13+ log.debug('Fetching user data', { userId: maskedUserId });
1114
1215 try {
1316 const user = await db.select()
@@ -16,23 +19,27 @@ export const GET: RequestHandler = async ({ params }) => {
1619 .get();
1720
1821 if (!user) {
19- console.log('[Users] User not found:', { userId: maskedUserId });
22+ log.warn('User not found', { userId: maskedUserId });
2023 throw error(404, 'User not found');
2124 }
2225
2326 const safeUser = createSafeUser(user);
2427
25- console.log('[Users] User data retrieved:', {
28+ log.debug('User data retrieved', {
2629 userId: maskedUserId,
2730 status: safeUser.status,
2831 lastSeen: safeUser.lastSeen ? new Date(safeUser.lastSeen).toISOString() : null
2932 });
33+
3034 return json({
3135 success: true,
3236 user: safeUser
3337 });
34- } catch {
35- console.log('[Users] Error fetching user data:', { userId: maskedUserId });
38+ } catch (err) {
39+ log.error('Error fetching user data', {
40+ userId: maskedUserId,
41+ error: err instanceof Error ? err.message : 'Unknown error'
42+ });
3643 throw error(500, 'Failed to fetch user information');
3744 }
3845 };⏎̸
@@ -4,10 +4,13 @@ import { users } from '$lib/db/schema';
4 import { eq } from 'drizzle-orm';4 import { eq } from 'drizzle-orm';
5 import type { RequestHandler } from './$types';5 import type { RequestHandler } from './$types';
6 import { createSafeUser } from '$lib/types/chat';6 import { createSafeUser } from '$lib/types/chat';
7+import { createLogger } from '$lib/utils/logger.server';
8+
9+const log = createLogger('users-server');
7 10
8 export const GET: RequestHandler = async ({ params }) => {11 export const GET: RequestHandler = async ({ params }) => {
9 const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;12 const maskedUserId = `${params.userId.slice(0, 4)}...${params.userId.slice(-4)}`;
10- console.log('[Users] Fetching user data:', { userId: maskedUserId });13+ log.debug('Fetching user data', { userId: maskedUserId });
11 14
12 try {15 try {
13 const user = await db.select()16 const user = await db.select()
@@ -16,23 +19,27 @@ export const GET: RequestHandler = async ({ params }) => {
16 .get();19 .get();
17 20
18 if (!user) {21 if (!user) {
19- console.log('[Users] User not found:', { userId: maskedUserId });22+ log.warn('User not found', { userId: maskedUserId });
20 throw error(404, 'User not found');23 throw error(404, 'User not found');
21 }24 }
22 25
23 const safeUser = createSafeUser(user);26 const safeUser = createSafeUser(user);
24 27
25- console.log('[Users] User data retrieved:', { 28+ log.debug('User data retrieved', {
26 userId: maskedUserId,29 userId: maskedUserId,
27 status: safeUser.status,30 status: safeUser.status,
28 lastSeen: safeUser.lastSeen ? new Date(safeUser.lastSeen).toISOString() : null31 lastSeen: safeUser.lastSeen ? new Date(safeUser.lastSeen).toISOString() : null
29 });32 });
33+
30 return json({34 return json({
31 success: true,35 success: true,
32 user: safeUser36 user: safeUser
33 });37 });
34- } catch {38+ } catch (err) {
35- console.log('[Users] Error fetching user data:', { userId: maskedUserId });39+ log.error('Error fetching user data', {
40+ userId: maskedUserId,
41+ error: err instanceof Error ? err.message : 'Unknown error'
42+ });
36 throw error(500, 'Failed to fetch user information');43 throw error(500, 'Failed to fetch user information');
37 }44 }
38 };⏎̸45 };⏎̸
M vite.config.ts +23 -3
@@ -2,6 +2,19 @@ import { defineConfig } from 'vitest/config';
22 import { loadEnv } from 'vite';
33 import { sveltekit } from '@sveltejs/kit/vite';
44
5+/**
6+ * Logging configuration
7+ * This project uses two logging systems:
8+ * 1. Console logging (development/client-side) - controlled by console stripping
9+ * 2. Pino logging (server-side) - controlled by LOG_LEVEL
10+ *
11+ * LOG_LEVEL affects both systems:
12+ * - error: only error logs
13+ * - warn: warn and error logs
14+ * - info: info, warn, and error logs (default in production)
15+ * - debug: all logs (default in development)
16+ */
17+
518 // Define which console methods to strip based on LOG_LEVEL
619 const getConsoleFuncsToStrip = (logLevel: string) => {
720 switch (logLevel?.toLowerCase()) {
@@ -18,9 +31,11 @@ const getConsoleFuncsToStrip = (logLevel: string) => {
1831 };
1932
2033 export default defineConfig(({ mode }) => {
21- // Load env file based on `mode` value. mode can be 'development', 'production', etc.
34+ // Load env file based on `mode` value
2235 const env = loadEnv(mode, process.cwd(), '');
23- console.log(`Building with LOG_LEVEL=${env.LOG_LEVEL || 'info'} in ${mode} mode`);
36+ const logLevel = env.LOG_LEVEL || (mode === 'development' ? 'debug' : 'info');
37+
38+ console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);
2439
2540 return {
2641 plugins: [sveltekit()],
@@ -31,9 +46,14 @@ export default defineConfig(({ mode }) => {
3146 minify: 'terser',
3247 terserOptions: {
3348 compress: {
34- pure_funcs: getConsoleFuncsToStrip(env.LOG_LEVEL || 'info')
49+ // Strip console methods based on LOG_LEVEL
50+ pure_funcs: getConsoleFuncsToStrip(logLevel)
3551 }
3652 },
53+ },
54+ // Make LOG_LEVEL available to client-side code
55+ define: {
56+ 'process.env.LOG_LEVEL': JSON.stringify(logLevel)
3757 }
3858 };
3959 });
@@ -2,6 +2,19 @@ import { defineConfig } from 'vitest/config';
2 import { loadEnv } from 'vite';2 import { loadEnv } from 'vite';
3 import { sveltekit } from '@sveltejs/kit/vite';3 import { sveltekit } from '@sveltejs/kit/vite';
4 4
5+/**
6+ * Logging configuration
7+ * This project uses two logging systems:
8+ * 1. Console logging (development/client-side) - controlled by console stripping
9+ * 2. Pino logging (server-side) - controlled by LOG_LEVEL
10+ *
11+ * LOG_LEVEL affects both systems:
12+ * - error: only error logs
13+ * - warn: warn and error logs
14+ * - info: info, warn, and error logs (default in production)
15+ * - debug: all logs (default in development)
16+ */
17+
5 // Define which console methods to strip based on LOG_LEVEL18 // Define which console methods to strip based on LOG_LEVEL
6 const getConsoleFuncsToStrip = (logLevel: string) => {19 const getConsoleFuncsToStrip = (logLevel: string) => {
7 switch (logLevel?.toLowerCase()) {20 switch (logLevel?.toLowerCase()) {
@@ -18,9 +31,11 @@ const getConsoleFuncsToStrip = (logLevel: string) => {
18 };31 };
19 32
20 export default defineConfig(({ mode }) => {33 export default defineConfig(({ mode }) => {
21- // Load env file based on `mode` value. mode can be 'development', 'production', etc.34+ // Load env file based on `mode` value
22 const env = loadEnv(mode, process.cwd(), '');35 const env = loadEnv(mode, process.cwd(), '');
23- console.log(`Building with LOG_LEVEL=${env.LOG_LEVEL || 'info'} in ${mode} mode`);36+ const logLevel = env.LOG_LEVEL || (mode === 'development' ? 'debug' : 'info');
37+
38+ console.log(`Building with LOG_LEVEL=${logLevel} in ${mode} mode`);
24 39
25 return {40 return {
26 plugins: [sveltekit()],41 plugins: [sveltekit()],
@@ -31,9 +46,14 @@ export default defineConfig(({ mode }) => {
31 minify: 'terser',46 minify: 'terser',
32 terserOptions: {47 terserOptions: {
33 compress: {48 compress: {
34- pure_funcs: getConsoleFuncsToStrip(env.LOG_LEVEL || 'info')49+ // Strip console methods based on LOG_LEVEL
50+ pure_funcs: getConsoleFuncsToStrip(logLevel)
35 }51 }
36 },52 },
53+ },
54+ // Make LOG_LEVEL available to client-side code
55+ define: {
56+ 'process.env.LOG_LEVEL': JSON.stringify(logLevel)
37 }57 }
38 };58 };
39 });59 });

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close