feat(logging): 引入 LogTape 替换 console.* 为结构化日志

为什么选 LogTape(2026 实测):
- pino 在 bun build --compile 编译产物里因 worker_threads + 动态 require 在
  /\$bunfs/ 虚拟文件系统中崩溃,与单二进制部署核心目标冲突;
- LogTape 零依赖(5.3KB)、零 worker、纯 ESM、原生 Bun 导出条件,runtime
  agnostic,配合 configureSync 完美兼容 --bytecode 模式(无裸 top-level await);
- 一等公民集成:@logtape/drizzle-orm(SQL 查询日志)、@logtape/otel(后续
  OpenTelemetry sink 留扩展点)。

变更:
- src/server/logger.ts: configureSync 引导 + getLogger 重导出。format 默认
  process.stdout.isTTY ? pretty : json,可经 LOG_FORMAT 显式覆盖(绕开 Bun
  bundler 把 process.env.NODE_ENV 在 --minify 时 inline 成字面量的特殊处理)。
- src/server/api/interceptors.ts: logError 改用 getLogger(['api']).error(...) +
  结构化 properties,弃 logger.error 顶层 API。
- src/cli/migrate.ts: 所有 console.log 改走 getLogger(['cli','migrate']),logger
  在 run() 内 lazy-import 以保持 citty subcommand 模块体 side-effect-free。
- src/server/db/index.ts: env.LOG_DB=true 时挂 DrizzleLogger 适配器,SQL 查询
  按类别 ['db'] 在 debug 级输出(含 query/params/formattedQuery 三字段)。

新增 env 旋钮(t3-oss 校验):
- LOG_LEVEL: trace|debug|info|warning|error|fatal,默认 info
- LOG_FORMAT: pretty|json,默认 TTY 自动选
- LOG_DB: stringbool,默认 false

端到端验证(compose + Postgres 18-alpine):
- TTY 终端:pretty 输出含  图标 + ANSI 彩色 + 类别·路径 ✓
- 管道/Docker:JSON Lines 一行一条,含 @timestamp/level/logger/properties ✓
- LOG_FORMAT=pretty 强制覆盖 ✓
- ./server migrate 应用 migration 并经 logger 输出 ✓
- ./server serve + RPC round-trip:interceptor logError 与 drizzle SQL 日志
  在生产 JSON 模式下结构化输出 ✓
- fix / typecheck / test 3/3 / build / compile 117M 二进制全绿
This commit is contained in:
2026-04-25 16:04:31 +08:00
parent d206a3315f
commit cc3a5dc5ad
9 changed files with 109 additions and 48 deletions
+41 -32
View File
@@ -5,40 +5,43 @@
"": {
"name": "fullstack-starter",
"dependencies": {
"@orpc/client": "latest",
"@orpc/contract": "latest",
"@orpc/openapi": "latest",
"@orpc/server": "latest",
"@orpc/tanstack-query": "latest",
"@orpc/zod": "latest",
"@t3-oss/env-core": "latest",
"@tanstack/react-query": "latest",
"@tanstack/react-router": "latest",
"@tanstack/react-router-ssr-query": "latest",
"@tanstack/react-start": "latest",
"citty": "latest",
"drizzle-orm": "latest",
"drizzle-zod": "latest",
"postgres": "latest",
"react": "latest",
"react-dom": "latest",
"uuid": "latest",
"zod": "latest",
"@logtape/drizzle-orm": "^2.0.5",
"@logtape/logtape": "^2.0.5",
"@logtape/pretty": "^2.0.5",
"@orpc/client": "^1.14.0",
"@orpc/contract": "^1.14.0",
"@orpc/openapi": "^1.14.0",
"@orpc/server": "^1.14.0",
"@orpc/tanstack-query": "^1.14.0",
"@orpc/zod": "^1.14.0",
"@t3-oss/env-core": "^0.13.11",
"@tanstack/react-query": "^5.100.1",
"@tanstack/react-router": "^1.168.24",
"@tanstack/react-router-ssr-query": "^1.166.11",
"@tanstack/react-start": "^1.167.48",
"citty": "^0.2.2",
"drizzle-orm": "0.45.2",
"drizzle-zod": "^0.8.3",
"postgres": "^3.4.9",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"uuid": "^14.0.0",
"zod": "^4.3.6",
},
"devDependencies": {
"@biomejs/biome": "latest",
"@tailwindcss/vite": "latest",
"@tanstack/devtools-vite": "latest",
"@tanstack/react-devtools": "latest",
"@tanstack/react-query-devtools": "latest",
"@tanstack/react-router-devtools": "latest",
"@types/bun": "latest",
"@vitejs/plugin-react": "latest",
"drizzle-kit": "latest",
"nitro": "npm:nitro-nightly@latest",
"tailwindcss": "latest",
"typescript": "latest",
"vite": "latest",
"@biomejs/biome": "^2.4.13",
"@tailwindcss/vite": "^4.2.4",
"@tanstack/devtools-vite": "^0.6.0",
"@tanstack/react-devtools": "^0.10.2",
"@tanstack/react-query-devtools": "^5.100.1",
"@tanstack/react-router-devtools": "^1.166.13",
"@types/bun": "^1.3.13",
"@vitejs/plugin-react": "^6.0.1",
"drizzle-kit": "0.31.10",
"nitro": "npm:nitro-nightly@3.0.1-20260424-182106-f8cf6ccc",
"tailwindcss": "^4.2.4",
"typescript": "^6.0.3",
"vite": "^8.0.10",
},
},
},
@@ -173,6 +176,12 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@logtape/drizzle-orm": ["@logtape/drizzle-orm@2.0.5", "", { "peerDependencies": { "@logtape/logtape": "^2.0.5" } }, "sha512-woM4x9J7B4XzoQTY1bzR6uJVlqezn6oY6flV+rDD6lsuwP2llNVvptgCOGjRutDye+6WZldSUIGe7UFK/faBzA=="],
"@logtape/logtape": ["@logtape/logtape@2.0.5", "", {}, "sha512-UizDkh20ZPJVOddRxG1F77WhHdlNl/sbQgoO8T534R7XvUBMAJ9En9f35u+meW2tRsNLvjz6R87Zanwf53tspQ=="],
"@logtape/pretty": ["@logtape/pretty@2.0.5", "", { "peerDependencies": { "@logtape/logtape": "^2.0.5" } }, "sha512-jU5pYL0CW0tFmxBS5umMF5VEMq1vXLvkqKrj7KRHnSlb5SrBOSCYl0w4q7FmPPFVADmgTmzVVr6IcIWwK/2Nig=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
"@oozcitak/dom": ["@oozcitak/dom@2.0.2", "", { "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/url": "^3.0.0", "@oozcitak/util": "^10.0.0" } }, "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w=="],