151 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## ADDED Requirements
### Requirement: 三层日志体系
系统 MUST 实现三层日志体系L1 业务日志 / L2 审计日志 / L3 错误日志,分目录分文件输出。每层用途、保留期、格式明确分离。
| 层级 | 路径 | 用途 | 保留期 |
|---|---|---|---|
| L1 业务 | `Logs/Business/{yyyy-MM-dd}.log` | RPC 入口/出口、业务操作 | 30 天 |
| L2 审计 | `Logs/Audit/{module}/{yyyy-MM-dd}.jsonl` | 抽卡、付费、GM、资源变动 | 永久 |
| L3 错误 | `Logs/Error/{yyyy-MM-dd}.log` | 异常、错误码、慢查询 | 90 天 |
#### Scenario: 日志目录分离
- **WHEN** 服务端启动
- **THEN** 在配置的日志根目录下自动创建 `Business/` `Audit/` `Error/` 三个子目录
- **AND** 各层日志互不混淆
#### Scenario: 按天滚动
- **WHEN** 跨天时
- **THEN** 自动创建新日期的日志文件
- **AND** 超过保留期的旧文件自动清理L2 可配置归档到 OSS
### Requirement: 结构化 JSON 行格式jsonl
L2 审计日志和关键 L1 业务日志 MUST 使用 jsonl 格式(每行一个独立 JSON 对象),便于 `grep`/`jq`/ELK 工具消费。MUST 至少包含字段:`ts`ISO8601 时间戳)、`level``module``playerId`(如有)、`action``reqId`(如有)。
#### Scenario: 抽卡审计日志
- **WHEN** 玩家完成一次抽卡
- **THEN** `Logs/Audit/Gacha/{date}.jsonl` 追加一行
- **AND** 内容形如:
```json
{"ts":"2026-05-27T11:30:01.234","level":"INFO","module":"Gacha","playerId":1001,"action":"Draw","poolId":2,"drawType":"Multi10","costItem":2,"costCount":9,"rewards":[{"itemId":10001,"count":1,"rare":4}],"guaranteeRemain":85,"reqId":"abc123"}
```
#### Scenario: jq 可解析
- **WHEN** 用 `cat Logs/Audit/Gacha/2026-05-27.jsonl | jq '.rewards[].rare'`
- **THEN** 输出所有抽卡品质列表
- **AND** 不出现解析错误
### Requirement: RPC Handler 强制埋点
每个服务端 RPC Handler MUST 在入口和出口埋点 L1 业务日志。入口日志含playerId、actionName、关键参数出口日志含playerId、actionName、错误码、耗时毫秒
#### Scenario: 入口日志
- **WHEN** `C2G_GachaDrawHandler` 被调用
- **THEN** L1 日志立刻输出 `[Gacha] Begin Draw playerId=1001 poolId=2 drawType=Multi10`
- **AND** 即使后续业务校验失败也必须有入口日志
#### Scenario: 出口日志
- **WHEN** Handler 返回响应
- **THEN** L1 日志输出 `[Gacha] End Draw playerId=1001 error=0 cost=23ms`
- **AND** 耗时计算覆盖 Handler 全部生命周期
#### Scenario: 自动 Handler 包装
- **WHEN** 开发者新增 Handler
- **THEN** 通过基类或属性自动埋点入口/出口,不需要手写
- **AND** 不允许某个 Handler 漏埋点(编译期/启动期自检)
### Requirement: 关键操作必写审计日志L2
下列业务操作 MUST 写 L2 审计日志:抽卡(每次/每抽、付费、GM 操作、英雄招募/升星/觉醒、装备锻造/分解、资源大额变动(>1000 单位)、玩家登录/登出。审计日志 MUST 同步落盘(不允许丢失)。
#### Scenario: GM 操作审计
- **WHEN** 任何 GM 命令被执行
- **THEN** `Logs/Audit/GM/{date}.jsonl` 追加一行
- **AND** 包含 `operator`、`action`、`targetPlayer`、`args`
- **AND** 即使命令失败也记录
#### Scenario: 资源大额变动审计
- **WHEN** 玩家金币/钻石 / 高价值道具单次变动 ≥ 1000
- **THEN** `Logs/Audit/Resource/{date}.jsonl` 追加一行
- **AND** 含变动前后数量、变动原因(抽卡/购买/GM/任务等)
#### Scenario: 抽卡审计不可丢失
- **WHEN** 服务端突然 kill -9
- **THEN** 已完成的抽卡审计日志 MUST 100% 完整(同步刷盘)
- **AND** 未完成的抽卡不在审计日志中
### Requirement: 错误码返回必写错误日志L3
每次服务端 RPC Handler 返回非 0 错误码 MUST 同步写 L3 错误日志。MUST 含 playerId、errorCode、errorMessage、上下文参数。重复错误码 1 分钟内同 playerId 仅记录首条 + 计数(防止刷屏)。
#### Scenario: 错误码返回写日志
- **WHEN** Handler 返回 `ERR_GACHA_COST_NOT_ENOUGH`
- **THEN** `Logs/Error/{date}.log` 输出 `[Gacha][ERR] playerId=1001 errorCode=30001 (ERR_GACHA_COST_NOT_ENOUGH) poolId=2 drawType=Multi10`
- **AND** 同时返回客户端正常的错误响应
#### Scenario: 重复错误降频
- **WHEN** 同 playerId 同 errorCode 1 分钟内连续触发 100 次
- **THEN** 仅记录首条 + 周期末计数 `repeated 99 times`
- **AND** 避免日志爆量
### Requirement: 模块标签 + reqId 全链路追踪
所有日志 MUST 携带模块标签(`[Hero]`、`[Bag]`、`[Gacha]`、`[Equip]`、`[DB]`、`[Login]`、`[GM]`、`[Net]`)。每个客户端发起的 RPC MUST 携带 reqIduuid 或自增序列),服务端日志 MUST 全程透传该 reqId。
#### Scenario: 单玩家行为链路追查
- **WHEN** 客户端发送 `C2G_GachaDraw { reqId: "abc123" }`
- **THEN** 服务端 Handler 收到后日志含 `reqId=abc123`
- **AND** 后续调 `BagComponentSystem.AddItems` 内部日志也含该 reqId
- **AND** 可通过 `grep 'reqId=abc123' Logs/**/*.log` 还原完整链路
#### Scenario: 模块标签过滤
- **WHEN** 运维需要排查抽卡问题
- **THEN** `grep -F '[Gacha]' Logs/Business/2026-05-27.log` 能精确过滤
- **AND** 与其他模块日志完全隔离
### Requirement: 慢操作告警
服务端 MUST 监测以下慢操作并写 L3 错误日志告警DB 查询 > 100ms、DB 写入 > 200ms、Handler 执行 > 500ms、Lua/C# Compile > 1000ms。慢操作日志 MUST 含调用栈和参数摘要。
#### Scenario: 慢查询告警
- **WHEN** 一次 `DBComponent.Query` 耗时 250ms
- **THEN** L3 日志输出 `[DB][SLOW] Query Hero playerId=1001 duration=250ms exceeded threshold=100ms`
- **AND** 含调用栈(简化版)
#### Scenario: 慢 Handler 告警
- **WHEN** 一次 RPC Handler 执行 800ms
- **THEN** L3 日志输出 `[Net][SLOW] C2G_GachaDraw playerId=1001 duration=800ms`
- **AND** 后续可在监控大盘聚合告警
### Requirement: 客户端日志桥接(可选)
客户端 MUST 提供日志桥接开关:开启时,客户端 `Log.Error` / `Log.Warning` 关键日志通过专用 RPC 上报到服务端。服务端写入 `Logs/Client/{date}.log`,含 playerId / clientVersion / 设备信息。开关由 StartConfig 控制,默认开发期开、生产期可关。
#### Scenario: 客户端崩溃日志上报
- **WHEN** 客户端发生 `NullReferenceException`
- **AND** 桥接开关 ON
- **THEN** 异常信息通过 `C2G_ClientLogReport` 上报
- **AND** 服务端 `Logs/Client/{date}.log` 追加一行 jsonl
#### Scenario: 上报频率限制
- **WHEN** 客户端短时间内大量错误
- **THEN** 服务端 MUST 对每个 playerId 限速1 秒最多 5 条)
- **AND** 超过的日志在客户端本地累计,每分钟批量上报
### Requirement: 配置化与开关
日志各项行为 MUST 通过 `StartConfig.Logging` 或独立 `LogConfig` 配置日志根路径、各层级开关、滚动策略、保留期、客户端桥接开关、敏感字段脱敏列表。MUST 支持运行时通过 GM 命令 `gm SetLogLevel <module> <level>` 调整。
#### Scenario: 临时调高某模块日志级别
- **WHEN** 运维执行 `gm SetLogLevel Gacha Debug`
- **THEN** Gacha 模块日志立刻输出 Debug 级别
- **AND** 持续到下次重启或再次设置
#### Scenario: 敏感字段脱敏
- **WHEN** 配置 `MaskFields = ["phone", "email", "idCard"]`
- **AND** 业务日志中含 `phone=13800138000`
- **THEN** 输出为 `phone=138****8000`
- **AND** 不允许任何日志层级输出明文敏感信息