日志
日志格式
- 系统采用统一的日志记录格式
- 日志配置通过 config 读取 log.yaml 中获得,分为:
- formatters: 日志输出的文本格式与时间格式
- handlers: 日志处理器,定义日志的输出方式,可绑定对应的 formatters 和日志等级
- filter: 日志过滤器,可以进行日志级别、名称等自定义过滤逻辑
- loggers: 日志记录器,定义日志的入口配置,指定日志的最低等级与处理器
- 日志记录器分为一个个 logger,当日志产生后,logger 会调用它所分配的 handler,handler 中会将日志格式化成指定的 formatter,并调用对应的 filter,最后输出
- 日志记录器设置 propagate: no 阻止日志向上传播,防止重复记录
- 本系统有两个 handler 分别对应控制台和数据库输出,一个或多个 logger 分配一个 filter
日志加载
- 当配置重载调用 load_config() 函数时,若日志相关配置更新(通过存储旧哈希值判断),会调用 reset_log 清除所有的日志设置,然后通过 dictConfig() 加载 config 中的日志配置项,并为日志添加过滤器
日志处理器
- 设置了控制台日志处理器与数据库日志处理器
- 二者格式均为
%(asctime)s|%(levelname)s|%(name)s|%(event)s|%(message)s - 二者均根据 SOURCE_DICT 中配置的内容将日志记录器的 source 字段转换为对应的字段(控制台统一 7 个字符,数据库为 6-7 个,去空格)
- 转换后三个 uvicorn 处理器都变成了 website,达到合并日志的效果
- 控制台日志处理器去除原日志中的颜色,并按照
debug:黄``info:黑``error:红的颜色输出到控制台(腾讯服务器图形界面显示不了灰色??) - 数据库日志处理器将 level、name、event、message 字段插入写入队列,在队列中增加 hasTextIndex 字段,再写入 Mongodb 数据库中(直接写入无法写入连接数据库、初始化等日志)
日志过滤
- UvicornFilter
- uvicorn 拥有三个日志记录器: uvicorn,uvicorn.access,uvicorn.error
- 在 set_log() 中覆盖其日志记录器,添加日志格式与处理器,在处理器中添加过滤器 UvicornFilter
- 过滤器判断日志中是否存在 args 参数
- 如果存在则是路径访问日志如
INFO: 127.0.0.1:5161 - "GET / HTTP/1.1" 404 Not Found - 如果不存在则把 event 设置为“运行日志”,输出原参数
- 如果存在则是路径访问日志如
- 过滤器提取其中的
ip:127.0.0.1:5161method:GETpath:/http_version:1.1status_code:404 - 将 status_code 通过 http.yaml 配置转换成中文,作为参数 event 写入日志
- 其他四个字段处理成如下格式存入 message 参数:
method[ip]path-> HTTP/http_version
- ServerFilter
- ssh 连接会产生空行,故添加过滤器 ServerFilter 来消除空行
- 将
debug1:的调试日志前缀去除,分为 debug 和 info - 控制台将输出等级均设置成 debug
- NapcatFilter
- 清除了空行
- 将原来是 debug 等级的日志(以'[]'开头)更改为 debug(从控制台中读取的全部都是 info)
- 从消息中提取 level 和 info 信息作为 level 和 message 字段
- 在 其他服务配置中可以找到除 napcat 外 xiaomiqiu、Prometheus 系软件的日志情况及处理结果
日志格式
- 日志格式包括
- asctime:时间
- level:DEBUG,INFO,ERROR
- source:七位英文
- event:四位中文
- message:日志信息
- 日志的记录时需主动添加 event 字段:
loggers["system"].error("abc", extra={"event": "配置读取"}) - 以下为日志所有的 source、event 与 message 格式(带括号为变量,level 用简写)
| source | level | event | message | location |
|---|---|---|---|---|
| system | D | 模块加载 | [加载]-> (模块) | reloader |
| system | D | 模块加载 | [重载]-> (模块) | reloader |
| system | E | 模块加载 | [加载]-> (模块): (错误) | reloader |
| system | E | 模块加载 | [重载]-> (模块): (错误) | reloader |
| system | D | 配置更新 | [配置数据]更新 | config |
| system | D | 配置更新 | [配置数据]写入-> (文件) | config |
| system | D | 配置更新 | [配置数据]临时数据保存成功 | config |
| system | E | 配置更新 | [配置数据]日志错误-> (异常) | config |
| system | E | 配置更新 | [配置数据]自动更新异常-> (异常) | config |
| system | D | 运行日志 | [数据库]连接成功-> (数据库) | config |
| system | D | 索引创建 | (L1) | config |
| system | E | 索引创建 | [索引创建](名称) 失败-> (异常) | config |
| system | E | 运行失败 | [初始化]失败-> (错误) | main |
| system | D | 运行日志 | [任务]被取消-> (任务) | main |
| system | E | 运行失败 | [任务](任务) 异常-> (异常) | main,log |
| system | D | 运行日志 | [信号]收到退出信号-> (信号) | main |
| system | D | 错误堆栈 | [消息处理/定时任务/后端运行]-> 堆栈: (堆栈)\n变量: (变量) | msg_process,app,config |
| system | E | 定时任务 | [定时任务](任务) 异常-> (E1) | config |
| server | D/I | 运行日志 | (L2) | command |
| server | E | 运行失败 | [服务器](异常) | command |
| napcat | D/I/E | 运行日志 | (L3) | log |
| webstie | I | 运行日志 | (L4) | app |
| webstie | I | (L4) | (L4) | app |
| website | E | 运行失败 | (E2) | (ip): (路径) | app |
| website | D/I/E | 网页日志 | (L5) | app,cab |
| adapter | D | 消息接收 | [接收]⌈(平台)⌋(数据) | xx_receive |
| adapter | D | 消息接收 | [回调]⌈(平台)⌋-> 成功: (数据) | LR232/WECHAT_receive |
| adapter | D | 消息发送 | [令牌刷新]⌈(平台)⌋-> (令牌): (有效期) | access_token |
| adapter | D | 消息发送 | [(消息发送类型)]⌈(平台)⌋-> (返回值) | (数据) | xx_dispatch |
| adapter | D | 消息去重 | ⌈(平台)⌋(数据) | LR232/WECHAT_receive |
| adapter | E | 消息超时 | ⌈(平台)⌋(数据) | WECHAT_receive |
| message | D | 消息处理 | [消息清理]完成-> 共清理 (数量) 条消息 | msg_pool |
| message | D | 消息处理 | (消息体) | msg_process |
| message | I | 消息处理 | [(消息类型)]⌈(平台)⌋处理/发送-> (用户): (内容) | msg_process,msg_send |
| message | E | 消息处理 | [消息解析]失败-> 无法解析表情 ID: (id) | msg |
| message | E | 消息处理 | [消息处理]错误-> (消息): (E3) | msg_process |
| message | I | 消息分析 | [(指令类型)]⌈(平台)⌋(用户)-> (接收): (发送) | system |
| message | D/E | 文件处理 | (L6) | file |
| message | D/E | 容器处理 | (L7) | check |
参数说明
基本变量
- 模块: logic 中的文件名
- 文件: storage/yml 下的文件
- 数据库: mysql 和 mongodb
- 名称: idx_n 自建索引名
- 任务: 循环运行的各任务
- 平台: 四个平台
- 消息类型: 见消息类型
- 消息发送类型: 见消息类型
- 指令类型: 所有页面操作与消息指令
日志信息
- L1:索引相关
[索引创建](名称) 已存在 text 索引-> 跳过
[索引创建](名称) 已存在-> (键): (选项)
[索引删除](名称) 名称冲突-> (键): (选项)
[索引删除](名称) 键值冲突-> (键): (选项)
[索引创建](名称) 成功-> (键): (选项)
[索引创建]完成
- L2:ssh 连接日志
- L3:napcat 日志
- L4:uvicorn 日志
- 请求被格式化为
method[IP]path-> HTTP/1.1,event 被设置为返回码的中文(http.yaml),如 200-请求成功 - 其他正常输出,event 被设置为"运行日志"
- 请求被格式化为
- L5:后端页面产生日志
I:
[网页服务]启动 app
[指令更新](用户)-> (指令) command
[数据库更新](用户)-> (数据) database
[文件上传](用户)-> (文件) file
[文件删除](用户)-> (文件) file
[文件夹上传](用户)-> (文件) file
[文件夹新建](用户)-> (文件) file
[文件重命名](用户)-> (旧): (新) file
[文件移动](用户)-> (旧): (新) file
[新增/删除评论](用户)-> (功能): (评论) panel
[时间点创建](用户)-> (内容) timeline
[时间点更新](用户)-> (内容) timeline
[时间点删除](用户)-> (序号) timeline
[用户组更新](用户)-> 私聊: (用户组) | 群聊: (群组) user
[wiki 创建](用户)-> (内容) wiki
[wiki 更新](用户)-> (内容) wiki
[wiki 名更新](用户)-> (内容) wiki
[wiki 排序](用户)-> (内容) wiki
[wiki 组排序](用户)-> (内容) wiki
D:
[日志页]查询字段-> (内容) log
[日志页]查询结果-> (内容) log
E:
[指令页]更新错误-> (错误) command
[数据页]获取错误-> (错误) database
[数据页]更新错误-> (错误) database
[数据页]新增行错误-> (错误) database
[数据页]删除行错误-> (错误) database
[数据页]ws 连接错误-> (错误) database
[文件页]删除失败-> (错误) file
[主页]搜索失败-> (搜索): (错误) home
[主页]地图获取失败-> (地图z/x/y): (错误) home
[笑话]写入失败-> (错误) joke
[IP](ip)-> 封禁 10 分钟 joke
[笑话]获取失败-> (错误) joke
[日志页]查询错误-> (错误) log
[资源页]获取失败-> 无法访问上层文件或非法路径 static
[资源页]获取失败-> 文件未找到 | 位置: (路径) static
[用户页]更新失败-> (错误) user
- L6:文件操作
D:
[文图转换]转换成功-> 图片: (图片)
[文件下载]下载成功-> 文件: (文件)
[音频转换]pcm 写入-> 音频: (音频)
[音频转换]pcm 转 silk-> 音频: (音频)
[音频转换]silk 已存在-> 音频: (音频)
[图片压缩]图片无需压缩-> 大小: (大小)
[图片压缩]缩略图压缩完成-> 大小: (大小) | 分辨率: (长x宽)
[图片压缩]压缩完成-> 大小: (大小) | 品质: (品质)
[图片压缩]压缩继续-> 大小: (大小) | 品质: (品质)
[图片压缩]图片已存在-> 图片: (图片)
[音频压缩]音频无需压缩-> 大小: (大小)
[音频压缩]压缩完成-> 大小: (大小) | 比特率: (比特率)
[音频压缩]压缩继续-> 大小: (大小) | 比特率: (比特率)
[音频压缩]音频已存在-> 音频: (音频)
[视频压缩]视频无需压缩-> 大小: (大小)
[视频压缩]压缩完成-> 大小: (大小) | 比特率: (比特率)
[视频压缩]压缩继续-> 大小: (大小) | 比特率: (比特率)
[视频压缩]视频已存在-> 视频: (视频)
E:
[图片压缩]压缩失败-> 错误: 无法压缩图片至 (大小)
[音频转换]silk 转换失败-> 错误: (错误)
[音频压缩]probe 错误-> 错误: (错误)
[音频压缩]ffmpeg 错误-> 错误: (错误)
[音频压缩]压缩失败-> 错误: 无法压缩音频至 (大小)
[音频转换]ffmpeg 错误-> 错误: (错误)
[视频压缩]probe 错误-> 错误: (错误)
[视频压缩]ffmpeg 错误-> 错误: (错误)
[视频压缩]压缩失败-> 错误: 无法压缩视频至 (大小)
[文件删除]临时文件删除失败-> (文件): (错误)
[文件删除]旧文件删除失败-> (文件): (错误)
[文件删除]延迟删除失败-> (文件): (错误)
- L7:容器操作 D: [网络检查]连接成功 [容器重启]重启成功 E: [网络检查]连接失败-> (异常)
错误信息及说明
- 以下三条异常存在反复写入等问题,项目初始化时产生,直接用 print
[配置数据]yaml 文件格式错误-> (文件名): (异常)在日志记录器加载前完成,无法调用日志[数据库]连接失败-> Mongodb: (异常)无法写入 Mongodb 数据库[数据库]写入失败-> Mongodb: (异常)会反复写入 Mongodb 数据库
- 以下错误包含错误日志以及未考虑到的程序异常
- E1:定时任务捕获的错误
[令牌刷新]⌈(平台)⌋请求失败-> 无 access_token: (数据) - E2:uvicorn 捕获的错误
[消息接收]⌈(平台)⌋请求失败-> 未定义的 msg_type/t/post_type/MsgType: (消息)
[消息接收]⌈(平台)⌋请求失败-> 不存在的 op 码: (消息)
[消息接收]⌈(平台)⌋请求失败-> 数据不完整: (数据)
[(消息发送类型)]⌈(平台)⌋请求失败-> (异常) | 数据: (数据)
[(消息发送类型)]⌈(平台)⌋请求失败-> (失败码): (返回值) | 数据: (数据)
[(消息发送类型)]⌈(平台)⌋请求失败-> 返回: (返回 json) | 数据: (数据)
[回调配置]⌈(平台)⌋请求失败-> 数据不完整: (数据)
[回调配置]⌈(平台)⌋请求失败-> 错误: (错误)
[文件上传]⌈(平台)⌋请求失败-> 无 media_id
[文件上传]⌈(平台)⌋请求失败-> (文件名): 类型不支持
- E3:消息处理捕获的错误
[消息]QQ 状态不存在-> (状态)
[消息]精华设置失败-> (数据)
[消息]测试群列表获取超时-> (群号)
[消息]用户视频获取超时-> (用户)
[消息]订阅视频获取超时-> (用户)
[消息]转发消息获取超时-> (数据)
- E4:其他地方产生的错误,上报到 E1,E2,E3 的调用方处
config.py
[配置数据]写入失败-> 配置项 (配置) 不存在,无法确定其来源文件
[配置数据]写入失败-> 配置项: (key) | 文件: (file_path.name) | 错误: (e)
[future]请求失败-> (key) 获取超时: (时间)s
[数据库]mongodb 未初始化
[数据库]查询失败: (错误) | SQL: (语句) | 参数: (参数)
[数据库]更新失败: (错误) | SQL: (语句) | 参数: (参数)
logic.data
[数据]面板未找到-> (名字)
[数据]任务越界-> 编号: (号码) | 当前任务数: (数量)
[文件处理]下载链接请求超时-> 视频: (视频)
[文件处理]未获取到下载链接-> 视频: (视频)
[文件处理]下载失败-> (链接): (返回码)
全局错误路径
- 纵观其他可能产生错误的模块
- 在非 main 中启动的而是自行初始化的模块中
- reloader 自行负责错误捕获及输出
- config 自行处理配置初始化及重载时的错误捕获及输出,配置写入时的错误由对应的任务调用写入并捕获
- future_manager 一般不会产生异常
- mongo_init 打印异常
- config.load 自行捕获异常
- main 里的主要模块中
- 定时任务捕获每个任务的错误并继续运行
- main(main.py,command.py,log.py) 里会捕获异常任务并不影响其他任务执行,退出时输出
- 消息处理自行捕获异常
- start 自行捕获异常
- config_watcher 自行捕获异常
- log_writer 打印异常
- 各平台任务由定时任务和 uvicorn 接收异常
相关知识
- 日志的 record 属性中包含 levelname,name,msg,args,pathname,lineno,funcName,created,exc_info 字段
- 如果在 yml 中定义了文件日志处理器,则不管被不被引用,都会创建一个 .log 文件
子任务异常
- task 自己使用 create_task 创建的子任务不受 main 管理
- 可能会导致取消时错误
- 如 init_app 里 create_task(server_serve())开启了 uvicorn 服务器,但这个任务的异常没有被处理,需要在创建的新任务上覆盖一个异常捕获器来处理
- 示例:
async def init_serve():
try:
await server.serve()
except KeyboardInterrupt:
backend_logger.debug(f"后台服务停止", extra={"event": "运行日志"})
serve_task = asyncio.create_task(init_serve())