跳到主要内容

日志

日志格式

  • 系统采用统一的日志记录格式
  • 日志配置通过 config 读取 log.yaml 中获得,分为:
    1. formatters: 日志输出的文本格式与时间格式
    2. handlers: 日志处理器,定义日志的输出方式,可绑定对应的 formatters 和日志等级
    3. filter: 日志过滤器,可以进行日志级别、名称等自定义过滤逻辑
    4. 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:5161 method:GET path:/ http_version:1.1 status_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 用简写)
sourceleveleventmessagelocation
systemD模块加载[加载]-> (模块)reloader
systemD模块加载[重载]-> (模块)reloader
systemE模块加载[加载]-> (模块): (错误)reloader
systemE模块加载[重载]-> (模块): (错误)reloader
systemD配置更新[配置数据]更新config
systemD配置更新[配置数据]写入-> (文件)config
systemD配置更新[配置数据]临时数据保存成功config
systemE配置更新[配置数据]日志错误-> (异常)config
systemE配置更新[配置数据]自动更新异常-> (异常)config
systemD运行日志[数据库]连接成功-> (数据库)config
systemD索引创建(L1)config
systemE索引创建[索引创建](名称) 失败-> (异常)config
systemE运行失败[初始化]失败-> (错误)main
systemD运行日志[任务]被取消-> (任务)main
systemE运行失败[任务](任务) 异常-> (异常)main,log
systemD运行日志[信号]收到退出信号-> (信号)main
systemD错误堆栈[消息处理/定时任务/后端运行]-> 堆栈: (堆栈)\n变量: (变量)msg_process,app,config
systemE定时任务[定时任务](任务) 异常-> (E1)config
serverD/I运行日志(L2)command
serverE运行失败[服务器](异常)command
napcatD/I/E运行日志(L3)log
webstieI运行日志(L4)app
webstieI(L4)(L4)app
websiteE运行失败(E2) | (ip): (路径)app
websiteD/I/E网页日志(L5)app,cab
adapterD消息接收[接收]⌈(平台)⌋(数据)xx_receive
adapterD消息接收[回调]⌈(平台)⌋-> 成功: (数据)LR232/WECHAT_receive
adapterD消息发送[令牌刷新]⌈(平台)⌋-> (令牌): (有效期)access_token
adapterD消息发送[(消息发送类型)]⌈(平台)⌋-> (返回值) | (数据)xx_dispatch
adapterD消息去重⌈(平台)⌋(数据)LR232/WECHAT_receive
adapterE消息超时⌈(平台)⌋(数据)WECHAT_receive
messageD消息处理[消息清理]完成-> 共清理 (数量) 条消息msg_pool
messageD消息处理(消息体)msg_process
messageI消息处理[(消息类型)]⌈(平台)⌋处理/发送-> (用户): (内容)msg_process,msg_send
messageE消息处理[消息解析]失败-> 无法解析表情 ID: (id)msg
messageE消息处理[消息处理]错误-> (消息): (E3)msg_process
messageI消息分析[(指令类型)]⌈(平台)⌋(用户)-> (接收): (发送)system
messageD/E文件处理(L6)file
messageD/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
  1. [配置数据]yaml 文件格式错误-> (文件名): (异常) 在日志记录器加载前完成,无法调用日志
  2. [数据库]连接失败-> Mongodb: (异常) 无法写入 Mongodb 数据库
  3. [数据库]写入失败-> 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())