feat: 引入运行时日志体系和存储配置,配置文件改为必填

- 新增 pino/pino-pretty/pino-roll 依赖,实现结构化日志(console pretty + file JSONL rolling)
- 新增 Logger 接口及 PinoLoggerWrapper/ConsoleFallbackLogger/NoopLogger/MemoryLogger 实现
- 新增 src/pino-roll.d.ts 类型声明
- 新增 server.storage.dataDir 配置(默认 ./data,相对路径基于配置文件目录)
- 新增 server.logging 配置(level/console/file/rotation,支持变量引用)
- 配置文件从可选改为必填,parseRuntimeArgs 无参数时抛错
- bootstrap 创建 logger、确保 dataDir、shutdown flush、失败路径 fallback
- startServer 接收 logger 并输出结构化监听日志
- ESLint 新增 no-restricted-syntax 禁止 src/server 直接 console.*(排除 logger.ts)
- 更新 config.example.yaml、README.md、DEVELOPMENT.md 同步配置和日志文档
- 完善测试覆盖:logger、config、schema、bootstrap 共 150 个测试通过
This commit is contained in:
2026-05-25 14:44:37 +08:00
parent c592f2b97c
commit 60d50afad1
22 changed files with 1658 additions and 219 deletions

View File

@@ -26,6 +26,7 @@ src/
config.ts CLI 参数解析与配置文件加载 facade可选 YAML configPath支持 --help/-h
config/ 配置解析模块types、issues、variables、normalizer、schema
dev.ts 开发模式启动入口mode: "development"
logger.ts 统一日志接口和运行时实现
main.ts 生产模式启动入口mode: "production",安全头启用)
server.ts HTTP server 启动工厂Bun.serve routes 声明式路由 + fetch fallback 静态资源服务)
static.ts 生产模式静态资源服务SPA fallback、Content-Type 映射、immutable 缓存)
@@ -71,7 +72,7 @@ src/
bump-version-logic.ts 纯版本管理逻辑parse、validate、bump、format
bump-version.ts 版本升迁 CLI 脚本
clean.ts 清理构建产物与临时文件
tests/ Bun test 测试(结构镜像 src 目录)
tests/ Bun test 测试(结构镜像 src 目录)
setup.ts 全局测试配置jsdom、polyfill
helpers.ts 测试辅助工具rmRetry
server/ 后端测试
@@ -88,6 +89,7 @@ tests/ Bun test 测试(结构镜像 src 目录)
openspec/ OpenSpec 变更、规格文档与 fast-drive workflow schema
config.example.yaml 配置文件示例server.listen 布局 + 显式变量引用)
config.schema.json 配置文件 JSON Schema由 bun run schema 生成)
data/ 运行时数据目录(日志文件、数据库等)
```
---
@@ -104,11 +106,13 @@ config.schema.json 配置文件 JSON Schema由 bun run schema 生成)
```
启动流程:
dev.ts / main.ts → parseRuntimeArgs(cli args)
→ bootstrap({ configPath, mode })
→ loadServerConfig(configPath)可选 YAML 解析 → ServerConfig{ host, port }
startServer({ config, mode })Bun.serve routes 声明式路由 + fetch fallback
注册 SIGINT/SIGTERM shutdown
dev.ts / main.ts → parseRuntimeArgs(cli args) → 必须指定 config.yaml
→ bootstrap({ configPath, mode, version? })
→ loadServerConfig(configPath)YAML 解析 → configDir、dataDir、logging
createRuntimeLogger(config.logging, mode, version)
mkdirSync(config.dataDir, { recursive: true })
→ startServer({ config, logger })Bun.serve 声明式路由 + fetch fallback
→ logger 记录启动成功SIGINT/SIGTERM → logger.flush() → exit
HTTP 请求:
Request → Bun.serve routes 声明式匹配 → routes/*.ts(handler)
@@ -183,6 +187,12 @@ export function handleMeta(mode: RuntimeMode, version: string): Response;
- `serveStaticAsset(pathname, assets)` — 静态资源分发(文件扩展名路由 → immutable 缓存,无扩展名 → SPA fallback 返回 index.html
- `hasFileExtension(path)` / `contentTypeFor(path)` / `htmlResponse(html)` — 辅助函数
- **`logger.ts`**:统一日志接口和运行时实现
- `Logger` 接口:`trace`/`debug`/`info`/`warn`/`error`/`fatal`/`child`/`flush`
- `createRuntimeLogger(config)`:生产运行时,基于 Pino 结构化日志console pretty + file JSONL rolling
- `createConsoleFallback()`:配置加载失败前的降级日志
- `createNoopLogger()` / `createMemoryLogger()`:测试替身
### 1.5 类型定义规范
- **共享类型**以 `src/shared/api.ts` 为唯一源头,前后端共同引用
@@ -191,16 +201,19 @@ export function handleMeta(mode: RuntimeMode, version: string): Response;
- 前端不得 `import src/server/` 下的任何文件
- **严格联合类型**优先于宽类型:如 `RuntimeMode: "development" | "production" | "test"` 而非 `RuntimeMode: string`
- API 响应类型(`ApiErrorResponse``MetaResponse`)定义在 shared 中
- `ResolvedConfig` 类型包含 `configDir``dataDir``logging`,由配置解析流程产出
### 1.6 配置文件规范
配置采用分层生命周期:`unknown → AuthoringConfig → NormalizedConfig → ValidatedConfig → ServerConfig`
```
CLI argv → parseRuntimeArgs → { configPath? }
CLI argv → parseRuntimeArgs → { configPath }
→ 必须指定 configPath无 configPath 启动失败)
→ loadServerConfig(configPath)
无 configPath → 默认值 { host: "127.0.0.1", port: 3000 }
有 configPath → YAML 解析 → normalize(变量替换) → strict validate → resolve → ServerConfig{ host, port }
YAML 解析 → normalize(变量替换) → strict validate → resolve
ServerConfig{ host, port, configDir, dataDir, logging }
→ logging 字段用于 createRuntimeLoggerdataDir 字段用于 mkdirSync
```
配置加载流程:
@@ -208,14 +221,28 @@ CLI argv → parseRuntimeArgs → { configPath? }
1. **解析 YAML**`Bun.YAML.parse` 将配置文件解析为 `unknown`
2. **normalize**:提取 `variables`,替换 `${KEY}` / `${KEY|default}` 变量引用,移除 `variables` 段,产出 `NormalizedConfig`
3. **strict validate**:使用 Ajv + TypeBox 生成的 schema 严格校验(拒绝未知字段、校验类型和范围)
4. **resolve**:从校验后的配置中提取 `server.listen.host``server.listen.port`,填充默认值
4. **resolve**:从校验后的配置中提取 `server.listen.host``server.listen.port``server.storage.dataDir``server.logging` 等字段,填充默认值
`ServerConfig` 包含以下字段:
| 字段 | 来源 | 默认值 |
| ------ | ------------------------------------------ | ----------- |
| `host` | `server.listen.host`(可含变量引用)→ 默认 | `127.0.0.1` |
| `port` | `server.listen.port`(可含变量引用)→ 默认 | `3000` |
| 字段 | 来源 | 默认值 |
| --------- | ------------------------------------------ | --------------------- |
| `host` | `server.listen.host`(可含变量引用)→ 默认 | `127.0.0.1` |
| `port` | `server.listen.port`(可含变量引用)→ 默认 | `3000` |
| `dataDir` | `server.storage.dataDir` → 默认 `./data` | 配置文件目录下 `data` |
| `logging` | `server.logging` | 见下方日志配置 |
**日志配置(`server.logging`**
| 字段 | 类型 | 说明 | 默认值 |
| ------------------------- | ------ | -------------------------------------------------------------------- | -------------------------------- |
| `level` | string | 全局日志级别trace/debug/info/warn/error/fatal未设置时默认 info | `"info"` |
| `console.level` | string | 控制台日志级别,未设置时继承 `level` | 继承 `level` |
| `file.level` | string | 文件日志级别,未设置时继承 `level` | 继承 `level` |
| `file.path` | string | 日志文件路径,相对路径基于配置文件目录,默认基于 `dataDir` | `<dataDir>/logs/${APP.name}.log` |
| `file.rotation.size` | string | 按大小滚动,支持 `B`/`KB`/`MB`/`GB` 单位 | `"50MB"` |
| `file.rotation.frequency` | string | 按时间滚动(`hourly`/`daily`/`weekly` | `"daily"` |
| `file.rotation.maxFiles` | number | 最大归档文件数 | `14` |
配置文件示例(`config.example.yaml`
@@ -225,6 +252,19 @@ server:
listen:
host: "${HOST|127.0.0.1}"
port: ${PORT|3000}
storage:
dataDir: ${DATA_DIR|./data}
logging:
level: ${LOG_LEVEL|info}
console:
level: info
file:
level: info
path: "./data/logs/my-app.log"
rotation:
size: "50MB"
frequency: daily
maxFiles: 14
```
变量语法:
@@ -287,7 +327,32 @@ bun run version:set 2.0.0 # 显式设置版本号
### 1.8 错误模式
- **API 错误**`{ error: "描述", status: <code> }`,状态码 400/404
- **日志**非致命异常用 `console.warn`,启动失败用 `console.error` + `process.exit(1)`
- **日志**后端运行时代码统一通过 `Logger` 接口输出结构化和纯文本日志,禁止直接使用 `console.*``src/server/logger.ts` 是唯一例外)。开发环境控制台输出 pretty 格式,生产环境同时输出 JSONL 文件日志并支持大小和时间滚动。敏感字段自动 redaction。启动失败和未初始化场景使用 `ConsoleFallbackLogger` 兜底。
### 1.9 日志模块
`Logger` 接口定义统一的日志抽象,支持以下实现:
| 实现 | 用途 |
| ----------------------- | --------------------------------------------- |
| `PinoLoggerWrapper` | 生产运行时,封装 Pino、pino-pretty、pino-roll |
| `ConsoleFallbackLogger` | 配置加载失败前的降级日志 |
| `NoopLogger` | 静默丢弃日志 |
| `MemoryLogger` | 测试替身,可断言日志内容 |
使用方式:
```typescript
// 生产运行时创建
const logger = createRuntimeLogger(config.logging, mode, version);
// 测试中使用 MemoryLogger
const logger = createMemoryLogger();
// ... 执行被测代码 ...
expect(logger.entries).toContainEqual({ level: "info", msg: "server started" });
```
敏感字段自动 redaction请求头中的 `authorization``cookie``x-api-key` 以及请求体中的 `password``token``secret` 等字段在日志输出时自动替换为 `[redacted]`
---
@@ -699,6 +764,8 @@ bun run verify # 完整验证check + build
**前端导入限制**`src/web/` 下的文件禁止 `import src/server/` 下的运行时实现,通过 `no-restricted-imports` 规则强制执行。
**后端日志限制**`src/server/**/*.ts` 下的文件(除 `src/server/logger.ts` 外)禁止直接使用 `console.*`,通过 `no-restricted-syntax` 规则强制执行,确保所有日志统一通过 `Logger` 接口输出。
### Prettier 配置
配置文件:`.prettierrc.json`,通过 `eslint-plugin-prettier` 集成为 ESLint 规则(`lint` 命令同时检查格式),也可通过 `format` 命令独立运行。