From 2af1ac329ab7aa67a06a5f602fdb1866784015a5 Mon Sep 17 00:00:00 2001 From: priosshrsth Date: Wed, 22 Jul 2026 15:39:11 +0000 Subject: [PATCH] feat(logger): serverless-safe pretty logger + err serializers Add `err`/`error` serializers to the default logger options so Error objects log message + stack instead of `{}` in serverless runtimes. Add `createPrettyLogger()` which runs pino-pretty as a synchronous stream (no worker thread), making it safe on Vercel/Cloudflare. pino-pretty is dynamically imported and declared as an optional peer dependency so plain-JSON consumers never bundle it. Co-Authored-By: Claude Opus 4.8 (1M context) --- .claude/CLAUDE.md | 18 +++++++++--------- CLAUDE.md | 18 +++++++++--------- README.md | 12 ++++++++++-- bun.lock | 31 +++++++++++++++++++++++++++++++ package.json | 5 +++++ src/logger/index.ts | 36 +++++++++++++++++++++++++++++++++--- 6 files changed, 97 insertions(+), 23 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index c8b31d8..9536b55 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -37,15 +37,15 @@ Always keep docs in sync with the code. Do not defer documentation to a later st ### Entry Points -| Export | Purpose | -| ------------------------ | -------------------------------------------------------------------------------------- | -| `assembly-kit` | `createAssemblyKit()`, error classes, token utilities, `paginate()` | -| `assembly-kit/schemas` | All Zod schemas and inferred types (no client dependency) | -| `assembly-kit/client` | `createAssemblyKit`, `AssemblyKit`, `AssemblyKitOptions` | -| `assembly-kit/errors` | All error classes | -| `assembly-kit/token` | `AssemblyToken`, `createToken` | -| `assembly-kit/logger` | `createLogger`, `logger` (requires pino peer dep) | -| `assembly-kit/bridge-ui` | React hooks wrapping app-bridge (`usePrimaryCta`, `useSecondaryCta`, `useActionsMenu`) | +| Export | Purpose | +| ------------------------ | --------------------------------------------------------------------------------------------- | +| `assembly-kit` | `createAssemblyKit()`, error classes, token utilities, `paginate()` | +| `assembly-kit/schemas` | All Zod schemas and inferred types (no client dependency) | +| `assembly-kit/client` | `createAssemblyKit`, `AssemblyKit`, `AssemblyKitOptions` | +| `assembly-kit/errors` | All error classes | +| `assembly-kit/token` | `AssemblyToken`, `createToken` | +| `assembly-kit/logger` | `createLogger`, `createPrettyLogger`, `logger` (pino peer dep; pretty also needs pino-pretty) | +| `assembly-kit/bridge-ui` | React hooks wrapping app-bridge (`usePrimaryCta`, `useSecondaryCta`, `useActionsMenu`) | ### Source Layer Dependency Order diff --git a/CLAUDE.md b/CLAUDE.md index fbc9dd5..5f7d572 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -194,12 +194,12 @@ export const getAssemblyKit = cache((apiKey: string, workspaceId: string) => ## Entry Points -| Import path | Key exports | -| ------------------------ | --------------------------------------------------------------------------------------------------------- | -| `assembly-kit` | `createAssemblyKit`, `AssemblyKit`, errors, schemas, token utils | -| `assembly-kit/schemas` | All Zod schemas and inferred types | -| `assembly-kit/client` | `createAssemblyKit`, `AssemblyKit`, `AssemblyKitOptions` | -| `assembly-kit/errors` | All error classes | -| `assembly-kit/token` | `AssemblyToken`, `createToken` | -| `assembly-kit/logger` | `createLogger`, `logger` (requires pino peer dep) | -| `assembly-kit/bridge-ui` | `usePrimaryCta`, `useSecondaryCta`, `useActionsMenu` (requires react + @assembly-js/app-bridge peer deps) | +| Import path | Key exports | +| ------------------------ | -------------------------------------------------------------------------------------------------------------------- | +| `assembly-kit` | `createAssemblyKit`, `AssemblyKit`, errors, schemas, token utils | +| `assembly-kit/schemas` | All Zod schemas and inferred types | +| `assembly-kit/client` | `createAssemblyKit`, `AssemblyKit`, `AssemblyKitOptions` | +| `assembly-kit/errors` | All error classes | +| `assembly-kit/token` | `AssemblyToken`, `createToken` | +| `assembly-kit/logger` | `createLogger`, `createPrettyLogger`, `logger` (requires pino peer dep; `createPrettyLogger` also needs pino-pretty) | +| `assembly-kit/bridge-ui` | `usePrimaryCta`, `useSecondaryCta`, `useActionsMenu` (requires react + @assembly-js/app-bridge peer deps) | diff --git a/README.md b/README.md index 67f304e..ef96329 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,15 @@ import { logger } from "assembly-kit/logger"; logger.info("starting up"); ``` -The logger emits plain JSON in all environments (no worker-thread transport, so it works in serverless runtimes like Vercel). Pipe output through the `pino-pretty` CLI locally if you want human-readable logs. The default log level respects the `LOG_LEVEL` environment variable. +The logger emits plain JSON in all environments (no worker-thread transport, so it works in serverless runtimes like Vercel). `Error` objects are serialized with Pino's `err` serializer so message and stack survive — no more `{}` in Vercel logs. The default log level respects the `LOG_LEVEL` environment variable. + +For human-readable local output, use `createPrettyLogger`. It runs `pino-pretty` as a **synchronous stream** (no worker thread), so it is serverless-safe, but gate it to local runs to keep production plain JSON. Install `pino-pretty` (optional peer dependency) to use it: + +```typescript +import { createLogger, createPrettyLogger } from "assembly-kit/logger"; + +const log = process.env.NODE_ENV === "development" ? await createPrettyLogger() : createLogger(); +``` ### App Bridge (React Hooks) @@ -498,7 +506,7 @@ useActionsMenu([{ label: "Archive", onClick: () => archive() }], hasItems); | `assembly-kit/errors` | All error classes | | `assembly-kit/schemas` | All Zod schemas and inferred types (no client dependency) | | `assembly-kit/token` | `AssemblyToken`, `createToken`, `ClientTokenPayload`, `InternalUserTokenPayload` | -| `assembly-kit/logger` | `createLogger`, `logger` | +| `assembly-kit/logger` | `createLogger`, `createPrettyLogger`, `logger` | | `assembly-kit/bridge-ui` | `usePrimaryCta`, `useSecondaryCta`, `useActionsMenu` | ## Development diff --git a/bun.lock b/bun.lock index 56e5a7c..734bda3 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,7 @@ "@typescript/native-preview": "7.0.0-dev.20260505.1", "bumpp": "^11.0.1", "pino": "^10.3.1", + "pino-pretty": "^13.1.2", "react": "^19.2.5", "typescript": "^6.0.3", "vite-plus": "^0.1.20", @@ -23,6 +24,7 @@ "peerDependencies": { "@assembly-js/app-bridge": "^1.1.1", "pino": ">=9", + "pino-pretty": ">=11", "react": ">=18", "typescript": ">=5", "zod": ">=4", @@ -30,6 +32,7 @@ "optionalPeers": [ "@assembly-js/app-bridge", "pino", + "pino-pretty", "react", "typescript", "zod", @@ -235,20 +238,34 @@ "cac": ["cac@7.0.0", "", {}, "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ=="], + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], + "defu": ["defu@6.1.6", "", {}, "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + "fast-copy": ["fast-copy@4.0.4", "", {}, "sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA=="], + + "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "ky": ["ky@2.0.2", "", {}, "sha512-/GmXpo9F9W+f8n4Ivr2iH+7h7wL7jLbLKWkMlpflcCRb6kGjBfTlASEXaZ9qUgNTn4VgS0P2pwxxzQ4EM6Ulgg=="], @@ -277,6 +294,8 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -285,6 +304,8 @@ "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "oxfmt": ["oxfmt@0.46.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.46.0", "@oxfmt/binding-android-arm64": "0.46.0", "@oxfmt/binding-darwin-arm64": "0.46.0", "@oxfmt/binding-darwin-x64": "0.46.0", "@oxfmt/binding-freebsd-x64": "0.46.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.46.0", "@oxfmt/binding-linux-arm-musleabihf": "0.46.0", "@oxfmt/binding-linux-arm64-gnu": "0.46.0", "@oxfmt/binding-linux-arm64-musl": "0.46.0", "@oxfmt/binding-linux-ppc64-gnu": "0.46.0", "@oxfmt/binding-linux-riscv64-gnu": "0.46.0", "@oxfmt/binding-linux-riscv64-musl": "0.46.0", "@oxfmt/binding-linux-s390x-gnu": "0.46.0", "@oxfmt/binding-linux-x64-gnu": "0.46.0", "@oxfmt/binding-linux-x64-musl": "0.46.0", "@oxfmt/binding-openharmony-arm64": "0.46.0", "@oxfmt/binding-win32-arm64-msvc": "0.46.0", "@oxfmt/binding-win32-ia32-msvc": "0.46.0", "@oxfmt/binding-win32-x64-msvc": "0.46.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-CopwJOwPAjZ9p76fCvz+mSOJTw9/NY3cSksZK3VO/bUQ8UoEcketNgUuYS0UB3p+R9XnXe7wGGXUmyFxc7QxJA=="], "oxlint": ["oxlint@1.61.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.61.0", "@oxlint/binding-android-arm64": "1.61.0", "@oxlint/binding-darwin-arm64": "1.61.0", "@oxlint/binding-darwin-x64": "1.61.0", "@oxlint/binding-freebsd-x64": "1.61.0", "@oxlint/binding-linux-arm-gnueabihf": "1.61.0", "@oxlint/binding-linux-arm-musleabihf": "1.61.0", "@oxlint/binding-linux-arm64-gnu": "1.61.0", "@oxlint/binding-linux-arm64-musl": "1.61.0", "@oxlint/binding-linux-ppc64-gnu": "1.61.0", "@oxlint/binding-linux-riscv64-gnu": "1.61.0", "@oxlint/binding-linux-riscv64-musl": "1.61.0", "@oxlint/binding-linux-s390x-gnu": "1.61.0", "@oxlint/binding-linux-x64-gnu": "1.61.0", "@oxlint/binding-linux-x64-musl": "1.61.0", "@oxlint/binding-openharmony-arm64": "1.61.0", "@oxlint/binding-win32-arm64-msvc": "1.61.0", "@oxlint/binding-win32-ia32-msvc": "1.61.0", "@oxlint/binding-win32-x64-msvc": "1.61.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.18.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-ZC0ALuhDZ6ivOFG+sy0D0pEDN49EvsId98zVlmYdkcXHsEM14m/qTNUEsUpiFiCVbpIxYtVBmmLE87nsbUHohQ=="], @@ -303,6 +324,8 @@ "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + "pino-pretty": ["pino-pretty@13.1.3", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pump": "^3.0.0", "secure-json-parse": "^4.0.0", "sonic-boom": "^4.0.1", "strip-json-comments": "^5.0.2" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg=="], + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], "pixelmatch": ["pixelmatch@7.1.0", "", { "dependencies": { "pngjs": "^7.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng=="], @@ -313,6 +336,8 @@ "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="], "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], @@ -325,6 +350,8 @@ "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], @@ -337,6 +364,8 @@ "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], @@ -363,6 +392,8 @@ "vite-plus": ["vite-plus@0.1.20", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@voidzero-dev/vite-plus-core": "0.1.20", "@voidzero-dev/vite-plus-test": "0.1.20", "oxfmt": "=0.46.0", "oxlint": "=1.61.0", "oxlint-tsgolint": "=0.22.0" }, "optionalDependencies": { "@voidzero-dev/vite-plus-darwin-arm64": "0.1.20", "@voidzero-dev/vite-plus-darwin-x64": "0.1.20", "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.1.20", "@voidzero-dev/vite-plus-linux-arm64-musl": "0.1.20", "@voidzero-dev/vite-plus-linux-x64-gnu": "0.1.20", "@voidzero-dev/vite-plus-linux-x64-musl": "0.1.20", "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.1.20", "@voidzero-dev/vite-plus-win32-x64-msvc": "0.1.20" }, "bin": { "vp": "bin/vp", "oxfmt": "bin/oxfmt", "oxlint": "bin/oxlint" } }, "sha512-hxJqXTxiiFhszwAeD0MvKlztVuXE4TztTdJ64BPxGqgY67F0PDa5eZkUsrN91Ae8aYUMfweW6V/J57OUO9/0zw=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], diff --git a/package.json b/package.json index 4352fdc..11439ae 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@typescript/native-preview": "7.0.0-dev.20260505.1", "bumpp": "^11.0.1", "pino": "^10.3.1", + "pino-pretty": "^13.1.2", "react": "^19.2.5", "typescript": "^6.0.3", "vite-plus": "^0.1.20", @@ -57,6 +58,7 @@ "peerDependencies": { "@assembly-js/app-bridge": "^1.1.1", "pino": ">=9", + "pino-pretty": ">=11", "react": ">=18", "typescript": ">=5", "zod": ">=4" @@ -68,6 +70,9 @@ "pino": { "optional": true }, + "pino-pretty": { + "optional": true + }, "react": { "optional": true }, diff --git a/src/logger/index.ts b/src/logger/index.ts index 8f8c423..9ad821c 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -5,11 +5,17 @@ import type { Logger, LoggerOptions } from "pino"; export type { Logger, LoggerOptions } from "pino"; -// pino-pretty uses a worker-thread transport that breaks in serverless -// runtimes (Vercel, Cloudflare). Emit plain JSON everywhere; pipe through -// the pino-pretty CLI locally if you want pretty output. +// pino-pretty's default transport uses a worker thread that breaks in +// serverless runtimes (Vercel, Cloudflare). Emit plain JSON everywhere. +// The `err`/`error` serializers ensure Error objects log message + stack +// instead of `{}` — the usual serverless footgun. For pretty local output +// use `createPrettyLogger` below (sync stream, no worker thread). const DEFAULT_OPTIONS: LoggerOptions = { level: process.env.LOG_LEVEL ?? "info", + serializers: { + err: pino.stdSerializers.err, + error: pino.stdSerializers.err, + }, }; const buildLogger = (options: LoggerOptions = {}): Logger => @@ -46,3 +52,27 @@ export const createLogger = (options: LoggerOptions = {}): Logger => { /** A default global logger instance for convenience. */ export const logger: Logger = buildLogger(); + +/** + * Create a logger with human-readable colorized output for local development. + * + * Uses pino-pretty as a **synchronous stream** (`sync: true`) rather than the + * default worker-thread transport, so it is safe on serverless runtimes that + * forbid worker threads. Requires `pino-pretty` to be installed (optional peer + * dependency) — it is imported dynamically so plain-JSON users never bundle it. + * + * Gate it to local runs so production stays plain JSON: + * + * @example + * ```ts + * const log = + * process.env.NODE_ENV === "development" + * ? await createPrettyLogger() + * : createLogger(); + * ``` + */ +export const createPrettyLogger = async (options: LoggerOptions = {}): Promise => { + const { default: pretty } = await import("pino-pretty"); + const stream = pretty({ colorize: true, sync: true }); + return pino({ ...DEFAULT_OPTIONS, ...options }, stream); +};