## 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 携带 reqId(uuid 或自增序列),服务端日志 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 ` 调整。 #### Scenario: 临时调高某模块日志级别 - **WHEN** 运维执行 `gm SetLogLevel Gacha Debug` - **THEN** Gacha 模块日志立刻输出 Debug 级别 - **AND** 持续到下次重启或再次设置 #### Scenario: 敏感字段脱敏 - **WHEN** 配置 `MaskFields = ["phone", "email", "idCard"]` - **AND** 业务日志中含 `phone=13800138000` - **THEN** 输出为 `phone=138****8000` - **AND** 不允许任何日志层级输出明文敏感信息