From ce39faf778aefdcaf4505c986a4c7633844ca959 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sat, 25 Apr 2026 16:14:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor(logging):=20=E6=8E=A5=E5=8F=97=20oracl?= =?UTF-8?q?e=20=E5=AE=A1=E8=AE=A1=E5=BB=BA=E8=AE=AE=E6=89=93=E7=A3=A8?= =?UTF-8?q?=E4=B8=89=E5=A4=84=E4=B8=8D=E5=A4=9F=E6=9E=81=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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。 --- AGENTS.md | 2 +- src/server/db/index.ts | 2 +- src/server/logger.ts | 28 ++++++++++++++-------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b9200da..305c8fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/src/server/db/index.ts b/src/server/db/index.ts index 1aafc6c..692405e 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -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, }) diff --git a/src/server/logger.ts b/src/server/logger.ts index ac18696..349eacb 100644 --- a/src/server/logger.ts +++ b/src/server/logger.ts @@ -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'