前言
最近在面试测试开发岗位时遇到了一个查找提取 Python 日志的问题,当时有点懵逼,没有想出合理的解决方案,面试后自己又思考了下,发现这个问题其实还挺有现实意义的,在日常的工作中还可以帮助测试同学提高测试效率,所以就写了这篇文章。
问题及解决方案
问题的描述是这样的:
给你一个很大的 Python 日志,如何查找其中的 Traceback 信息?
那么首先这个问题是否有现实意义呢?因为我们都知道,一般的项目中都会有收集错误日志的 error_log,也就是说可以把项目程序运行中的错误即 Traceback 内容输出到一个日志当中。这个功能的实现方式有很多种,包括 Python 提供的 traceback 库也可以做到。所以从项目实现和运行的角度看,这个功能是没有什么意义的。
但是从测试的角度来看,是有一定用处的,比如,一般的大公司的项目,从功能开发到测试上线是这样一个流程:
其中,showcase 主要是给测试同学展示系统核心功能是否走通;提测主要是解决 showcase 中发现的问题。如果前期的这几个流程处理的越好,后面的测试和上线才能够更顺畅。
但是在测试的过程中总是会遇到一些 bug,这些 bug 在 showcase 和提测过程中不是很好发现,并且这些 bug 很多时候会报 Traceback 的。当 rd 提测的时候一般是使用自己的开发机或者测试环境,这些环境不能够发送 Senty 到 IM 软件上(如Lark,钉钉),所以这就会导致 bug 进入到测试阶段或者上线阶段。如果在 showcase 和提测的过程中解决了这个问题,就可以提高提测质量,提升测试效率,减少后续过程 bug 出现的可能性。所以,综上所述这个问题还是有一些现实意义的。
那么一个 Python 日志的中内容是什么样子的呢?我这里简单的提供了一个可能会出现的内容。
asdfghjkl
1234567890
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
IndexError: tuple index out of range
qwertyuiop
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
IndexError: tuple index out of range
zxcvbnm
9876543210
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
IndexError: tuple index out of range
可以看到,日志中夹杂着普通信息和 Traceback 信息,在日志中每行后面都有换行符。
在面试过程中,为了快速的提出解决方案,可能大多数人第一个想到的方法就是使用 Linux 命令,如 grep 命令进行查询。
但是 grep 命令一般只能用于简单的查询关键词,输出行内容,但是 Traceback 信息是跨越多行的(字符流中有\n),所以使用 grep 命令就比较难查找,在短时间内最多能想到的解决方案就是,输出 Traceback 的下多少行内容,如:
cat xxx grep -A 10 Traceback
这样能够输出 Traceback 及后 10 行。显然该方法是不能满足需求的。
第二个可能想到的方法就是使用正则表达式。这个方法是可行的,但是首先需要分析 Traceback 内容的规律,其次,要写比较复杂的正则表达式,因为,文本中可能有多行匹配到你设置的正表达式,并且有资源限制,所以也不是特别合理。
下面介绍下我想到的一个解决方案。
代码如下:
import re
def collect_traceback(source: str, target: str) -> None:
"""收集Python日志中的Traceback信息,并写入目标文件中
:param source: 日志文件路径
:param taregt: 结果输出文件路径
:return: None
"""
# 使用栈结构存储1条Traceback错误信息
error_stack = []
# 正则Traceback开头与结尾的正则表达式
trac_start_regx = re.compile(r'(^Traceback \(most recent call last\):)\n')
trac_end_regx = re.compile(r'(^((\w)*Error): (\w)*)\n')
with open(source, 'r') as f:
# 表明正在处理Traceback信息的标识
handle_trac = False
for line in f:
if handle_trac:
# 将错误信息入栈
# 如果错误信息结束,则输出错误到目标文件中,清空栈
# 重置handle_trac标识
error_stack.append(line)
if trac_end_regx.search(line):
while len(error_stack) > 0:
error_info = error_stack.pop()
write_and_appending_trac_info(target, error_info)
handle_trac = False
elif trac_start_regx.search(line):
handle_trac = True
error_stack.append(line)
def write_and_appending_trac_info(target: str, error: str) -> None:
"""以appending的方式写入Traceback信息到目标文件中
:param target: 结果输出文件路径
:param error: 要写入的错误信息
:return: None
"""
with open(target, 'a') as f:
f.write(error)
if __name__ == '__main__':
collect_traceback('./error_info', './traceback_result')
代码的核心思想就是利用一个栈结构来保存 Traceback 信息,Traceback 的开头和结尾就相当于栈底和栈顶,通过一个标识来确定当前是否在处理错误信息,每当遇到 Traceback 开始就入栈,每当遇到 Traceback 结束就出栈,输出。
程序的结果如下:
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
IndexError: tuple index out of range
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
IndexError: tuple index out of range
Traceback (most recent call last):
File "<doctest...>", line 10, in <module>
lumberjack()
File "<doctest...>", line 4, in lumberjack
bright_side_of_death()
IndexError: tuple index out of range
分析
这个解决方案相比于正则表达式的方法,有两个优势:
- 首先,提取字符串的规则简单一些,只需要考虑开头行和结尾行的正则表达式。
- 其次,不受内存资源的限制,如果一个极端情况下,日志内容非常多,那么使用正则的方式时需要首先加载文件内容到内存,这时就会出现内存不够的问题。
总结
这里实现了一个查看 Python 日志中 Traceback 信息的程序,通过解决该问题可以帮助研发同学快速发现程序中的问题,提高研发效率。如果读者想实现在 Java 项目或者其他语言的项目中查看错误信息,可能还需针对日志的格式对正则表达式做相应的修改。
解决该问题的方式有多种,这里只是介绍了自己的思路,可能还有更优雅,高效的解决方案,也希望读者们能够多多评论,提供更加贴近实际场景的解决方案。
关键字:Python;Traceback;错误;日志;