孤儿分支,只含 docs/。内容包括: - 项目总览 / 主脚本编写指南 / 日志指南 - 上传成绩开发文档 / UpsertUserAll 踩坑清单(28 条) - 真机 Hook 对照报告 / 实跑验证记录 - 购买功能票流程(含被服务端拒绝的排查过程) · 扫码登录协议技术规范
381 lines
12 KiB
Markdown
381 lines
12 KiB
Markdown
# 日志与主脚本指南
|
||
|
||
> 模块:`api/log_setup.py` | 模式:INFO / DEBUG
|
||
|
||
---
|
||
|
||
## 一、一句话理解
|
||
|
||
**api 调用会被自动识别并记录,主脚本不用写任何日志代码。**
|
||
|
||
主脚本要写的只有两件:
|
||
|
||
```python
|
||
setup_logging("脚本名") # 开头一句
|
||
... # 业务代码,照常调 api
|
||
teardown_logging() # 结尾一句(不写也会在退出时自动收尾)
|
||
```
|
||
|
||
---
|
||
|
||
## 二、自动日志长什么样
|
||
|
||
调用了 `ParseQRCode` 与 `UserPreview`,日志自动产出:
|
||
|
||
```
|
||
==================================================================
|
||
日志会话开始 —— main
|
||
==================================================================
|
||
主脚本 : main
|
||
开始时间 : 2026-10-05 17:37:42
|
||
日志模式 : INFO(仅运行情况与结果)
|
||
Python : 3.12.6 (CPython AMD64)
|
||
工作目录 : D:\SDGBSource\杂乱的api\wahappy-v0-goal
|
||
已挂载 api : 60 个(自动跟踪,主脚本无需写日志代码)
|
||
当次日志 : ...\logs\latest.log
|
||
日期日志 : ...\logs\2026-10-05.log
|
||
==================================================================
|
||
|
||
=== ParseQRCode 开始运行 ===
|
||
[接口说明] 解析二维码并换取 userId 与 token。
|
||
[位置参数] SGWCMAID2610022219463A297AF4CAB863406AA91ED2D29FF3BE20BF1113AC03095D97BD1573EDD691B6
|
||
解析二维码登录:chip_id=TESTCLIENT1
|
||
发送登录请求:timestamp=261005173742 host=ai.sys-allnet.cn/wc_aime/api/get_data
|
||
扫码登录成功:user_id=12345678 token=t3WikM5n...(长度 20)
|
||
[运行结果] 成功 tuple(2 项)(耗时 412 ms)
|
||
=== ParseQRCode 运行结束 ===
|
||
|
||
=== UserPreview 开始运行 ===
|
||
[接口说明] 获取用户预览信息。
|
||
[位置参数] 12345678, 'TESTTOKEN0000000000'
|
||
[关键字参数] client_id='TESTCLIENT1'
|
||
获取用户预览:user_id=12345678 client_id=TESTCLIENT1
|
||
用户预览获取完成:userName=☆Astral☆ playerRating=15000 isLogin=False isNetMember=1
|
||
[运行结果] 成功 UserPreviewResponseVO(22 字段)(耗时 388 ms)
|
||
=== UserPreview 运行结束 ===
|
||
|
||
==================================================================
|
||
本次运行调用的 api
|
||
==================================================================
|
||
1. ParseQRCode
|
||
2. UserPreview
|
||
==================================================================
|
||
日志会话结束 —— main
|
||
==================================================================
|
||
```
|
||
|
||
自动带出来的东西:
|
||
|
||
| 自动内容 | 来源 |
|
||
|---|---|
|
||
| `===函数名 开始运行===` / `===函数名 运行结束===` | 导入时自动包装 |
|
||
| `[接口说明]` | 函数 docstring 首行 |
|
||
| `[位置参数]` / `[关键字参数]` | 本次调用传入的值 |
|
||
| `[运行结果]` | 成功摘要或异常类型,附耗时 |
|
||
| 会话结束的 **api 汇总清单** | 运行时记录调用顺序 |
|
||
|
||
---
|
||
|
||
## 三、两种模式
|
||
|
||
| 模式 | 你能看到什么 |
|
||
|---|---|
|
||
| **INFO**(默认) | 开始 → 接口说明 → 参数 → 结果 → 结束 |
|
||
| **DEBUG** | 上面全部 **+** 请求 URL、请求头、**请求体 JSON 全文**、状态码、耗时、响应头、**响应体 JSON 全文** |
|
||
|
||
切换方式:改 `config.py` 一行。
|
||
|
||
```python
|
||
LOG_LEVEL = "info" # 日常运行
|
||
LOG_LEVEL = "debug" # 排查协议问题
|
||
```
|
||
|
||
DEBUG 下额外多出:
|
||
|
||
```
|
||
-> 请求 GetGameSettingApi
|
||
URL : https://maimai-gm.wahlap.com:42081/Maimai2Servlet/mE2s3Jhd/83a5d5a20b062c5c2ad817460b8f7f76
|
||
请求头 : {"Content-Type":"application/json","charset":"UTF-8","Mai-Encoding":"1.55","User-Agent":"83a5...#TESTCLIENT1","Accept-Encoding":"","Content-Encoding":"deflate"}
|
||
请求体 :
|
||
{
|
||
"placeId": 1000,
|
||
"clientId": "TESTCLIENT1"
|
||
}
|
||
|
||
<- 响应 GetGameSettingApi
|
||
状态码 : 200
|
||
耗时 : 312 ms
|
||
响应头 : {"Date":"...","Content-Type":"application/json"}
|
||
响应体 :
|
||
{
|
||
"isAouAccession": false,
|
||
"gameSetting": {
|
||
"isMaintenance": false,
|
||
"requestInterval": 1200,
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
> DEBUG 模式下**完整输出,不折叠、不截断** —— 调试时就是要能逐条核对
|
||
> 1000+ 条曲目、104 条地图。如需限制,改 `config.py` 的 `LOG_VALUE_MAX`。
|
||
|
||
---
|
||
|
||
## 四、主脚本怎么写
|
||
|
||
### 4.1 ★ 核心原则:**不要为每个 api 调用写日志**
|
||
|
||
日志全部写在 api 脚本内部。主脚本**只需要**:
|
||
|
||
1. 开头 `setup_logging("脚本名")`
|
||
2. 照常调 api 函数
|
||
3. 结尾 `teardown_logging()`
|
||
|
||
**不要**在每次调用前写 `log_section`、调用后写 `log_info` —— 那是多余的,
|
||
api 函数自己会打印 `=== 函数名 开始运行 ===` / `[运行结果]` / `=== 函数名 运行结束 ===`。
|
||
|
||
> 完整范例见工程根目录的 `upsert.py`:整条「扫码 → 预览 → 登录 → 结算上传 → 登出」
|
||
> 只有四行业务代码,**一行日志都没写**。
|
||
|
||
### 4.2 最小可用版本
|
||
|
||
```python
|
||
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
import sys
|
||
from api import (setup_logging, teardown_logging,
|
||
ParseQRCode, UserPreview, UserLogin, UserLogout, LogoutType)
|
||
|
||
def main() -> int:
|
||
setup_logging("我的脚本") # ← 只需要这一句
|
||
try:
|
||
user_id, token = ParseQRCode(sys.argv[1])
|
||
UserPreview(user_id, token)
|
||
login = UserLogin(user_id, token)
|
||
UserLogout(user_id, token, login.loginDateTime,
|
||
logout_type=LogoutType.TestIn)
|
||
return 0
|
||
finally:
|
||
teardown_logging()
|
||
```
|
||
|
||
**就这些。** 没有装饰器,没有埋点,没有 log_section。
|
||
|
||
### 4.3 嵌套调用也会自动记日志
|
||
|
||
`UpsertUserAll` 内部会调 `fetch_upsert_snapshot` / `UserNewItemList` / `UserData`,
|
||
这些**内部调用同样会打印自己的开始/结束行**(见 `upsert_user_all._resolve_api_func`),
|
||
所以结算链路的日志层次是完整嵌套的:
|
||
|
||
```
|
||
=== UpsertUserAll 开始运行 ===
|
||
[接口说明] 上传一局成绩(UpsertUserAllApi)—— 一个函数跑完整条结算链路。
|
||
=== fetch_upsert_snapshot 开始运行 ===
|
||
...
|
||
=== fetch_upsert_snapshot 运行结束 ===
|
||
=== UserNewItemList 开始运行 ===
|
||
...
|
||
=== UserNewItemList 运行结束 ===
|
||
...
|
||
=== UpsertUserAll 运行结束 ===
|
||
```
|
||
|
||
### 4.4 想额外记点自己的话(可选)
|
||
|
||
不写也完全不影响:
|
||
|
||
```python
|
||
from api import log_info, log_warning, log_error, log_section, log_field
|
||
|
||
log_section("阶段标题") # 带分隔线的分节
|
||
log_info("普通信息") # 就当成「会进日志文件的 print」
|
||
log_warning("需要留意的情况")
|
||
log_error("出错了")
|
||
log_field("预览数据", preview) # INFO 一行概要;DEBUG 展开完整 JSON
|
||
```
|
||
|
||
### 4.5 ★ 保证登出
|
||
|
||
账号若停留在 `isLogin=True`,下次登录会 `returnCode=100`。用这个模板:
|
||
|
||
```python
|
||
session = {"user_id": 0, "token": "", "login_dt": 0, "logged_in": False}
|
||
try:
|
||
...
|
||
login = UserLogin(user_id, token)
|
||
session.update(login_dt=login.loginDateTime, logged_in=True)
|
||
...
|
||
UserLogout(...)
|
||
session["logged_in"] = False
|
||
finally:
|
||
if session["logged_in"]:
|
||
log_warning("[收尾] 异常中断,强制登出 ...")
|
||
try:
|
||
UserLogout(session["user_id"], session["token"], session["login_dt"],
|
||
logout_type=LogoutType.TestIn)
|
||
except Exception as e:
|
||
log_error(f"[收尾] 强制登出失败:{e}")
|
||
```
|
||
|
||
---
|
||
|
||
## 五、api 脚本怎么写
|
||
|
||
**不用加装饰器**,直接定义函数就行:
|
||
|
||
```python
|
||
# api/userpreview.py
|
||
from .log_setup import log_info, log_debug, log_request, log_response
|
||
from .maimai_net import request_dict
|
||
|
||
def UserPreview(user_id: int, token: str, *, client_id: str = CLIENT_ID):
|
||
"""获取用户预览信息。""" # ← 首行成为 [接口说明]
|
||
log_info(f"获取用户预览:user_id={user_id}") # 可选
|
||
|
||
raw = request_dict("GetUserPreviewApi", req, user_id=user_id, client_id=client_id)
|
||
# ↑ maimai_net 内部已经调了 log_request / log_response,
|
||
# DEBUG 模式下会自动输出收发数据,不用在这里重复写
|
||
|
||
return parse(raw)
|
||
```
|
||
|
||
### 新增 api 必须登记
|
||
|
||
写完新函数后,在 `api/__init__.py` 做两步:
|
||
|
||
```python
|
||
# 1) 导入
|
||
from .xxx import MyNewApi
|
||
|
||
# 2) 加进跟踪名单
|
||
_API_FUNCS = (
|
||
"ParseQRCode",
|
||
"UserPreview",
|
||
...
|
||
"MyNewApi", # ← 加这里
|
||
)
|
||
```
|
||
|
||
`autowrap(globals(), _API_FUNCS)` 会在导入时自动给它套上日志外壳。
|
||
**漏了这步,该函数就不会输出 `===开始运行===`。**
|
||
|
||
---
|
||
|
||
## 六、完整 API 参考
|
||
|
||
### 6.1 会话(主脚本用)
|
||
|
||
```python
|
||
setup_logging(script_name, level=None) -> str
|
||
# 初始化会话:确定模式、打开两个日志文件、打印横幅
|
||
# level=None 时读 config.LOG_LEVEL
|
||
# 同一进程重复调用安全(只初始化一次)
|
||
|
||
teardown_logging()
|
||
# 收尾:打印「本次运行调用的 api」清单 + 结束横幅 + 关文件
|
||
# 不调用也会在退出时自动执行
|
||
```
|
||
|
||
### 6.2 自动跟踪
|
||
|
||
```python
|
||
autowrap(namespace, names) -> list[str] # api/__init__.py 调用,一般不用手写
|
||
get_called_apis() -> list[str] # 本次会话调用过的 api,按首次调用顺序
|
||
```
|
||
|
||
### 6.3 模式
|
||
|
||
```python
|
||
get_level() -> "info" | "debug"
|
||
set_level(v) -> "info" | "debug"
|
||
is_debug() -> bool
|
||
LEVEL_INFO = "info"
|
||
LEVEL_DEBUG = "debug"
|
||
```
|
||
|
||
### 6.4 流程日志(INFO 可见)
|
||
|
||
```python
|
||
log_info("普通信息")
|
||
log_step("扫码登录") # → [步骤] 扫码登录
|
||
log_step("扫码登录", index=1, total=5) # → [步骤 1/5] 扫码登录
|
||
log_result("user_id=123") # → [结果] user_id=123
|
||
log_warning("账号已登录") # → [警告] ...
|
||
log_error("网络不可达") # → [错误] ...
|
||
log_section("阶段标题") # 带分隔线的分节
|
||
log_field("预览数据", preview) # INFO 一行;DEBUG 多行展开
|
||
log_field("原始响应", text, debug_only=True) # 只在 DEBUG 出现
|
||
```
|
||
|
||
### 6.5 收发数据日志(DEBUG 专属)
|
||
|
||
```python
|
||
log_debug("只在 DEBUG 出现")
|
||
log_request(url="https://...", body=req, headers=hdr, api_name="XxxApi")
|
||
log_response(status=200, body=raw, headers=hdr, api_name="XxxApi", elapsed_ms=412)
|
||
log_payload("解密后明文", text)
|
||
```
|
||
|
||
**INFO 模式下这些函数第一行就 `return`**,不做序列化、不拼字符串,可以放心留在热路径里。
|
||
|
||
### 6.6 兼容写法
|
||
|
||
```python
|
||
from api import api_log, get_logger
|
||
|
||
@api_log # 仍可用(会自动跳过重复包装)
|
||
def MyFunc(): ...
|
||
|
||
logger = get_logger()
|
||
logger.info("..."); logger.warning("..."); logger.debug("...")
|
||
```
|
||
|
||
---
|
||
|
||
## 七、日志文件
|
||
|
||
| 文件 | 打开模式 | 说明 |
|
||
|---|---|---|
|
||
| `logs/latest.log` | `w`(覆盖) | 每次运行重写,**只保留最近一次** |
|
||
| `logs/YYYY-MM-DD.log` | `a`(追加) | **当日所有运行累积**,按天分文件 |
|
||
|
||
两者**同时写入**。每行都带 `[YYYY-MM-DD HH:MM:SS]` 时间戳。
|
||
|
||
DEBUG 的收发数据同样写进文件,可以跑一次 DEBUG 再翻 `latest.log`。
|
||
|
||
---
|
||
|
||
## 八、常见问题
|
||
|
||
**Q: 为什么 api 调用没有 `===开始运行===`?**
|
||
A: 该函数没登记在 `api/__init__.py` 的 `_API_FUNCS` 里。补上即可。
|
||
|
||
**Q: 为什么 `log_debug` 什么都没输出?**
|
||
A: 当前是 INFO 模式。改 `config.py` 的 `LOG_LEVEL = "debug"`。
|
||
|
||
**Q: 改了 config.py 没生效?**
|
||
A: 模式在 `setup_logging()` 时读取。之前已初始化过就需重启脚本,
|
||
或显式 `setup_logging("名字", level="debug")`。
|
||
|
||
**Q: 主脚本没调用 `setup_logging` 会怎样?**
|
||
A: 仍会打印到控制台(自动跟踪在导入时就生效),但不写文件、没有横幅与汇总。
|
||
|
||
**Q: 写测试 / 单独跑 api 脚本时不想要日志?**
|
||
A: 不调用 `setup_logging` 就不会开文件;控制台输出仍在。
|
||
|
||
**Q: 能完全关掉吗?**
|
||
A: 可以 `set_level("info")` 抑制 DEBUG 部分。要连控制台也静音需改 `_emit` 的 `console` 参数。
|
||
|
||
---
|
||
|
||
## 九、相关文件
|
||
|
||
| 文件 | 作用 |
|
||
|---|---|
|
||
| `api/log_setup.py` | 日志模块本体(含自动包装引擎) |
|
||
| `api/__init__.py` | 统一导出 + `_API_FUNCS` 登记表 |
|
||
| `config.py` | `LOG_LEVEL` / `LOG_VALUE_MAX` 开关 |
|
||
| `upsert.py` | 主脚本范例(含完整的登出兜底) |
|
||
| `api/maimai_net.py` | 网络层,已接入 DEBUG 收发数据日志 |
|