diff --git a/package.json b/package.json index 4ff0ac777..cec19062f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/plugins/1.migrate.ts b/plugins/1.migrate.ts index c1861ed02..132d76cf6 100644 --- a/plugins/1.migrate.ts +++ b/plugins/1.migrate.ts @@ -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(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2e5db7c1..1dead2524 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/integrations/email/service.ts b/src/integrations/email/service.ts index ce8c46db3..bc5b4b03a 100644 --- a/src/integrations/email/service.ts +++ b/src/integrations/email/service.ts @@ -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", + ); } }; diff --git a/src/integrations/orpc/client.ts b/src/integrations/orpc/client.ts index 5c9928469..e44edff5c 100644 --- a/src/integrations/orpc/client.ts +++ b/src/integrations/orpc/client.ts @@ -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 => { 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); }), ], }); diff --git a/src/integrations/orpc/services/ai.ts b/src/integrations/orpc/services/ai.ts index 45b4fa0be..05e33c180 100644 --- a/src/integrations/orpc/services/ai.ts +++ b/src/integrations/orpc/services/ai.ts @@ -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, S extends Record { - logger.error("OpenAPI handler error", { - route: "/api/openapi", - error, - }); + logServerError("OpenAPI handler error", error, { route: "/api/openapi" }); }), ], }); diff --git a/src/routes/api/rpc.$.ts b/src/routes/api/rpc.$.ts index 5f4541a63..cc523950b 100644 --- a/src/routes/api/rpc.$.ts +++ b/src/routes/api/rpc.$.ts @@ -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" }); }), ], }); diff --git a/src/routes/mcp/index.ts b/src/routes/mcp/index.ts index 937fce63f..2a7f08962 100644 --- a/src/routes/mcp/index.ts +++ b/src/routes/mcp/index.ts @@ -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, diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 083163451..3083b2df8 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,73 +1,71 @@ -type LogLevel = "debug" | "info" | "warn" | "error"; +import pino from "pino"; -type LogContext = Record & { - 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 = {}): 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), -};