

场景一:对于数据量比较大的流程,单条数据异常通常会跳过,继续执行下一条数据
如果使用屏幕录制一直录制,视频就会很大,找到对应异常的视频片段也比较麻烦
场景二:对于指令中的异常
显示的异常信息会比较少,看不到指令中发生异常的具体位置信息
1.新建python模块:err_hanlder.py
2.全部复制拷贝下面的代码到 err_hanlder.py
# 使用提醒:
# 1. xbot包提供软件自动化、数据表格、Excel、日志、AI等功能
# 2. package包提供访问当前应用数据的功能,如获取元素、访问全局变量、获取资源文件等功能
# 3. 当此模块作为流程独立运行时执行main函数
# 4. 可视化流程中可以通过"调用模块"的指令使用此模块
import xbot
import xbot_visual
from xbot import print, sleep
from .import package
from .package import variables as glv
from xbot.win32.screenshot import save_screen_to_file
import os
import re
import json
import datetime
import traceback
from os.path import exists, join
from os import mkdir
def get_xbot_name():
"""
获取应用名称,主应用名称,非指令
"""
dir_path = re.search(r'''.+\\apps\\[^\\]+\\(?=xbot_extensions|xbot_robot)''', __file__).group()
with open(join(dir_path, "xbot_robot", "package.json"), "rb") as f:
load_dirt = json.load(f)
xbot_name = load_dirt['name']
return xbot_name
def rename_if_exists(filename):
basename, ext = os.path.splitext(filename)
i = 0
while os.path.isfile(filename):
i += 1
filename = f"{basename}({i}){ext}"
return filename
def exception_screenshot(save_path):
# 获取主应用名称
xbot_name = get_xbot_name()
# 拼接文件夹
now = datetime.datetime.now()
date_string = now.strftime("%Y%m%d%H%M%S")
dir_path = join(save_path, xbot_name, date_string)
def outer(func):
def wrapper(*args, **kwargs):
nonlocal xbot_name
nonlocal dir_path
# 提取异常信息
error_msg = re.split(r'(?=File ".+\.py", line \d+, in )', traceback.format_exc())[-1]
filepath = re.findall(r'File "(.+\.py)", line \d+, in ', error_msg)[0]
# 获取指令所在的流程和行号
result = re.findall(r'_block=\("(.+)", (\d+),.+\)', error_msg)
if len(result) > 0:
# 创建保存文件夹
xbot_visual.dir.makedir(parent=os.path.dirname(dir_path), name=os.path.basename(dir_path))
log_path = join(dir_path, f"log.txt")
error_app_name = None
name, line = result[0]
error_msg = '【{}】中第【{}】条指令出错:{}'.format(name, line, args[0])
if not os.path.dirname(filepath).endswith("\\xbot_robot"):
# 读取指令名称
with open(join(os.path.dirname(filepath), "package.json"), "rb") as f:
json_package = json.load(f)
error_app_name = json_package['name']
if error_app_name:
error_msg = '指令【{}】出现异常 - 【{}】中第【{}】条指令出错:{}'.format(error_app_name, name, line, args[0])
xbot_visual.programing.log(type="info", text=error_msg)
img_path = join(dir_path, f"{date_string}.png")
img_path = rename_if_exists(img_path) # 防止重名
with open(log_path, "a", encoding="u8") as f:
f.write(f'异常截图:{os.path.basename(img_path)} - {error_msg}\n')
save_screen_to_file(img_path, "png")
res = func(*args, **kwargs)
return res
return wrapper
return outer
row_trace = xbot_visual.trace
def exception_screenshot_api(save_path):
if row_trace == xbot_visual.trace:
# 单例模式,确保函数只被装饰一次
xbot_visual.trace = exception_screenshot(save_path)(row_trace)
def main(args):
pass
3.在主流程第一行使用指令【调用模块】,传入图片保存文件夹。如下:

1.在【普普通通的指令】中写一个异常代码,然后运行,只能看到调用指令的流程和行号信息,看不到指令中具体报错位置

2.在【普普通通的指令】中写一个异常代码,在【当前应用流程】写一个异常代码。运行后如下图日志所示,可以清楚的知道指令中的报错位置和当前流程中的报错位置

在刚刚传入的文件夹中也可以找到具体的异常截图和异常信息

1.当使用try...catch时,源码中会多出一行代码:exception = xbot_visual.trace(exception)

2.前后打印日志,可以看到这个函数的作用是寻找异常栈信息。但是如果在指令中,这个功能就会失效

3.利用装饰器替换这个函数,就可以在每次异常捕获时自定义实现自己想要的功能。对于主动抛出的异常和设置变量发生的异常则不会触发截图功能。使用python模块traceback获取异常信息如下:
Traceback (most recent call last):
File "C:\Users\yd\AppData\Local\ShadowBot\users\59882836586950656\apps\65514a74-7c96-4646-b54c-42cbcca1f497\xbot_robot\main.py", line 16, in main
], _block=("main", 3, "普普通通的指令"))
File "<string>", line 89, in wrapper
File "<string>", line 84, in wrapper
File "<string>", line 82, in run
File "C:\Users\yd\AppData\Local\ShadowBot\users\59882836586950656\apps\65514a74-7c96-4646-b54c-42cbcca1f497\xbot_extensions\activity_1cb63042\main.py", line 9, in main
xbot_visual.programing.log(type="info", text=lambda: 姓名, _block=("main", 1, "打印日志"))
File "<string>", line 89, in wrapper
File "<string>", line 81, in wrapper
File "C:\Users\yd\AppData\Local\ShadowBot\users\59882836586950656\apps\65514a74-7c96-4646-b54c-42cbcca1f497\xbot_extensions\activity_1cb63042\main.py", line 9, in <lambda>
xbot_visual.programing.log(type="info", text=lambda: 姓名, _block=("main", 1, "打印日志"))
NameError: name '姓名' is not defined再利用正则提取最后一个py文件下边的_block即可