# 日志基础设施整理方案

## 目标

当前服务已经有基础日志能力：HTTP/gRPC 边界日志在 `pkg/logx`，服务启动和 worker 使用标准库 `log` 输出。但生产环境需要把日志整理成稳定、可检索、可告警、可控成本的基础设施。

目标：

- 所有服务统一输出 JSON 结构化日志到 stdout/stderr。
- 业务代码不直接依赖腾讯云 CLS SDK。
- 部署层采集容器 stdout/stderr 到腾讯云 CLS。
- 日志字段统一，支持按 `request_id`、`command_id`、`user_id`、`room_id`、`transaction_id` 追踪。
- 敏感字段默认脱敏，避免 token、密码、支付凭证、UserSig、SecretKey 进入日志。
- 保留后台审计日志和业务账务流水，不用运行日志替代审计和账务事实。

非目标：

- 首版不做完整链路追踪系统。
- 首版不引入重型日志框架。
- 首版不在代码里直连 CLS API 写日志。
- 首版不采集客户端 SDK 日志；客户端日志和崩溃分析后续单独设计。

## 当前现状

已有能力：

- `gateway-service` HTTP 入口接入 `logx.HTTPMiddleware`。
- `room-service`、`wallet-service`、`user-service`、`activity-service` gRPC server 接入 `logx.UnaryServerInterceptor`。
- `logx` 会记录请求、响应、状态码、错误 reason、耗时和 `request_id`。
- `logx` 已经对 `password`、`token`、`secret`、`credential`、`user_sig`、`authorization` 等字段做脱敏。
- 健康检查不会进入高频 access log。

主要问题：

- App 后端服务已接入 `logx.Init`，默认输出 stdout JSON；`log.Printf` 会被兜底包成 `stdlog` JSON。
- 核心 worker、RTC callback、room outbox 已改为结构化字段日志。
- admin-server 是独立 Go module，按项目边界在 `server/admin/internal/platform/logging` 接入同一 stdout JSON 口径，不 import `hyapp/pkg/logx`。
- access log 默认不记录 request/response body；本地可通过配置临时打开，生产需要继续按接口和环境控制体积。
- 没有部署层日志采集配置，也没有腾讯云 CLS logset/topic 规划。

## 总体架构

```mermaid
flowchart LR
    S1["gateway-service"] --> STDOUT["stdout/stderr JSON logs"]
    S2["room-service"] --> STDOUT
    S3["wallet-service"] --> STDOUT
    S4["user-service"] --> STDOUT
    S5["activity-service"] --> STDOUT
    S6["hyapp-admin-server"] --> STDOUT

    STDOUT --> Runtime["Container Runtime"]
    Runtime --> Collector["TKE Log Collector / CLS LogListener"]
    Collector --> CLS["Tencent Cloud CLS"]
    CLS --> Search["检索与排障"]
    CLS --> Dashboard["仪表盘"]
    CLS --> Alarm["告警策略"]
```

核心决策：

- 服务只负责输出标准 JSON 日志。
- 日志采集由部署层负责。
- 本地开发继续用 stdout，`docker compose logs` 可查看。
- 生产环境通过腾讯云容器日志采集或 CLS LogListener 采集 stdout。
- 后台审计继续写 `admin_operation_logs`，钱包账务继续写 `wallet_transactions`、`wallet_entries`，不能用运行日志替代。

## 日志等级

统一使用：

| Level | 使用场景 |
| --- | --- |
| `debug` | 本地调试，生产默认关闭 |
| `info` | 正常业务事件、access log、worker 成功摘要 |
| `warn` | 可恢复异常、降级、重试、外部依赖短暂失败 |
| `error` | 请求失败、事务失败、外部依赖不可用、worker 批次失败 |

配置项：

```yaml
log:
  level: info
  format: json
  include_request_body: false
  include_response_body: false
  max_payload_bytes: 2048
```

本地可以打开 body：

```yaml
log:
  level: debug
  format: text
  include_request_body: true
  include_response_body: true
  max_payload_bytes: 8192
```

生产默认不记录完整 request/response body，只记录关键字段和错误摘要。

## 统一字段规范

每条日志都应该尽量包含基础字段：

| 字段 | 说明 |
| --- | --- |
| `time` | 日志时间 |
| `level` | `debug/info/warn/error` |
| `msg` | 短事件名，例如 `http_access`、`grpc_access`、`wallet_transaction_failed` |
| `env` | `local/dev/staging/prod` |
| `app_code` | 多 App 租户标识 |
| `service` | 服务名 |
| `version` | 镜像版本或 git sha |
| `node_id` | 当前服务节点 |
| `request_id` | 请求追踪 ID |

业务字段按场景补充：

| 场景 | 推荐字段 |
| --- | --- |
| HTTP | `method`、`path`、`status`、`duration_ms`、`client_ip` |
| gRPC | `grpc_method`、`grpc_code`、`reason`、`duration_ms` |
| 房间 | `room_id`、`room_version`、`command_id`、`user_id` |
| 钱包 | `transaction_id`、`command_id`、`user_id`、`asset_type`、`biz_type` |
| 礼物 | `room_id`、`sender_user_id`、`target_user_id`、`gift_id`、`gift_count` |
| 币商 | `seller_user_id`、`target_user_id`、`amount`、`transaction_id` |
| worker | `worker`、`worker_id`、`batch_size`、`retry_count` |
| 外部依赖 | `provider`、`endpoint`、`external_code`、`duration_ms` |

字段命名固定使用 snake_case，和 protobuf/mysql 字段风格一致，方便 CLS 查询。

## `pkg/logx` 改造

`pkg/logx` 应该从“access log middleware”升级成“统一日志入口”。

建议新增：

```go
type Config struct {
    Service             string
    Env                 string
    Version             string
    NodeID              string
    Level               string
    Format              string
    IncludeRequestBody  bool
    IncludeResponseBody bool
    MaxPayloadBytes     int
}

func Init(cfg Config) error
func Logger(ctx context.Context) *slog.Logger
func With(ctx context.Context, attrs ...slog.Attr) context.Context
func Info(ctx context.Context, msg string, attrs ...slog.Attr)
func Warn(ctx context.Context, msg string, attrs ...slog.Attr)
func Error(ctx context.Context, msg string, err error, attrs ...slog.Attr)
```

改造原则：

- 服务启动时调用 `logx.Init` 设置全局 logger。
- `log.Printf` 逐步替换成 `logx.Info/Warn/Error` 或 `slog` 结构化字段。
- HTTP/gRPC middleware 自动把 `request_id`、`service`、`app_code` 写入日志上下文。
- `max_payload_bytes` 从配置读取，不写死。
- 生产默认不记录完整 body；只有错误或 debug 环境才允许记录脱敏 body。

## Access Log 策略

HTTP access log：

- 成功请求记录一条 `info`。
- 4xx 记录 `warn` 或 `info`，取决于业务错误类型。
- 5xx 记录 `error`。
- healthcheck 不记录。
- request/response body 默认关闭。

gRPC access log：

- `codes.OK` 记录 `info`。
- 业务前置失败，例如 `InvalidArgument`、`FailedPrecondition`，记录 `warn` 或 `info`。
- `Internal`、`Unavailable`、`DeadlineExceeded` 记录 `error`。
- `grpc.health.v1.Health` 不记录。

建议生产日志示例：

```json
{
  "time": "2026-05-09T12:00:00.000Z",
  "level": "INFO",
  "msg": "grpc_access",
  "env": "prod",
  "service": "wallet-service",
  "request_id": "req-123",
  "app_code": "lalu",
  "grpc_method": "/hyapp.wallet.v1.WalletService/TransferCoinFromSeller",
  "grpc_code": "OK",
  "reason": "",
  "duration_ms": 18,
  "command_id": "cmd-456",
  "seller_user_id": 1001,
  "target_user_id": 2002,
  "transaction_id": "wtx-789"
}
```

## 敏感字段规则

禁止进入日志：

- 明文密码
- Firebase ID token
- Google/Apple provider credential
- refresh token / access token
- 腾讯云 SecretId / SecretKey
- Tencent IM UserSig
- RTC token
- 支付 provider receipt 原文
- USDT 完整链上凭证如包含敏感内部备注时，最多记录摘要
- 身份证件、银行卡、提现收款账号全文

可以记录：

- `request_id`
- `command_id`
- `transaction_id`
- `room_id`
- `user_id`
- `provider`
- provider 错误码
- token/receipt 的 hash 摘要，前提是确实需要排障

`logx` 的脱敏 key 列表需要继续集中维护，禁止业务代码自己拼接敏感原文。

## 腾讯云 CLS 接入方案

推荐方案：stdout 采集到 CLS。

### 方案 A：TKE 容器标准输出采集

如果服务部署在腾讯云 TKE：

1. 每个服务输出 JSON 到 stdout/stderr。
2. TKE 开启日志采集。
3. 采集目标选择容器标准输出。
4. 解析方式选择 JSON。
5. 不同环境写入不同 CLS topic。

推荐 topic：

| 环境 | Logset | Topic |
| --- | --- | --- |
| dev | `hyapp-dev` | `service-logs` |
| staging | `hyapp-staging` | `service-logs` |
| prod | `hyapp-prod` | `service-logs` |
| prod | `hyapp-prod` | `admin-audit-runtime` |
| prod | `hyapp-prod` | `wallet-security` |

首版可以所有服务进同一个 `service-logs` topic，通过 `service` 字段过滤。等日志量变大，再把 `gateway-service`、`wallet-service`、`room-service` 拆 topic。

### 方案 B：CVM / Docker 部署使用 CLS LogListener

如果服务部署在 CVM Docker：

1. 服务仍输出 JSON 到 stdout。
2. Docker 日志落到宿主机默认 json log 文件。
3. 安装 CLS LogListener。
4. LogListener 采集容器日志文件或指定目录。
5. CLS 端按 JSON 解析。

不建议在每个 Go 服务里集成 CLS SDK。直连 SDK 会把业务进程和日志平台绑定，CLS 抖动时还可能影响业务路径。

## CLS 索引字段

CLS topic 需要开启键值索引。

建议索引字段：

- `level`
- `env`
- `service`
- `app_code`
- `request_id`
- `command_id`
- `user_id`
- `room_id`
- `transaction_id`
- `biz_type`
- `grpc_code`
- `reason`
- `status`
- `path`
- `worker`
- `event_type`

全文索引可以保留，但排障优先用字段检索，避免只靠模糊搜索。

## 告警策略

首版告警不要太多，先覆盖最关键的错误和积压。

建议：

| 告警 | 条件 |
| --- | --- |
| 服务 5xx 增多 | `service=gateway-service AND status>=500` 5 分钟超过阈值 |
| gRPC Internal | `grpc_code=Internal` 5 分钟超过阈值 |
| 钱包失败 | `service=wallet-service AND level=ERROR` 1 分钟超过阈值 |
| room outbox 积压 | worker 日志出现 `status=batch_failed` 或 retryable 数量持续增长 |
| 腾讯云 IM/RTC 回调异常 | `provider=tencent AND level>=WARN` 超过阈值 |
| 用户登录失败突增 | `msg=auth_failed` 或 `reason=AUTH_FAILED` 超过阈值 |

告警通知：

- 开发阶段：企业微信/飞书群。
- 生产阶段：按服务 owner 分组。
- 钱包和支付相关 error 单独告警，不能淹没在普通业务 error 里。

## 保留周期和成本控制

建议：

| 环境 | 保留时间 |
| --- | --- |
| local | 不上传 CLS |
| dev | 3-7 天 |
| staging | 7-14 天 |
| prod 普通服务日志 | 30 天 |
| prod 钱包/支付安全日志 | 90-180 天，按合规要求调整 |

成本控制：

- 生产关闭 request/response body。
- 高频成功日志只保留摘要。
- healthcheck 不采集或不输出。
- 大 payload 截断。
- 对重复 worker 成功日志做批次汇总，不逐条刷屏。
- 对外部 callback 原始 body 只在 debug 或白名单场景短期开启。

## 和审计日志的关系

运行日志和审计日志是两套东西。

运行日志：

- 用于排障、告警、性能分析。
- 存在 CLS。
- 可以按保留周期清理。

审计日志：

- 用于追责、后台操作记录、财务追踪。
- 存在业务数据库，例如 `admin_operation_logs`。
- 必须有操作者、动作、资源、结果、IP、原因。
- 不能因为 CLS 采集失败而丢失。

钱包账务流水：

- 存在 `wallet_transactions`、`wallet_entries`、`wallet_recharge_records` 等表。
- 是账务事实来源。
- 运行日志只辅助定位，不能替代账务表。

## 阶段划分

第一阶段是服务内日志标准化，目标是让所有进程稳定输出结构化 JSON 到 stdout/stderr。当前第一阶段已经完成到可进入联调的状态：

- App 后端服务接入 `logx.Init`。
- `admin-server` 因为是独立 Go module，使用 `server/admin/internal/platform/logging` 接入。
- HTTP/gRPC access log 支持统一字段、脱敏、body 开关、payload 截断和错误等级。
- `log.Printf` 类 worker 日志已改成结构化日志或被 stdout JSON 兜底。
- `logx` 已补充 gRPC 常用追踪字段提取；即使生产关闭 request body，也能在 access log 中检索 `command_id`、`user_id`、`room_id`、`transaction_id` 等字段。
- 启动完成后的进程级失败会按 `ERROR` 结构化输出，不再落成普通 INFO `stdlog`。

第二阶段是腾讯云 CLS 部署接入，目标是把第一阶段的 stdout JSON 真正采集、索引、查询和告警：

1. 在腾讯云 CLS 创建 logset/topic，按环境隔离 `dev`、`staging`、`prod`。
2. 配置 TKE 容器标准输出采集，或在 CVM/Docker 部署 CLS LogListener。
3. CLS topic 使用 JSON 解析，开启字段索引。
4. 建立基础查询模板：按 `request_id` 查链路，按 `service` 看错误，按 `transaction_id` 查钱包，按 `room_id` 查房间。
5. 建立第一批 dashboard：服务错误、接口耗时、gRPC 错误、wallet 失败、room outbox 重试、登录失败。
6. 建立第一批告警：gateway 5xx、gRPC Internal/Unavailable、wallet ERROR、room outbox 重试异常、腾讯云回调异常。
7. 验证保留周期和采集成本，生产默认不上传 request/response body。

第三阶段再做观测增强，包括 OpenTelemetry trace、Prometheus 指标、慢请求日志、安全日志 topic、客户端日志关联。

## 实施顺序

1. 已增加统一日志配置结构：`log.level`、`log.format`、`log.include_request_body`、`log.include_response_body`、`log.max_payload_bytes`。
2. 已扩展 `pkg/logx.Init`，服务启动时统一设置 JSON/text handler、level、基础字段。
3. 已给 gateway、room、user、wallet、activity 接入 `logx.Init`；admin-server 通过独立 module 内的 `platform/logging` 接入。
4. 已改造 `logx.HTTPMiddleware` 和 `logx.UnaryServerInterceptor`，按配置控制 body、截断大小和错误等级。
5. 已替换核心路径的 `log.Printf`：
   - room outbox worker
   - user region rebuild worker
   - user mic time worker
   - invite recharge worker
   - Tencent RTC callback
6. 本地验证 JSON 日志格式：`docker compose logs gateway-service` 能看到结构化 JSON。
7. 生产部署层接入腾讯云 CLS stdout 采集。
8. 在 CLS 配置 JSON 解析和字段索引。
9. 建立基础 dashboard：错误率、接口耗时、钱包失败、room outbox 重试、登录失败。
10. 建立首批告警策略。

## 测试和验收

代码侧：

- `logx` 单测覆盖脱敏字段。
- `logx` 单测覆盖 payload 截断。
- HTTP middleware 单测覆盖 success、4xx、5xx 日志级别。
- gRPC interceptor 单测覆盖 OK、业务错误、Internal。
- 配置解析单测覆盖默认值和非法 log level。

本地验收：

```bash
docker compose up -d
docker compose logs gateway-service
docker compose logs wallet-service
```

验收标准：

- 日志是 JSON 或本地配置指定的 text。
- 每条 access log 有 `service`、`request_id`、`duration_ms`。
- 敏感字段显示为 `***REDACTED***`。
- healthcheck 不刷屏。
- 生产配置下 request/response body 不输出。

CLS 验收：

- 能按 `request_id` 查到 gateway 和下游服务日志。
- 能按 `service=wallet-service AND transaction_id=...` 查到钱包交易日志。
- 能按 `room_id` 查房间命令相关日志。
- 能按 `level=ERROR` 聚合最近 5 分钟错误。
- 告警能正确触发并发送到通知渠道。

## 后续演进

日志整理完成后，再考虑：

- OpenTelemetry trace，把 `trace_id/span_id` 加入日志字段。
- Prometheus 指标和日志告警分工，错误率和延迟优先走 metrics。
- 慢请求日志，按 `duration_ms` 阈值记录额外字段。
- 安全日志 topic，把支付、提现、币商、管理员高危操作单独留存。
- 客户端日志和崩溃分析接入，和服务端 `request_id` 或 `session_id` 关联。
