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:
@@ -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 字段用于 createRuntimeLogger;dataDir 字段用于 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` 命令独立运行。
|
||||
|
||||
Reference in New Issue
Block a user