# 日志与主脚本指南 > 模块:`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 收发数据日志 |