16 KiB
日志基础设施整理方案
目标
当前服务已经有基础日志能力: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-serviceHTTP 入口接入logx.HTTPMiddleware。room-service、wallet-service、user-service、activity-servicegRPC server 接入logx.UnaryServerInterceptor。logx会记录请求、响应、状态码、错误 reason、耗时和request_id。logx已经对password、token、secret、credential、user_sig、authorization等字段做脱敏。- 健康检查不会进入高频 access log。
主要问题:
- App 后端服务已接入
logx.Init,默认输出 stdout JSON;log.Printf会被兜底包成stdlogJSON。 - 核心 worker、RTC callback、room outbox 已改为结构化字段日志。
- admin-server 是独立 Go module,按项目边界在
server/admin/internal/platform/logging接入同一 stdout JSON 口径,不 importhyapp/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 日志。
- 日志采集由部署层负责。
- 本地开发继续用 stdout,
docker compose logs可查看。 - 生产环境通过腾讯云容器日志采集或 CLS LogListener 采集 stdout。
- 后台审计继续写
admin_operation_logs,钱包账务继续写wallet_transactions、wallet_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_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”升级成“统一日志入口”。
建议新增:
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不记录。
建议生产日志示例:
{
"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_idcommand_idtransaction_idroom_iduser_idprovider- provider 错误码
- token/receipt 的 hash 摘要,前提是确实需要排障
logx 的脱敏 key 列表需要继续集中维护,禁止业务代码自己拼接敏感原文。
腾讯云 CLS 接入方案
推荐方案:stdout 采集到 CLS。
方案 A:TKE 容器标准输出采集
如果服务部署在腾讯云 TKE:
- 每个服务输出 JSON 到 stdout/stderr。
- TKE 开启日志采集。
- 采集目标选择容器标准输出。
- 解析方式选择 JSON。
- 不同环境写入不同 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:
- 服务仍输出 JSON 到 stdout。
- Docker 日志落到宿主机默认 json log 文件。
- 安装 CLS LogListener。
- LogListener 采集容器日志文件或指定目录。
- CLS 端按 JSON 解析。
不建议在每个 Go 服务里集成 CLS SDK。直连 SDK 会把业务进程和日志平台绑定,CLS 抖动时还可能影响业务路径。
CLS 索引字段
CLS topic 需要开启键值索引。
建议索引字段:
levelenvserviceapp_coderequest_idcommand_iduser_idroom_idtransaction_idbiz_typegrpc_codereasonstatuspathworkerevent_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结构化输出,不再落成普通 INFOstdlog。
第二阶段是腾讯云 CLS 部署接入,目标是把第一阶段的 stdout JSON 真正采集、索引、查询和告警:
- 在腾讯云 CLS 创建 logset/topic,按环境隔离
dev、staging、prod。 - 配置 TKE 容器标准输出采集,或在 CVM/Docker 部署 CLS LogListener。
- CLS topic 使用 JSON 解析,开启字段索引。
- 建立基础查询模板:按
request_id查链路,按service看错误,按transaction_id查钱包,按room_id查房间。 - 建立第一批 dashboard:服务错误、接口耗时、gRPC 错误、wallet 失败、room outbox 重试、登录失败。
- 建立第一批告警:gateway 5xx、gRPC Internal/Unavailable、wallet ERROR、room outbox 重试异常、腾讯云回调异常。
- 验证保留周期和采集成本,生产默认不上传 request/response body。
第三阶段再做观测增强,包括 OpenTelemetry trace、Prometheus 指标、慢请求日志、安全日志 topic、客户端日志关联。
实施顺序
- 已增加统一日志配置结构:
log.level、log.format、log.include_request_body、log.include_response_body、log.max_payload_bytes。 - 已扩展
pkg/logx.Init,服务启动时统一设置 JSON/text handler、level、基础字段。 - 已给 gateway、room、user、wallet、activity 接入
logx.Init;admin-server 通过独立 module 内的platform/logging接入。 - 已改造
logx.HTTPMiddleware和logx.UnaryServerInterceptor,按配置控制 body、截断大小和错误等级。 - 已替换核心路径的
log.Printf:- room outbox worker
- user region rebuild worker
- user mic time worker
- invite recharge worker
- Tencent RTC callback
- 本地验证 JSON 日志格式:
docker compose logs gateway-service能看到结构化 JSON。 - 生产部署层接入腾讯云 CLS stdout 采集。
- 在 CLS 配置 JSON 解析和字段索引。
- 建立基础 dashboard:错误率、接口耗时、钱包失败、room outbox 重试、登录失败。
- 建立首批告警策略。
测试和验收
代码侧:
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 有
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关联。