你会在Python项目日志中查找Traceback信息吗?

1,569 阅读5分钟

前言

最近在面试测试开发岗位时遇到了一个查找提取 Python 日志的问题,当时有点懵逼,没有想出合理的解决方案,面试后自己又思考了下,发现这个问题其实还挺有现实意义的,在日常的工作中还可以帮助测试同学提高测试效率,所以就写了这篇文章。

问题及解决方案

问题的描述是这样的:

给你一个很大的 Python 日志,如何查找其中的 Traceback 信息?

那么首先这个问题是否有现实意义呢?因为我们都知道,一般的项目中都会有收集错误日志的 error_log,也就是说可以把项目程序运行中的错误即 Traceback 内容输出到一个日志当中。这个功能的实现方式有很多种,包括 Python 提供的 traceback 库也可以做到。所以从项目实现和运行的角度看,这个功能是没有什么意义的。

但是从测试的角度来看,是有一定用处的,比如,一般的大公司的项目,从功能开发到测试上线是这样一个流程:

log.drawio.png

其中,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 内容的规律,其次,要写比较复杂的正则表达式,因为,文本中可能有多行匹配到你设置的正表达式,并且有资源限制,所以也不是特别合理。

下面介绍下我想到的一个解决方案。

log2.drawio.png

代码如下:

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;错误;日志;