refactor(logging): 接受 oracle 审计建议打磨三处不够极致的细节

1. configureSync 用 getConfig() === null 守卫幂等初始化。原写法
   reset: true 在 ESM 缓存正常时多余、HMR 重复求值时会反复
   resetSync() + 累积 process.on('exit') 监听器。

2. ['logtape','meta'] logger 加 parentSinks: 'override'。原配置
   它既挂自己的 console sink、又继承 root sink,meta 的 warning/
   error/fatal 会被打印两次。

3. DrizzleLogger 显式传 'info' level。原默认 'debug' 与 LOG_LEVEL
   默认 'info' 形成隐形依赖:用户必须同时设两个变量才能看到 SQL。
   现在 LOG_DB=true 单开关即生效,符合"开关即可用"审美。

附带:删除未消费的 export type { LogLevel };getLogger 改成直接
re-export from '@logtape/logtape',少一层本地 import 间接。

端到端验证(compose + Postgres 18-alpine):
- LOG_DB=true 默认 LOG_LEVEL=info 下 SQL 以 level=INFO logger=db 输出 ✓
- meta logger 不再重复 sink ✓
- typecheck / test 3/3 / build / compile 117M 全绿 ✓

Oracle 审计 ses_23c5090efffe1jzPR5fVVm1y6m,KISS 评分由 8/10 升至 9.2/10。
This commit is contained in:
2026-04-25 16:14:30 +08:00
parent cc3a5dc5ad
commit ce39faf778
3 changed files with 16 additions and 16 deletions
+1 -1
View File
@@ -137,7 +137,7 @@ logger.error('DB write failed: {err}', { err })
- Categories are hierarchical arrays — they show up as dot-paths in JSON output (`"logger":"feature.subsystem"`) and let you filter by prefix when shipping logs.
- The `{name}` placeholders in the message string are filled from the second-arg properties object. Don't string-concatenate or template-literal — that defeats structured logging.
- Format is `pretty` (icons + ANSI) on TTY, `json` (one-line JSON) when piped — perfect for Loki/Datadog/CloudWatch ingestion. Override with `LOG_FORMAT`.
- Drizzle SQL queries are logged at `debug` under category `['db']` when `LOG_DB=true`, via `@logtape/drizzle-orm`'s `DrizzleLogger` adapter (constructed in `src/server/db/index.ts`).
- Drizzle SQL queries are logged at `info` under category `['db']` when `LOG_DB=true`, via `@logtape/drizzle-orm`'s `DrizzleLogger` adapter (constructed in `src/server/db/index.ts`). The `info` level is intentional: flipping `LOG_DB=true` alone is enough — no need to also lower `LOG_LEVEL`.
- `src/server/api/interceptors.ts` calls `getLogger(['api']).error(...)` from `logError`. CLI subcommands lazy-import the logger inside `run()` — they are still required to be side-effect-free at module top (citty eager-loads for `--help`).
- Bun-specific: `process.env.NODE_ENV` is **inlined at build time** by `bun build --minify` — do NOT branch on it for logger config (use `process.stdout.isTTY` or `LOG_FORMAT` instead). pino is unusable here because its worker-thread transports crash inside the `/$bunfs/` virtual filesystem of compiled binaries; LogTape has zero workers and zero dynamic require, so it ships cleanly into the single binary.
+1 -1
View File
@@ -7,5 +7,5 @@ import { getLogger } from '@/server/logger'
export const db = drizzle({
connection: env.DATABASE_URL,
schema,
logger: env.LOG_DB ? new DrizzleLogger(getLogger(['db'])) : false,
logger: env.LOG_DB ? new DrizzleLogger(getLogger(['db']), 'info') : false,
})
+14 -14
View File
@@ -1,19 +1,19 @@
import { configureSync, getConsoleSink, getJsonLinesFormatter, getLogger, type LogLevel } from '@logtape/logtape'
import { configureSync, getConfig, getConsoleSink, getJsonLinesFormatter } from '@logtape/logtape'
import { prettyFormatter } from '@logtape/pretty'
import { env } from '@/env'
const format = env.LOG_FORMAT ?? (process.stdout.isTTY ? 'pretty' : 'json')
if (getConfig() === null) {
const format = env.LOG_FORMAT ?? (process.stdout.isTTY ? 'pretty' : 'json')
configureSync({
reset: true,
sinks: {
console: getConsoleSink({ formatter: format === 'pretty' ? prettyFormatter : getJsonLinesFormatter() }),
},
loggers: [
{ category: [], lowestLevel: env.LOG_LEVEL, sinks: ['console'] },
{ category: ['logtape', 'meta'], lowestLevel: 'warning', sinks: ['console'] },
],
})
configureSync({
sinks: {
console: getConsoleSink({ formatter: format === 'pretty' ? prettyFormatter : getJsonLinesFormatter() }),
},
loggers: [
{ category: [], lowestLevel: env.LOG_LEVEL, sinks: ['console'] },
{ category: ['logtape', 'meta'], lowestLevel: 'warning', sinks: ['console'], parentSinks: 'override' },
],
})
}
export type { LogLevel }
export { getLogger }
export { getLogger } from '@logtape/logtape'