自动化报告程序中的关键错误






4.86/5 (23投票s)
如何自动化收集程序中严重错误的详细信息,从而极大地简化您的错误分析和故障排除工作。
介绍
只有不写代码的人才不会犯错。每个开发人员都可能犯错,并且喜欢犯错,但都不喜欢修复它们。一次编码错误可能导致数据处理算法的实现不正确,而在另一次则可能导致异常(崩溃或严重错误)。在本文中,我将展示如何自动化收集程序中严重错误的详细信息,从而极大地简化您的错误分析和故障排除工作。
本文主要面向开发人员和质量保证人员。初级水平即可阅读。阅读本文后,您将了解:
- 为什么您无法通过电子邮件与用户有效沟通来修复软件错误;
- 可以自动收集哪些关于软件崩溃的信息;
- 如何利用这些信息来解决大部分用户遇到的问题。
我们真的需要自动化错误报告吗?
当我告诉一位朋友我计划在我的软件中使用崩溃报告库时,您知道我遇到了什么样的反应吗?我听到这样的回复:“哦,谁需要这么复杂的系统?在我的项目中,如果我的软件出了什么问题,我只会让最终用户给我写一封信,描述他们按了哪个按钮,在做什么。然后我复现错误并修复它。”
是的,程序员是懒惰的。我们想说服自己,我们能够修复最终用户发现的错误,并且可以轻松地通过电子邮件收集问题信息。同样,我们也说服自己,没有单元测试,甚至没有修复编译时出现的所有警告,就可以编写出良好且稳定的代码。此外,客户需要项目“尽快完成”。我们认为所有错误都将在未来的某个时候被发现和消除。测试人员应该发现 bug,所以让他们去做。时间永远不够用,截止日期临近,老板不会批准所有这些“额外的锦上添花”。
实践表明,这样的项目可以在创纪录的时间内完成,也许大部分错误都会被测试人员发现。但尽管如此,发布后,最终用户还是会向支持部门发送大量电子邮件,抱怨程序中的异常。随着时间的推移,缺乏一致的架构、代码注释、单元测试等的影响会显现出来。每个修改他人代码的开发人员都会引入新的 bug 和潜在的异常,其中一些会被测试人员捕获,而另一些则会成功发布。
当用户发送电子邮件寻求帮助时,开发人员通常会怎么做?
当用户发送一封电子邮件到我们的支持部门,告知他们在他们的机器上安装的程序中发生了严重错误时,我通常会要求支持部门了解用户的应用程序版本和操作系统版本。这是因为用户的软件环境对于识别错误原因至关重要。
第一个问题是我必须通过中间人(支持部门)与用户沟通。我还需要解释我想要什么。第二个问题是我必须向用户索要技术信息,但用户很少有技术知识。我接触过的大多数用户在我看来都是好人,但他们无法表达自己的想法,也不知道技术术语。我无法向他们解释如何更改注册表项以及如何打开隐藏的 LocalAppData 文件夹。
当用户回复我的请求时,我几乎总会有一些额外的问题,比如“请尝试按此按钮并更改此选项”。作为回应,我收到了沉默。用户根本没有耐心多次执行这些操作,直到我收集到所需信息。
在我们的项目中,无论是什么——NT服务还是GUI应用程序——我都使用日志记录。我将错误消息和正在进行的操作写入日志文件。理论上,日志文件应该帮助我收集用户不知道也不知道的应用程序技术信息。
当发生错误时,我请用户将日志文件发送给我。问题就出在这里。事实上,向用户解释日志文件确切的位置以及他们应该在何时获取日志文件是相当困难的。这不是一个简单的问题。用户经常在错误发生后重新启动应用程序,然后日志文件会被覆盖,然后为空地发给我,让我感到困惑。
通常,在我的机器上重现 bug 是我的主要目标。这将使我能够修复它。仅凭用户的指示,我很少能明白需要执行哪些操作才能使应用程序崩溃。仅凭眼前的文本日志,也很难回答这个问题。
因此,我与用户沟通的下一个阶段是要求他们截屏或录制视频,以显示他们的操作以及他们是如何在应用程序中出现错误的。您认为有多少人给我发了视频?不,但尽管如此,我还是遇到过这样的用户。而这个视频有时对于重现错误非常有帮助。
因此,手动收集应用程序严重错误信息的主要缺点是用户懒惰且不善言辞。他们不会验证您的假设来查找问题的原因——他们没有耐心和技术知识来做到这一点。
那么,到底如何更好地收集错误信息——手动还是自动?
微软在 2000 年代初就提出了自动收集错误数据和发送错误报告的想法(您可以通过 此链接 找到一篇关于此的微软研究院出版物),当时他们被许多产品(包括 Windows 和 Office)中发生的各种错误搞得焦头烂额。Windows 团队开发了一个工具,可以生成系统核心转储,然后对其进行分析。
独立地,Office 开发团队创建了一个工具,能够捕获未处理的异常并创建最小转储文件。最小转储文件仅包含进程虚拟内存的一小部分,足以读取发生异常的线程的调用堆栈。最小转储文件非常方便,因为它可以自动通过 Internet 发送。
- 修复 20% 的“顶部”检测到的错误可以解决 80% 的客户问题;
- 修复 1% 的错误根源,您可以解决 50% 的客户问题。
任何开发人员都可以使用 WER 来自动化收集应用程序中错误的详细信息。WER 将错误报告发送到专用的 WER 服务器,开发人员可以“免费”访问该服务器。但您需要有一个来自 VeriSign 的 代码签名证书。购买此类证书每年需要 500 美元。事实上,如果您为 Windows 平台开发,您需要此证书,因此他们假设访问 WER 服务器是“免费”的。
我个人更喜欢 CrashRpt,因为我只为 Windows 编写程序。此外,该库允许我不仅通过 HTTP 将错误报告发送到我的服务器,还可以作为电子邮件发送到我的邮箱,这对我来说更方便。下面,我将展示您还可以使用此库做什么,并提供一些链接到描述具体操作方法的文章。
可以自动收集哪些错误数据?
所以,我们假设应用程序中发生了严重错误(异常)。异常可能由许多因素触发:引用内存中的 NULL 地址、堆栈溢出、内存耗尽等等。在 MSDN 中,您可以找到 C 运行时提供的数十种用于拦截(处理)异常的函数。CrashRpt 内部就使用了这些函数来捕获异常。
当发生异常时,异常处理函数会运行 CrashRpt 代码,该代码首先收集异常指针(一个包含异常地址、代码和类型的结构)。接下来,CrashRpt 启动一个新进程并将异常指针传递给该进程。父应用程序(发生异常的应用程序)可能不稳定,一旦提取完所有错误数据就会被终止。
最小转储
主要的错误数据收集工作然后在那新进程中继续进行。
首先,使用微软的系统库 *dbghelp.dll* 写入最小转储文件。为此,父进程的所有线程都会被挂起,并记录进程的“快照”。快照包括已加载到进程中的所有 DLL 模块的名称和版本,以及进程中正在工作的线程列表。对于每个线程,都会记录调用堆栈映像。此外,操作系统版本、CPU 数量及其品牌名称等信息也会被写入最小转储文件。最小转储的大小通常在几十 KB 左右。
注意: 有关 Visual C++ 中的异常处理和最小转储的高级信息,您可以参考 CodeProject 上的 Effective Exception Handling in Visual C++ 文章。
一旦我们获得了最小转储文件,我们就可以在 Visual Studio 中打开它,并可视化崩溃时的程序状态:应用程序版本、操作系统版本,以及查看异常发生的代码位置(请参阅下图)。这难道不会让我们的生活更轻松吗?
日志
自动化收集崩溃信息并不意味着我们不能使用传统的日志。我们仍然可以像以前一样,将当前操作和错误记录到日志文件中,并在崩溃时自动将其添加到错误报告中。这消除了之前在“手动”向用户索要日志时可能出现的混乱。现在,日志将始终包含程序崩溃时期的实际数据,您不必解释如何打开隐藏的 LocalAppData 文件夹以及从该文件夹中取哪个文件。
除了日志文件,我们还可以添加任何我们希望的自定义应用程序生成的文件(例如,INI 配置文件等)。
屏幕截图
我不喜欢最小转储和日志的一点是,它们通常无法提供重现错误的方法。是的,我可以查看程序中发生崩溃的位置,并可以推测它可能发生的原因。例如,崩溃通常是由于变量未初始化,程序访问了垃圾内存地址。
注意:有关程序崩溃最常见原因的高级信息,您可以参考 Making Your C++ Code Robust 文章。
但无论我付出多少努力,在大多数情况下,我都找不到能够让我重现崩溃的操作顺序。这不仅是因为每个用户的软件环境都与我的计算机环境不同。这也是因为用户有自己独特的程序使用模式。您使用程序的方式,您对它执行的操作,可能与用户正在做的事情截然不同。
因此,屏幕截图是关于错误非常有用的信息。屏幕截图使我能够看到崩溃时用户的屏幕。CrashRpt 库能够自动创建屏幕截图,并将其保存为 JPEG(可以调整压缩质量)或 PNG 文件。结果,我可以知道用户在错误发生时点击了哪个按钮,相信我,这有助于重现问题。
下图是应用程序崩溃时自动创建的屏幕截图示例。截图仅包含应用程序窗口区域,其余区域自动涂黑(以保护用户隐私)。
视频录制
还记得我提到过有几次用户发给我的视频显示了他们在程序崩溃前所做的操作吗?使用 CrashRpt 库,您不必要求用户制作屏幕录像。该库本身就可以制作(当然,需要最终用户的同意)。
显然,我们无法预测崩溃发生的时刻。因此,CrashRpt 会定期(以您定义的间隔)截屏并将其保存到磁盘(以未压缩的 BMP 文件格式)。随着屏幕截图的累积,旧的屏幕截图会被删除,新的屏幕截图会取代它们的位置。在我机器上的测试中,这大约占用了 5-7% 的 CPU 时间和几百 MB 的磁盘空间。
如果发生异常,记录的屏幕截图将使用 OGG Theora 视频编解码器 进行压缩,您将获得一个视频文件,可以在 Chrome 或 Firefox 浏览器或任何视频播放器中打开。
我同意视频录制操作相当耗费资源,但并非必须始终启用它。例如,您可以仅在所有收集用户信息的尝试都失败,并且重现错误的最后机会是请求视频时才启用它。
结论
当然,即使不自动化错误数据收集,修复程序中的严重错误也是“可能”的。您可以在一定程度上成功地通过电子邮件向用户索要所需数据。但自动收集错误数据可以真正有效地做到这一点。它可以让开发人员的生活更轻松,而且不一定只是在软件发布后。例如,我们在公司内部的 Alpha 和 Beta 测试早期阶段使用自动化来简化与 QA 工程师的沟通。因为测试人员也并不总是知道从哪里获取日志文件以及如何截屏或录制视频。
如果您决定尝试 CrashRpt,请参阅 Integrating Crash Reporting into Your Application - A Beginners Tutorial 文章以获取详细说明和源代码。
顺便问一下,您知道 C 语言中包含严重错误的程序最短的是哪个吗?它如下所示:
main () {main ();}
历史
- 2012 年 11 月 27 日 - 首次发布。