游戏日志系统
用生活化的比喻,让你从"靠 printf 调试"到"能用 ELK 建立完整的日志体系"
前置知识:第06章 游戏性能监控(监控和日志的关系)
阅读指南(初学者必看)
为什么你需要学习游戏日志系统?
没有日志 = 黑盒运维:
- 线上出 Bug → 不知道哪行代码出错
- 玩家反馈"金币少了" → 没有操作记录无法查证
- 服务器崩溃 → 不知道崩溃前发生了什么
学完本章,你能回答:
- 日志级别怎么划分?每级记什么?
- JSON 日志格式怎么设计?
- ELK(Elasticsearch + Logstash + Kibana)怎么搭建?
- Loki 轻量日志方案怎么用?
- 游戏日志有什么特殊需求?
本文结构
第一部分:日志规范
第二部分:ELK 日志系统
第三部分:Loki 轻量方案
第四部分:游戏日志特殊需求
第五部分:日志分析与查询
一、日志规范
生活类比:日志就像"飞机黑匣子"——平时没人看,出事了就是最重要的证据。
日志级别
ERROR ── 影响功能的错误,需要立即处理
例:数据库连接失败、支付回调异常、OOM
WARN ── 潜在问题,不影响当前功能但需要关注
例:GC 耗时过长、队列接近满、重试成功
INFO ── 关键业务操作
例:玩家登录、创建房间、战斗结算、充值成功
DEBUG ── 调试信息,生产环境关闭
例:消息收发详情、状态变化、缓存命中/未命中
TRACE ── 更细的调试信息(几乎不用)
JSON 日志格式
{
"timestamp": "2026-04-23T16:30:00.123Z",
"level": "INFO",
"service": "room-server",
"traceId": "abc123",
"spanId": "def456",
"userId": "player_123",
"roomId": "room_456",
"action": "battle_start",
"duration": 1500,
"message": "Battle started"
}
结构化日志格式(带上下文)
{
"timestamp": "2024-01-15T14:23:45.123Z",
"level": "INFO",
"traceId": "a1b2c3d4e5f6",
"service": "game-server",
"thread": "http-nio-8080-exec-1",
"logger": "com.game.service.PlayerService",
"message": "玩家登录成功",
"context": {
"playerId": "12345",
"ip": "192.168.1.1",
"device": "iPhone14,2"
}
}
日志脱敏规则
| 数据类型 | 脱敏方式 | 示例 |
|---|---|---|
| 密码 | *** | pass*** |
| 手机号 | 中间4位* | 138****1234 |
| 身份证 | 中间8位* | 310***********1234 |
| 银行卡 | 保留后4位 | ************1234 |
| IP | 最后一节* | 192.168.1.* |
| Token | 截断 | eyJhbG*** |
日志内容禁忌
- 不要打印密码、Token、身份证号等敏感信息
- 不要打印超大对象(一次日志几 MB)
- 不要在循环中打印 INFO 级别日志
- 异常日志要打印堆栈,不要只打印 message
二、ELK 日志系统
生活类比:ELK 就像"图书馆"——Elasticsearch 是书架(存储和搜索),Logstash 是图书管理员(整理和分类),Kibana 是检索系统(可视化查询)。
日志采集流程
应用 → Filebeat → Logstash → Elasticsearch → Kibana
采集 清洗转换 存储搜索 可视化查询
Logstash 配置
# logstash-game.conf
input {
beats {
port => 5044
}
}
filter {
json {
source => "message"
}
# 添加地理位置
geoip {
source => "clientIp"
}
# 日志级别转换
mutate {
uppercase => ["level"]
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "game-logs-%{+YYYY.MM.dd}"
}
}
ELK Docker Compose
version: '3.8'
services:
elasticsearch:
image: elasticsearch:8.11.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
logstash:
image: logstash:8.11.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro
ports:
- "5044:5044"
depends_on:
- elasticsearch
kibana:
image: kibana:8.11.0
ports:
- "5601:5601"
depends_on:
- elasticsearch
volumes:
es_data:
Kibana 常用查询
# 查某个玩家的所有操作
userId: "player_123"
# 查所有 ERROR 级别日志
level: "ERROR"
# 查某个房间的战斗日志
roomId: "room_456" AND action: "battle_*"
# 查最近 1 小时的慢请求
duration: >1000 AND @timestamp: [now-1h TO now]
# 查支付相关日志
action: "payment_*" AND level: "ERROR"
三、Loki 轻量方案
Loki 是 Grafana Labs 推出的轻量级日志系统,只索引标签不索引日志内容,成本更低。
# docker-compose.yml for Loki
services:
loki:
image: grafana/loki:2.9.0
ports:
- "3100:3100"
volumes:
- ./loki-config.yml:/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:2.9.0
volumes:
- ./promtail-config.yml:/etc/promtail/config.yml
- /var/log:/var/log:ro
Promtail 配置:
server:
http_listen_port: 9080
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: game-server-logs
static_configs:
- targets:
- localhost
labels:
job: game-server
__path__: /var/log/game/*.log
Grafana 中查询 Loki 日志:
{job="game-server"} |= "ERROR"
{job="game-server"} |= "playerId=12345"
{job="game-server"} | json | level="ERROR" | line_format "{{.message}}"
四、游戏日志特殊需求
游戏日志分类
| 日志类型 | 保留时间 | 存储位置 | 查询频率 |
|---|---|---|---|
| 系统日志 | 30 天 | ES | 高(排查问题) |
| 业务日志 | 90 天 | ES + HDFS | 中(运营分析) |
| 战斗日志 | 30 天 | ES | 高(玩家投诉) |
| 审计日志 | 180 天 | ES + 归档 | 低(合规审计) |
| 性能日志 | 7 天 | Prometheus | 高(实时监控) |
游戏日志数据量
10万在线 × 每秒10条日志 = 100万条/秒
每条日志约 500 字节 = 500MB/秒 = 43TB/天
优化策略:
1. 分级别存储(ERROR 全存,INFO 采样 10%)
2. 分索引(按服务/日期分开)
3. 冷热分离(7天内热数据 SSD,7天后冷数据 HDD)
4. 生命周期管理(30天后自动删除或归档)
关键业务日志设计
// 玩家登录日志
log.info(JSON.stringify({
action: "player_login",
userId: player.getId(),
deviceId: player.getDeviceId(),
ip: player.getIp(),
platform: player.getPlatform(),
version: player.getClientVersion(),
loginTime: System.currentTimeMillis()
}));
// 战斗结算日志
log.info(JSON.stringify({
action: "battle_settle",
roomId: room.getId(),
battleId: battle.getId(),
duration: battle.getDuration(),
players: battle.getPlayers().stream()
.map(p -> Map.of("id", p.getId(), "result", p.getResult(), "scoreChange", p.getScoreChange()))
.collect(toList()),
rewards: battle.getRewards()
}));
// 充值日志(最重要,涉及钱)
log.info(JSON.stringify({
action: "payment_success",
orderId: order.getId(),
userId: order.getUserId(),
amount: order.getAmount(),
currency: order.getCurrency(),
channelId: order.getChannelId(),
productId: order.getProductId(),
gameId: order.getGameId()
}));
五、日志分析与查询
常用分析场景
1. 实时在线人数
→ 按 1 分钟聚合 count(userId)
2. 每日活跃用户(DAU)
→ 按 1 天聚合 count(distinct userId)
3. 玩家流失分析
→ 最后登录时间 > 7 天的用户
4. 战斗平均时长
→ avg(duration) WHERE action = "battle_settle"
5. 充值金额统计
→ sum(amount) WHERE action = "payment_success"
自问自答
Q1:日志该记多少?
原则:关键操作必须记(登录/支付/战斗),调试信息可开关。太多影响性能,太少排查不了问题。建议:ERROR 全记,INFO 记关键业务,DEBUG 开关控制。
Q2:日志打到文件还是直接发 ES?
推荐打到文件 + Filebeat 采集。原因:1. 应用和日志系统解耦(ES 挂了不影响应用);2. 文件有本地缓存不怕丢;3. Filebeat 负责转发和重试。
Q3:JSON 日志比文本日志好在哪里?
JSON 可直接被 ES 解析和索引,查询效率高。文本日志需要写 Grok 正则解析,容易出错且性能差。
Q4:日志量太大 ES 存不下怎么办?
- 分级别保留(ERROR 30天,INFO 7天);2. 冷热分离(热数据 SSD,冷数据归档到 HDFS/S3);3. 采样(非关键日志只记 10%)。
Q5:日志和监控有什么区别?
日志记录"事件"(发生了什么),监控记录"指标"(数值趋势)。日志用于查原因,监控用于看趋势。两者互补。
Q6:日志太多怎么查? A: 加搜索!Kibana!按时间、关键字、日志级别查!使用结构化日志后,可以按字段精确过滤。
实践任务
- 制定日志规范:定义日志级别、格式、脱敏规则
- 搭建 ELK:Docker Compose 一键启动 ES + Logstash + Kibana
- 接入游戏日志:应用输出 JSON 日志 → Filebeat → ELK
- 创建 Kibana 看板:实时在线、战斗统计、错误率
- 日志告警:ERROR 日志 > 10条/分钟 触发告警
- 对比 Loki:搭建 Loki + Grafana,对比 ELK 的资源占用
初学者常见错误
错误1:日志没有统一规范
问题: 有的打印中文、有的打印英文、有的用 JSON、有的用文本。 解决: 制定团队日志规范,使用结构化日志,统一字段命名。
错误2:没有日志分级存储
问题: 所有日志存 30 天,存储成本爆炸。 解决: ERROR 存 30 天,INFO 存 7 天,DEBUG 存 1 天。
错误3:日志量太大导致应用卡顿
问题: 同步打印日志阻塞业务线程。 解决: 使用异步日志(Logback AsyncAppender),批量刷盘。
与其他章节的关联
| 本节内容 | 关联章节 | 关联点 |
|---|---|---|
| 监控 | 第06章 游戏性能监控 | 日志和监控互补 |
| 告警 | 第08章 告警与应急响应 | 日志异常触发告警 |
| CI/CD | 第01章 CI/CD 流水线 | 日志规范纳入代码检查 |
| Docker | 第02章 Docker 容器化 | 容器日志采集方案 |
| JVM 调优 | 3_1 第09章 JVM 调优实战 | GC 日志分析 |
| 全链路追踪 | 第06章 游戏性能监控 | Trace ID 贯穿日志 |
⬅️ 上一章:游戏性能监控 | ➡️ 下一章:告警与应急响应