docs: require explicit logger env

This commit is contained in:
2026-06-17 06:45:55 -07:00
parent 6d10c2a474
commit 3f4c67c78e

View File

@@ -0,0 +1,170 @@
# Logger Env 显式传递设计
## 背景
`server/src` 正在改造成可以在 Cloudflare 运行的服务。Cloudflare Worker 的 bindings 由入口函数参数 `env` 提供,不能依赖 Node.js 进程环境、模块级全局变量或默认空对象。当前代码已经在大范围迁移 `runtimeEnv`但日志模块仍存在一类独立风险logger 会在 import 阶段用空对象初始化,或者通过默认值隐藏调用方没有传入真实 `Env` 的问题。
当前发现的核心残留位于 `server/src/external/logtail/logtailUtils.ts`
```ts
const pinoLogger = initLogger({}, {});
export const logger = createLoggerStructure(pinoLogger, {});
```
这会让上游即使没有从 Cloudflare 入口传入 `env`,也能得到一个看似可用的 logger。结果是 `NODE_ENV``AXIOM_TOKEN` 等配置可能静默缺失,类型检查也无法暴露调用链断点。
## 目标
- 所有运行时 logger 初始化必须显式接收 `env: Env`
- `Env` 参数不允许默认值,不允许 `env?: Env`,不允许 `env = {} as Env`
- `logtailUtils.ts` 不导出模块级 `logger` singleton。
- `server/src` 不从 `logtailUtils.ts` 导入全局 `logger`
- 调用方必须从 Cloudflare/Hono/cron/worker/Node 显式入口逐层传递 `env` 或传递已由入口 env 创建出的 `ctx.logger`
- 类型检查和单元扫描测试能阻止后续重新引入空对象 env 或全局 logger。
## 非目标
- 不在本次任务中重写全部 Cloudflare 部署结构。
- 不改变 pino、Axiom、Logtail 或现有 logger 输出格式。
- 不把所有业务函数都强制新增 `env: Env`;如果已有 `ctx.logger`,优先复用 logger。
- 不处理 `better-auth` 包自身导出的 `logger`,它不是本项目 `logtailUtils.ts` 的全局 logger。
- 不整理与 logger/env 无关的既有大规模未提交改动。
## 推荐方案
采用“logger factory only加扫描测试”的方案。
备选方案比较:
- 只删除当前 `initLogger({}, {})`:改动最小,但无法防止其他文件继续导入或新增全局 `logger`
- logger factory only加扫描测试删除 import-time logger保留现有 `createLogger(env)``createDualLogger(env)` 模式,并用单元测试禁止回退。改动聚焦,能满足本次强约束。
- AST 级全仓 Env 规则:最严谨,但当前 repo 已有大量 env threading 改动,成本和误伤风险更高。可以作为后续强化,不作为本轮必要条件。
因此推荐第二种方案。
## 架构边界
`Logger` 应被视为依赖 Cloudflare bindings 的入口级对象,而不是模块级 singleton。
合法来源:
- Cloudflare `fetch(request, env, ctx)`
- Hono `c.env`
- cron/worker/Node 本地入口显式接收的 `initialEnv: Env``env: Env`
- 由上述 `env` 创建并传入上下文的 `ctx.logger`
非法来源:
- `initLogger({}, {})`
- `createLoggerStructure(..., {})`
- `export const logger = ...`
- 函数参数 `env: Env = {} as Env`
- 可选参数 `env?: Env` 后再用 `{}` fallback。
## 组件改造
### `logtailUtils.ts`
该模块应只提供 factory 和类型:
```ts
export const createLogger = (env: Env) =>
createLoggerStructure(initLogger({}, env), env);
export const createDualLogger = (env: Env) =>
createLoggerStructure(initLogger({ mode: "dual" }, env), env);
```
`createLoggerStructure``env` 参数使用 `Env`,不使用 `Partial<Env>`,也不提供默认值。删除模块级 `pinoLogger``export const logger`
### `initLogger.ts`
`initLogger` 收紧为:
```ts
export const initLogger = (options: InitLoggerOptions, env: Env) => { ... }
```
可选配置继续通过真实 `env` 判断,例如 `env.AXIOM_TOKEN`。缺少可选 token 时不创建 Axiom stream但不能通过空对象默认值制造这个状态。
### 调用点
`@/external/logtail/logtailUtils.js` 导入 `logger``server/src` 文件必须逐个改造:
- 已有 `ctx.logger` 的位置使用 `ctx.logger`
- 已有 `env: Env``c.env` 的位置局部创建 `const logger = createLogger(env)`
- 没有 `env` 的位置向上游补 `env: Env`,直到接到真实入口。
测试文件如果需要 logger应使用测试环境显式 `Env` 创建,或 mock `createLogger`,不能依赖生产模块导出的 singleton。
## 数据流
请求路径:
```text
Cloudflare fetch(request, env, ctx)
-> app.fetch(request, env, ctx)
-> Hono c.env
-> baseMiddleware/createLogger(c.env)
-> ctx.logger
-> service/helper 使用 ctx.logger 或显式 env
```
后台路径:
```text
Cloudflare queue/cron env 或 Node/Bun 显式 initialEnv
-> startWorkers/startCron/startNodeServer(env)
-> createLogger(env)
-> createWorkerContext({ ..., logger })
-> job/service/helper
```
任何 logger 初始化都必须能从参数链追溯到入口 `env`
## 错误处理
logger 初始化不应因为缺少可选配置而抛错。例如 `AXIOM_TOKEN` 缺失时可以只使用 stdout/dev stream。这个判断必须基于真实传入的 `env`
必填配置仍应通过显式 helper 校验,例如 `requireEnv(env, key, context)`。错误消息包含缺失 key 和调用场景,不输出 secret value。
如果实现时发现调用链无法提供 `Env`,正确处理方式是补齐上游参数或改用已存在的 `ctx.logger`,不是重新引入默认值。
## 测试与验证
新增或扩展单元扫描测试,覆盖 `server/src`
- 禁止 `env: Env = ...`
- 禁止 `env?: Env`
- 禁止 `env = {} as Env`
- 禁止 `initLogger({}, {})`
- 禁止 `createLoggerStructure(..., {})`
- 禁止 `logtailUtils.ts` 导出 `logger`
- 禁止 `server/src``@/external/logtail/logtailUtils` 导入 `logger`
实现完成后运行:
```bash
cd server && bun ts
cd server && bun test:unit
```
如果测试依赖本地服务或环境变量失败,应记录具体失败命令和错误,不把未运行或失败的验证报告为通过。
## 成功标准
- `server/src/external/logtail/logtailUtils.ts` 不再有 `initLogger({}, {})`
- `server/src/external/logtail/logtailUtils.ts` 不再导出全局 `logger`
- `server/src` 不再从本项目 `logtailUtils.ts` 导入全局 `logger`
- `initLogger``createLoggerStructure``createLogger``createDualLogger` 都要求显式 `env: Env`
- 扫描测试能防止默认 env 和全局 logger 回归。
- 类型检查和单元测试完成,或明确记录无法通过的外部原因。
## 风险与缓解
- 风险:部分后台代码当前没有 `env` 参数,只能通过全局 logger 记录日志。
缓解:优先使用已有 `ctx.logger`;没有上下文时沿入口调用链补 `env: Env`
- 风险:测试代码导入了生产全局 `logger`
缓解:测试改为显式 env logger 或 mock factory不保留生产 singleton。
- 风险:扫描测试误伤第三方 `logger` 名称。
缓解:规则限定在 `@/external/logtail/logtailUtils`、相对路径 `external/logtail/logtailUtils` 和本文件导出,不禁止 `better-auth``logger`