hyapp-server/docs/日志基础设施架构.md

16 KiB
Raw Permalink Blame History

日志基础设施整理方案

目标

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

目标:

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

非目标:

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

当前现状

已有能力:

  • gateway-service HTTP 入口接入 logx.HTTPMiddleware
  • room-servicewallet-serviceuser-serviceactivity-service gRPC server 接入 logx.UnaryServerInterceptor
  • logx 会记录请求、响应、状态码、错误 reason、耗时和 request_id
  • logx 已经对 passwordtokensecretcredentialuser_sigauthorization 等字段做脱敏。
  • 健康检查不会进入高频 access log。

主要问题:

  • App 后端服务已接入 logx.Init,默认输出 stdout JSONlog.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 规划。

总体架构

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 日志。
  • 日志采集由部署层负责。
  • 本地开发继续用 stdoutdocker compose logs 可查看。
  • 生产环境通过腾讯云容器日志采集或 CLS LogListener 采集 stdout。
  • 后台审计继续写 admin_operation_logs,钱包账务继续写 wallet_transactionswallet_entries,不能用运行日志替代。

日志等级

统一使用:

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

配置项:

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

本地可以打开 body

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_accessgrpc_accesswallet_transaction_failed
env local/dev/staging/prod
app_code 多 App 租户标识
service 服务名
version 镜像版本或 git sha
node_id 当前服务节点
request_id 请求追踪 ID

业务字段按场景补充:

场景 推荐字段
HTTP methodpathstatusduration_msclient_ip
gRPC grpc_methodgrpc_codereasonduration_ms
房间 room_idroom_versioncommand_iduser_id
钱包 transaction_idcommand_iduser_idasset_typebiz_type
礼物 room_idsender_user_idtarget_user_idgift_idgift_count
币商 seller_user_idtarget_user_idamounttransaction_id
worker workerworker_idbatch_sizeretry_count
外部依赖 providerendpointexternal_codeduration_ms

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

pkg/logx 改造

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

建议新增:

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/Errorslog 结构化字段。
  • HTTP/gRPC middleware 自动把 request_idserviceapp_code 写入日志上下文。
  • max_payload_bytes 从配置读取,不写死。
  • 生产默认不记录完整 body只有错误或 debug 环境才允许记录脱敏 body。

Access Log 策略

HTTP access log

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

gRPC access log

  • codes.OK 记录 info
  • 业务前置失败,例如 InvalidArgumentFailedPrecondition,记录 warninfo
  • InternalUnavailableDeadlineExceeded 记录 error
  • grpc.health.v1.Health 不记录。

建议生产日志示例:

{
  "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。

方案 ATKE 容器标准输出采集

如果服务部署在腾讯云 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-servicewallet-serviceroom-service 拆 topic。

方案 BCVM / 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_failedreason=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_transactionswallet_entrieswallet_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_iduser_idroom_idtransaction_id 等字段。
  • 启动完成后的进程级失败会按 ERROR 结构化输出,不再落成普通 INFO stdlog

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

  1. 在腾讯云 CLS 创建 logset/topic按环境隔离 devstagingprod
  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.levellog.formatlog.include_request_bodylog.include_response_bodylog.max_payload_bytes
  2. 已扩展 pkg/logx.Init,服务启动时统一设置 JSON/text handler、level、基础字段。
  3. 已给 gateway、room、user、wallet、activity 接入 logx.Initadmin-server 通过独立 module 内的 platform/logging 接入。
  4. 已改造 logx.HTTPMiddlewarelogx.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。

本地验收:

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

验收标准:

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

CLS 验收:

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

后续演进

日志整理完成后,再考虑:

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