cleaner logging in dev and prod

This commit is contained in:
Amruth Pillai
2026-03-19 12:55:37 +01:00
parent 7858efbd2b
commit bbc17b8995
12 changed files with 234 additions and 114 deletions
+2
View File
@@ -98,6 +98,7 @@
"motion": "^12.38.0",
"nodemailer": "^8.0.3",
"pg": "^8.20.0",
"pino": "^10.3.1",
"puppeteer-core": "^24.39.1",
"qrcode.react": "^4.2.0",
"react": "^19.2.4",
@@ -147,6 +148,7 @@
"node-addon-api": "^8.6.0",
"node-gyp": "^12.2.0",
"npm-check-updates": "^19.6.5",
"pino-pretty": "^13.1.3",
"tsx": "^4.21.0",
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
"vite-plugin-pwa": "^1.2.0",
+6 -3
View File
@@ -2,9 +2,12 @@ import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { definePlugin } from "nitro";
import { Pool } from "pg";
import pino from "pino";
const log = pino({ name: "migrate" });
async function migrateDatabase() {
console.log("Running database migrations...");
log.info("Running database migrations...");
const connectionString = process.env.DATABASE_URL;
@@ -17,9 +20,9 @@ async function migrateDatabase() {
try {
await migrate(db, { migrationsFolder: "./migrations" });
console.log("Database migrations completed");
log.info("Database migrations completed");
} catch (error) {
console.error("🚨 Database migrations failed:", error);
log.error({ err: error }, "Database migrations failed");
throw error;
} finally {
await pool.end();
+146
View File
@@ -214,6 +214,9 @@ importers:
pg:
specifier: ^8.20.0
version: 8.20.0
pino:
specifier: ^10.3.1
version: 10.3.1
puppeteer-core:
specifier: ^24.39.1
version: 24.39.1
@@ -356,6 +359,9 @@ importers:
npm-check-updates:
specifier: ^19.6.5
version: 19.6.5
pino-pretty:
specifier: ^13.1.3
version: 13.1.3
tsx:
specifier: ^4.21.0
version: 4.21.0
@@ -2639,6 +2645,9 @@ packages:
'@phosphor-icons/web@2.1.2':
resolution: {integrity: sha512-rPAR9o/bEcp4Cw4DEeZHXf+nlGCMNGkNDRizYHM47NLxz9vvEHp/Tt6FMK1NcWadzw/pFDPnRBGi/ofRya958A==}
'@pinojs/redact@0.4.0':
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
@@ -4179,6 +4188,10 @@ packages:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'}
atomic-sleep@1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -4555,6 +4568,9 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
colors@1.0.3:
resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==}
engines: {node: '>=0.1.90'}
@@ -4708,6 +4724,9 @@ packages:
date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
dateformat@4.6.3:
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
db0@0.3.4:
resolution: {integrity: sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==}
peerDependencies:
@@ -5190,6 +5209,9 @@ packages:
resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
engines: {node: '>=8.0.0'}
fast-copy@4.0.2:
resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -5210,6 +5232,9 @@ packages:
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
@@ -5476,6 +5501,9 @@ packages:
headers-polyfill@4.0.3:
resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==}
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
hono@4.12.5:
resolution: {integrity: sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==}
engines: {node: '>=16.9.0'}
@@ -5817,6 +5845,10 @@ packages:
jose@6.1.3:
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
@@ -6431,6 +6463,10 @@ packages:
ollama@0.6.3:
resolution: {integrity: sha512-KEWEhIqE5wtfzEIZbDCLH51VFZ6Z3ZSa6sIOg/E/tBV8S51flyqBOXi+bRxlOYKDf8i327zG9eSTb8IJxvm3Zg==}
on-exit-leak-free@2.1.2:
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
engines: {node: '>=14.0.0'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -6628,6 +6664,20 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
pino-abstract-transport@3.0.0:
resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
pino-pretty@13.1.3:
resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==}
hasBin: true
pino-std-serializers@7.1.0:
resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
pino@10.3.1:
resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
hasBin: true
pixelmatch@7.1.0:
resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==}
hasBin: true
@@ -6723,6 +6773,9 @@ packages:
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
process-warning@5.0.0:
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
@@ -6842,6 +6895,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
radash@12.1.1:
resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==}
engines: {node: '>=14.18.0'}
@@ -6953,6 +7009,10 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
recast@0.23.11:
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
engines: {node: '>= 4'}
@@ -7086,6 +7146,10 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -7103,6 +7167,9 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
secure-json-parse@4.1.0:
resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==}
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -7224,6 +7291,9 @@ packages:
resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
sonic-boom@4.2.1:
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
@@ -7447,6 +7517,10 @@ packages:
text-decoder@1.2.7:
resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==}
thread-stream@4.0.0:
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
engines: {node: '>=20'}
threads@1.7.0:
resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==}
@@ -10691,6 +10765,8 @@ snapshots:
'@phosphor-icons/web@2.1.2': {}
'@pinojs/redact@0.4.0': {}
'@polka/url@1.0.0-next.29': {}
'@prisma/client-runtime-utils@7.4.2':
@@ -12320,6 +12396,8 @@ snapshots:
at-least-node@1.0.0: {}
atomic-sleep@1.0.0: {}
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
@@ -12735,6 +12813,8 @@ snapshots:
color-name@1.1.4: {}
colorette@2.0.20: {}
colors@1.0.3: {}
commander@10.0.1: {}
@@ -12867,6 +12947,8 @@ snapshots:
date-fns@3.6.0: {}
dateformat@4.6.3: {}
db0@0.3.4(@electric-sql/pglite@0.3.15)(drizzle-orm@1.0.0-beta.18-7eb39f0(@electric-sql/pglite@0.3.15)(@opentelemetry/api@1.9.0)(@types/mssql@9.1.9(@azure/core-client@1.10.1))(@types/pg@8.18.0)(mssql@11.0.1(@azure/core-client@1.10.1))(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.7)(valibot@1.2.0(typescript@5.9.3))(zod@4.3.6))(mysql2@3.15.3):
optionalDependencies:
'@electric-sql/pglite': 0.3.15
@@ -13349,6 +13431,8 @@ snapshots:
pure-rand: 6.1.0
optional: true
fast-copy@4.0.2: {}
fast-deep-equal@3.1.3: {}
fast-equals@5.4.0: {}
@@ -13367,6 +13451,8 @@ snapshots:
fast-json-stable-stringify@2.1.0: {}
fast-safe-stringify@2.1.1: {}
fast-uri@3.1.0: {}
fast-xml-builder@1.0.0: {}
@@ -13638,6 +13724,8 @@ snapshots:
headers-polyfill@4.0.3: {}
help-me@5.0.0: {}
hono@4.12.5: {}
hookable@6.1.0: {}
@@ -13934,6 +14022,8 @@ snapshots:
jose@6.1.3: {}
joycon@3.1.1: {}
js-cookie@3.0.5: {}
js-md4@0.3.2: {}
@@ -14535,6 +14625,8 @@ snapshots:
dependencies:
whatwg-fetch: 3.6.20
on-exit-leak-free@2.1.2: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -14807,6 +14899,42 @@ snapshots:
picomatch@4.0.3: {}
pino-abstract-transport@3.0.0:
dependencies:
split2: 4.2.0
pino-pretty@13.1.3:
dependencies:
colorette: 2.0.20
dateformat: 4.6.3
fast-copy: 4.0.2
fast-safe-stringify: 2.1.1
help-me: 5.0.0
joycon: 3.1.1
minimist: 1.2.8
on-exit-leak-free: 2.1.2
pino-abstract-transport: 3.0.0
pump: 3.0.4
secure-json-parse: 4.1.0
sonic-boom: 4.2.1
strip-json-comments: 5.0.3
pino-std-serializers@7.1.0: {}
pino@10.3.1:
dependencies:
'@pinojs/redact': 0.4.0
atomic-sleep: 1.0.0
on-exit-leak-free: 2.1.2
pino-abstract-transport: 3.0.0
pino-std-serializers: 7.1.0
process-warning: 5.0.0
quick-format-unescaped: 4.0.4
real-require: 0.2.0
safe-stable-stringify: 2.5.0
sonic-boom: 4.2.1
thread-stream: 4.0.0
pixelmatch@7.1.0:
dependencies:
pngjs: 7.0.0
@@ -14889,6 +15017,8 @@ snapshots:
process-nextick-args@2.0.1: {}
process-warning@5.0.0: {}
process@0.11.10: {}
progress@2.0.3: {}
@@ -15071,6 +15201,8 @@ snapshots:
queue-microtask@1.2.3: {}
quick-format-unescaped@4.0.4: {}
radash@12.1.1: {}
range-parser@1.2.1: {}
@@ -15183,6 +15315,8 @@ snapshots:
readdirp@4.1.2:
optional: true
real-require@0.2.0: {}
recast@0.23.11:
dependencies:
ast-types: 0.16.1
@@ -15345,6 +15479,8 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {}
samlify@2.11.0:
@@ -15366,6 +15502,8 @@ snapshots:
scheduler@0.27.0: {}
secure-json-parse@4.1.0: {}
semver@6.3.1: {}
semver@7.7.4: {}
@@ -15573,6 +15711,10 @@ snapshots:
ip-address: 10.1.0
smart-buffer: 4.2.0
sonic-boom@4.2.1:
dependencies:
atomic-sleep: 1.0.0
sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
dependencies:
react: 19.2.4
@@ -15848,6 +15990,10 @@ snapshots:
transitivePeerDependencies:
- react-native-b4a
thread-stream@4.0.0:
dependencies:
real-require: 0.2.0
threads@1.7.0:
dependencies:
callsites: 3.1.0
+5 -12
View File
@@ -45,23 +45,16 @@ export const sendEmail = async (options: SendEmailOptions) => {
};
if (!transport) {
logger.info("SMTP not configured; skipping email send", {
to: payload.to,
subject: payload.subject,
});
logger.info({ to: payload.to, subject: payload.subject }, "SMTP not configured; skipping email send");
return;
}
try {
await transport.sendMail({ ...options, from });
} catch (error) {
logger.error("SMTP send failed", {
smtpHost: env.SMTP_HOST,
smtpPort: env.SMTP_PORT,
smtpSecure: env.SMTP_SECURE,
to: payload.to,
subject: payload.subject,
error,
});
logger.error(
{ err: error, smtpHost: env.SMTP_HOST, smtpPort: env.SMTP_PORT, to: payload.to, subject: payload.subject },
"SMTP send failed",
);
}
};
+3 -9
View File
@@ -8,17 +8,14 @@ import { getRequestHeaders } from "@tanstack/react-start/server";
import router from "@/integrations/orpc/router";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
import { logServerError } from "@/utils/logger";
export const getORPCClient = createIsomorphicFn()
.server((): RouterClient<typeof router> => {
return createRouterClient(router, {
interceptors: [
onError((error) => {
logger.error("oRPC server client error", {
scope: "server",
error,
});
logServerError("oRPC server client error", error, { scope: "server" });
}),
],
context: async () => {
@@ -45,10 +42,7 @@ export const getORPCClient = createIsomorphicFn()
interceptors: [
onError((error) => {
if (error instanceof DOMException && error.name === "AbortError") return;
logger.error("oRPC browser client error", {
scope: "client",
error,
});
console.warn("[oRPC client]", error instanceof Error ? error.message : error);
}),
],
});
+5 -4
View File
@@ -33,6 +33,7 @@ import {
} from "@/integrations/ai/tools/patch-resume";
import { defaultResumeData, resumeDataSchema } from "@/schema/resume/data";
import { type TailorOutput, tailorOutputSchema } from "@/schema/tailor";
import { logger } from "@/utils/logger";
import { isObject } from "@/utils/sanitize";
const aiExtractionTemplate = {
@@ -183,10 +184,10 @@ function mergeDefaults<T extends Record<string, unknown>, S extends Record<strin
function logAndRethrow(context: string, error: unknown): never {
if (error instanceof Error) {
console.error(`${context}:`, error.message);
logger.error({ err: error }, context);
throw error;
}
console.error(`Unknown error in ${context}:`, error);
logger.error({ err: error }, `Unknown error in ${context}`);
throw new Error(`An unknown error occurred during ${context}.`);
}
@@ -223,11 +224,11 @@ function parseAndValidateResumeJson(resultText: string): ResumeData {
});
} catch (error: unknown) {
if (error instanceof ZodError) {
console.error("Zod Validation Errors:", JSON.stringify(flattenError(error), null, 2));
logger.error({ err: flattenError(error) }, "Zod validation failed during resume parsing");
throw error;
}
console.error("Unknown error:", error);
logger.error({ err: error }, "Unknown error during resume data validation");
throw new Error("An unknown error occurred while validating the merged resume data.");
}
}
+1 -4
View File
@@ -43,10 +43,7 @@ export const authService = {
try {
await db.delete(schema.user).where(eq(schema.user.id, input.userId));
} catch (err) {
logger.error("Failed to delete user record", {
userId: input.userId,
error: err,
});
logger.error({ err, userId: input.userId }, "Failed to delete user record");
throw new ORPCError("INTERNAL_SERVER_ERROR");
}
+1 -6
View File
@@ -72,12 +72,7 @@ async function healthHandler() {
};
if (status === "unhealthy") {
logger.warn("Healthcheck failed", {
route: "/api/health",
database,
printer,
storage,
});
logger.warn({ route: "/api/health", database, printer, storage }, "Healthcheck failed");
}
const headers = new Headers();
+2 -5
View File
@@ -10,7 +10,7 @@ import router from "@/integrations/orpc/router";
import { resumeDataSchema } from "@/schema/resume/data";
import { env } from "@/utils/env";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
import { logServerError } from "@/utils/logger";
const openAPIHandler = new OpenAPIHandler(router, {
plugins: [
@@ -23,10 +23,7 @@ const openAPIHandler = new OpenAPIHandler(router, {
],
interceptors: [
onError((error) => {
logger.error("OpenAPI handler error", {
route: "/api/openapi",
error,
});
logServerError("OpenAPI handler error", error, { route: "/api/openapi" });
}),
],
});
+2 -5
View File
@@ -5,16 +5,13 @@ import { createFileRoute } from "@tanstack/react-router";
import router from "@/integrations/orpc/router";
import { getLocale } from "@/utils/locale";
import { logger } from "@/utils/logger";
import { logServerError } from "@/utils/logger";
const rpcHandler = new RPCHandler(router, {
plugins: [new BatchHandlerPlugin(), new RequestHeadersPlugin(), new StrictGetMethodPlugin()],
interceptors: [
onError((error) => {
logger.error("oRPC server error", {
route: "/api/rpc",
error,
});
logServerError("oRPC server error", error, { route: "/api/rpc" });
}),
],
});
+1 -4
View File
@@ -54,10 +54,7 @@ export const Route = createFileRoute("/mcp/")({
return await transport.handleRequest(request);
} catch (error) {
logger.error("MCP request failed", {
route: "/mcp",
error,
});
logger.error({ err: error, route: "/mcp" }, "MCP request failed");
return Response.json({
id: null,
+60 -62
View File
@@ -1,73 +1,71 @@
type LogLevel = "debug" | "info" | "warn" | "error";
import pino from "pino";
type LogContext = Record<string, unknown> & {
error?: unknown;
};
const isDev = process.env.NODE_ENV !== "production";
const level = process.env.LOG_LEVEL ?? (isDev ? "debug" : "info");
type SerializedError = {
name: string;
message: string;
stack?: string;
};
export const logger = pino({
level,
timestamp: pino.stdTimeFunctions.isoTime,
formatters: {
level: (label) => ({ level: label }),
},
redact: {
paths: [
"apiKey",
"password",
"secret",
"token",
"authorization",
"*.apiKey",
"*.password",
"*.secret",
"*.token",
"error.stack",
],
censor: "[REDACTED]",
},
...(isDev && {
transport: {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "HH:MM:ss.l",
ignore: "pid,hostname",
},
},
}),
});
function serializeError(error: unknown): SerializedError | undefined {
if (!error) return undefined;
const EXPECTED_ERROR_PATTERNS = [
"Unauthorized",
"Not Found",
"UNAUTHORIZED",
"NOT_FOUND",
"State mismatch",
"User not found",
"Credential account not found",
"Invalid API key",
] as const;
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack,
};
}
if (typeof error === "string") {
return {
name: "Error",
message: error,
};
}
if (typeof error === "object") {
return {
name: "Error",
message: JSON.stringify(error),
};
}
return {
name: "Error",
message: String(error as string),
};
export function isExpectedError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return EXPECTED_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
}
function log(level: LogLevel, message: string, context: LogContext = {}) {
const { error, ...rest } = context;
const payload = {
ts: new Date().toISOString(),
level,
message,
...rest,
error: serializeError(error),
};
const json = JSON.stringify(payload);
export function getErrorSummary(error: unknown): { name: string; message: string } {
if (error instanceof Error) return { name: error.name, message: error.message };
if (typeof error === "string") return { name: "Error", message: error };
if (typeof error === "object" && error !== null) return { name: "Error", message: JSON.stringify(error) };
return { name: "Error", message: String(error) };
}
if (level === "error") {
console.error(json);
export function logServerError(context: string, error: unknown, extra: Record<string, unknown> = {}): void {
const summary = getErrorSummary(error);
if (isExpectedError(error)) {
logger.warn({ err: summary, ...extra }, context);
return;
}
if (level === "warn") {
console.warn(json);
return;
}
console.log(json);
logger.error({ err: error instanceof Error ? error : summary, ...extra }, context);
}
export const logger = {
debug: (message: string, context?: LogContext) => log("debug", message, context),
info: (message: string, context?: LogContext) => log("info", message, context),
warn: (message: string, context?: LogContext) => log("warn", message, context),
error: (message: string, context?: LogContext) => log("error", message, context),
};