跳过日志文件 - 尝试自动异常处理!
codeRR 是一个开源的错误处理服务。它包含了您在记录/报告异常时忘记添加的上下文信息。
引言
本文从使用者的角度出发,将引导您了解所有功能,并展示如何在您自己的应用程序中开始使用。
codeRR 是一个用于检测和分析异常的开源服务。它可以在不稳定的网络连接下工作,甚至可以在公司代理后面运行。它为每个报告的异常附加了上下文信息,这使得识别和纠正异常原因变得更加容易。
背景
我从事编程已经超过20年了。在这期间,错误管理一直是个反复出现的问题,特别是当您的应用程序是 Windows/桌面应用程序时。用户的错误报告往往内容稀少,没有足够的细节来重现错误。您要么只能凭经验猜测,要么只能进行“猴子测试”来找出根本原因。
最糟糕的问题是,当您认为已经找到并纠正了原因后,稍后却又收到了关于同一问题的新错误报告。这时候,您实际上是让代码库变得更糟了。
错误报告的事实标准一直是日志记录。但您知道的,我们努力做好日志记录,包含足够的细节。但我们是否总会漏掉那个能告诉我们异常抛出原因的关键字段呢?
或者从用户的角度来看。如果您在别人的应用程序中遇到错误,您有多大可能会去分析这个错误?您会创建一个详细的错误报告并联系应用程序制造商的支持部门吗?我想不会。因此,一旦您收到一份错误报告,可以肯定的是,已经有好几个其他用户遇到了同样的错误。
codeRR 正是解决这些问题的一次尝试。codeRR 不依赖于用户的错误报告或日志文件。当用户遇到一个新的、唯一的异常时,codeRR 会联系您,并且您通常能获得所有需要的信息,从而可以直接开始修复 bug,而无需先进行任何分析。
功能导览
让我们先来了解一下所有功能。本文的最后一章包含一个循序渐进的入门指南。
codeRR 由一个客户端(nuget 包)和一个服务器(IIS Web 应用程序)组成。客户端在您的应用程序中检测异常,收集上下文信息,并将所有内容上传到服务器。
让我们通过一个简单的控制台程序来报告一个异常,看看我们能得到什么样的酷炫功能。
class Program
{
static void Main(string[] args)
{
// Initialization
var url = new Uri("https:///coderr/");
Err.Configuration.Credentials(url, "yourAppKey", "yourSharedSecret");
try
{
throw new InvalidOperationException("Hello world");
}
catch (Exception ex)
{
Err.Report(ex);
}
}
}
当该代码运行时,异常将被上传到服务器。当您访问您的 codeRR 网站时,您会看到类似这样的界面:
仪表盘为您提供了在 codeRR 中配置的所有应用程序的概览。
呈现的信息
描述
名称 | |
事件 | 所有被识别为同一错误的已报告异常的聚合。 |
活动事件 | 尚未解决或忽略的事件。 |
报表 | 一份已上传的错误报告(相当于一个已记录的异常) |
等待中的用户 | 在遇到错误页面时输入了自己电子邮件地址的用户,即他们希望获得状态更新,并可以被联系以进行进一步分析。 |
反馈 | 已填写错误描述(错误发生时他们正在做什么)的用户数量。 |
让我们点击该事件以获取有关该特定错误的更多信息。
事件
事件用于将错误报告分组。与日志库不同,如果您收到同一个异常 5000 次,codeRR 不会生成 5000 个不同的错误。相反,codeRR 会识别出收到的报告与之前的报告是同一个异常。这两个报告会被归为一个事件。ISO 20000 将事件定义为:
引用对服务的非计划性中断、服务质量的降低或尚未影响到客户服务的事件。
……这很贴合您的用户在抛出异常时的体验。
事件页面包含有关特定错误的信息。从这里,您可以浏览我们收到的关于该错误的所有报告。在上面的截图中,我再次运行了我的示例应用程序,使得该事件下总共有两个报告。
您可以对事件执行两种操作:
- 关闭事件 - 将其标记为已解决/已纠正(即不应再发生)
- 忽略事件 - 不再为此事件存储任何报告或生成通知
当您忽略一个事件时,所有新的报告都将被丢弃。报告计数器将继续增长,以便您可以看到报告仍在被接收。但新的报告将不会被分析或存储。
当您关闭一个事件时,您还可以编写一条消息,该消息将分发给所有等待新版本的用户。这一切都通过 codeRR 完成,并且只发送给那些在异常抛出时注册了状态通知的用户。
以下是 codeRR 迄今为止实现的一些分析功能。
标签
如果您看上面的截图,您会注意到 `console-application` 标签。codeRR 会为收到的报告识别一些 StackOverflow 标签。如果您点击一个标签,您将被引导到对该异常消息的搜索,并仅筛选该标签。
错误来源
错误来源显示了错误报告来自何处。该信息以热力图的形式呈现。因此,您可以轻松识别错误是否更频繁地发生在某个特定区域。这表明错误可能与本地化或文化问题有关。
当报告数量增多时,图钉将被替换为热力图。
上下文数据
事件下的上下文数据是聚合和专门化的结合。集合首先被专门化,例如,Web 浏览器的“`User-Agent`”字符串被提取到其自己的集合中。完成后,
信息被聚合然后进行比较。因此,codeRR 可以告诉您,例如,一个事件的 99% 的错误报告是针对文化“`sv-SE`”的,或者在所有报告中,操作系统中可用的内存少于 100MB。
反馈 / 错误描述
codeRR 为用户提供了一种在捕获异常时留下错误描述的方式,即他们可以描述异常发生时他们正在做什么。他们还可以留下自己的电子邮件地址,以便在事件关闭时收到通知。
这是 WinForms 项目的内置错误表单:
您也可以像这样自己收集信息:
//last param is email address
var feedback = new UserSuppliedInformation("I pressed the 'any' key.", null);
Err.LeaveFeedback(errorIdFromReport, feedback);
上传后,反馈将在事件下可用:
您的用户提供的反馈越多,就越容易识别和纠正问题。
错误报告
事件是了解您的应用程序有哪些不同错误的好方法,包括它们发生的频率和共同点等信息。但是,有时深入了解每个特定错误报告的细节,以便理解异常抛出的原因会更好。
因此,您可以点击事件下列表中的任何一个报告进行查看。
Notifications
是否曾想在您的应用程序中抛出异常时立即收到通知?
codeRR 支持多种类型的通知。您可以收到手机短信或电子邮件消息。
以下是 codeRR 支持的通知:
应用程序版本
codeRR 可以跟踪应用程序版本,以查看同一错误是否已存在于多个版本中,或者是否在几个版本后再次出现。
要激活此功能,您需要进入管理区域,并选择您要提升版本号的程序集。
您还需要确保在您的项目中指定了版本号:
因此,一旦报告了异常,该事件将被标记上应用程序版本:
如果您现在提升程序集版本:
...并报告相同的异常,您将看到该事件将包含两个应用程序版本:
客户端库
codeRR 服务器负责分析和呈现信息,而客户端库则用于检测异常和收集上下文信息。
客户端库将自身注入到您喜欢的 .NET 库/框架的管道中,以便能够自动收集异常和信息。但是,如果您有 `try`/`catch` 块,您也可以自己报告异常。
我们目前构建的库有:
名称 | 描述 |
Coderr.Client | 基础库。如果您想自己报告异常,这个库就足够了。 |
Core.Client.NetStd | .NET Standard (v1.6 和 v2.0) 库。 |
Coderr.Client.AspNet | 通用的 ASP.NET 库。捕获所有未处理的异常并报告它们。 收集有关 HTTP 请求、会话数据等信息。允许您轻松地为不同的 HTTP 错误代码创建自定义错误页面。 |
Coderr.Client.AspNet.Mvc5 | ASP.NET MVC5 专用库。 功能与 ASP.NET 库相同,但还会收集特定的 MVC5 信息,如路由数据、ViewBag 等。还允许您通过在错误视图文件夹中创建 razor 视图来自定义错误页面。 |
Coderr.Client.AspNet.WebApi2 | ASP.NET WebApi 2 的库。 跟踪异常、无效的模型状态、失败的授权尝试、丢失的 API。收集所有可用信息,包括 ASP.NET 管道和 WebApi 中的信息。例如模型、请求、路由数据等。 |
Coderr.Client.AspNetCore.Mvc | ASP.NET Core MVC 的库。 跟踪异常、无效的模型状态、失败的授权尝试、丢失的页面。收集所有可用信息,包括 OWIN 管道和 MVC 中的信息。例如 viewbag、请求、viewdata、viewbag、路由数据等。 |
Coderr.Client.Wcf | 捕获 WCF 管道中未处理的异常。 收集 WCF 特定信息,例如处理失败的入站 WCF 消息。 |
Coderr.Client.Log4Net | 报告您记录的所有异常,包括您编写的错误消息。 |
Coderr.Client.WinForms | 报告所有未处理的异常。 可以截取屏幕截图并收集所有打开窗体的状态。 |
Coderr.Client.WPF | 报告所有未处理的异常。 可以截取屏幕截图并收集所有打开窗体的状态。 |
也可以通过您自己的库/应用程序直接报告异常。客户端规范 (HTTP/JSON) 可在在线文档中找到。
下面是关于我们一些库功能的更多介绍。
核心库
这个库是所有使用 codeRR 进行异常报告的基础。所有其他客户端库都基于这个库。
使用核心库,您必须像这样手动报告异常:
try
{
doSomething();
}
catch (Exception ex)
{
Err.Report(ex);
}
您还可以附加不同种类的上下文信息,我们将在“入门”一章中再回到这个话题。
上下文集合
上下文集合是一组从特定来源获取的属性。上下文集合可以是 HTTP 标头、视图模型、路由数据等。
核心库中有一些内置的上下文集合。有些默认添加到管道中,这意味着每次报告异常时都会包含它们。其他集合则需要您在配置客户端库时手动添加。
如果您愿意,也可以轻松地创建并包含您自己的。
应用程序信息
此集合包含有关您的进程的信息。它包含诸如已用内存、线程数和已用 CPU 量等信息。
Assemblies
所有已加载到 `AppDomain` 中的程序集及其版本。
异常信息
来自异常的信息,包括所有属性。如果您曾经使用过 EntityFramework 并遇到过 DbEntityValidationException 吗?
它的异常消息只是说:
引用System.Data.Entity.Validation.DbEntityValidationException: 一个或多个实体验证失败。有关详细信息,请参见 'EntityValidationErrors' 属性。
当您在日志文件中看到这个时,您知道为什么会得到这个异常吗?
由于 codeRR 包含了所有异常属性,您可以直接获得验证信息。
以下是来自 `DbEntityValidationException` 异常的所有信息的一个子集。如您所见,您既可以得到 EF 实体中所有属性的值,也可以得到所有验证失败的信息。
文件版本
所有程序集的文件版本,因为程序集版本可能相同而文件版本更高(有人遇到过 GAC 地狱吗?)。
操作系统
错误是发生在所有 Windows 版本上还是仅在某些版本上?
系统信息
可用 RAM 内存量是否是瓶颈?
线程信息
线程信息,如当前的 UI 区域性。
哪个线程崩溃了? 提示:如果您自己启动线程,请给它们命名。
ASP.NET
这个库适用于不使用 MVC 的 ASP.NET 项目。所以它非常适合基于 ASP.NET 的库,例如 Nancy。
上下文集合
这些集合由 ASP.NET 库生成。
HTTP 标头
失败请求的所有 HTTP 标头。
上传的文件
请求中上传的所有文件的名称、大小和上下文类型。
表单
HTTP 表单中的所有项目(名称和值)。
查询字符串(QueryString)
查询字符串变量(键和值)。
Session
所有会话项(支持复杂对象)。
错误页面
该库包含一个自定义错误页面,当捕获到异常时可以激活(并显示)该页面。
要激活它,请添加以下代码:
var provider = new VirtualPathProviderBasedGenerator("~/Errors/");
Err.Configuration.SetErrorPageGenerator(provider);
该代码表示库应该在指定文件夹中查找错误页面(*.aspx* 或 *.html*)。
该库尝试根据 HTTP 代码查找页面。如果代码是 404,该库会尝试查找以下错误页面:
- FileNotFound.aspx
- FileNotFound.html
- Error.aspx
- Error.html
也就是说,它会先尝试查找最具体的文件。如果特定文件不存在,它会尝试加载一个通用的文件。
错误信息
要将信息显示到您的视图中,只需在您的代码后台声明以下属性之一。
属性 | 类型 | 描述 |
ErrorContext | HttpErrorReporterContext | 更多信息请查阅客户端 API 规范。 |
异常 | 异常 | 捕获到的异常 |
示例
public partial class NotFound : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
public Exception Exception { get; set; }
public HttpErrorReporterContext ErrorContext { get; set; }
}
然后只需在您的 HTML 中显示错误信息即可:
<%@ Page Language="C#" AutoEventWireup="true"
CodeBehind="NotFound.aspx.cs" Inherits="codeRR.Client.AspNet.Demo.Errors.NotFound" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Failed to find <%= Exception.Message %></title>
</head>
<body>
<form method="post" action="$URL$">
<input type="hidden" value="$reportId$" name="reportId" />
<div>
Page is not found
</div>
<div>
<%= ErrorContext.HttpStatusCode %>
</div>
<div>
<p>Could you please let us know how to reproduce it?
Any information you give us will help us solve it faster.</p>
<textarea rows="10" cols="40" name="Description"></textarea>
</div>
</form>
</body>
</html>
如果您只使用 HTML,您可以使用以下模板字符串:
模板文本 | 描述 |
$ExceptionMessage | 异常消息 |
$reportId$ | 生成的报告ID |
$URL$ | codeRR 自身的 URL,用于提交错误描述、电子邮件等。 |
$AllowReportUploading$ | 如果您想允许用户决定是否上传报告。 |
示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>An error occurred</title>
<meta name="ROBOTS" content="NOINDEX, NOFOLLOW" />
<meta name="X-powered-with" content="https://coderrapp.com" />
<style type="text/css">
/*CssStyles*/
</style>
</head>
<body>
<div style="" class="container">
<div style="width: 100%; text-align: center">
<h1>Looks like something went wrong!</h1>
</div>
<form method="post" action="$URL$">
<div class="img">
<img src="/images/Error.jpg" />
</div>
<div class="content">
<p>
Thanks for taking the time and letting us know about the issue.
</p>
<p>
We’re working to fix it for you as fast as we can,
apologies for the inconvenience.
</p>
<input type="hidden" value="$reportId$" name="reportId" />
<div class="AllowSubmissionStyle">
<p>
However, If you allow us to collect additional error information
we'll be able to analyze this error much faster.
</p>
<input type="checkbox" name="Allowed"
value="true" checked="$AllowReportUploading$" />
I allow thy to collect the additional information.
</div>
<div class="AllowFeedbackStyle">
<p>Could you please let us know how to reproduce it?
Any information you give us will help us solve it faster.</p>
<textarea rows="10" cols="40"
name="Description"></textarea>
</div>
<div class="AskForEmailAddress">
<p>Enter your email address if you would like to
receive status updates about this error.</p>
<input type="text" name="email"
placeholder="email address" />
</div>
<hr />
<input type="submit" value="Send report" />
<a href="/">Back to homepage</a>
</div>
<div style="clear: both;"></div>
</form>
</div>
</body>
</html>
ASP.NET MVC
这个客户端库将为 codeRR 提供 ASP.NET 和 MVC 特定的上下文信息。
除了检测和上传未捕获的异常外,该库还提供以下功能。
错误页面
该库内置了对错误页面的支持。
要使用库中包含的页面,请在 *global.asax* 中添加以下内容(在 `Err.Configuration.Credentials()` 行之后):
Err.Configuration.DisplayErrorPages();
自定义错误页面
如果我们内置的页面不合您的心意,您可以包含您自己的视图。
示例
@model codeRR.Client.AspNet.Mvc5.CoderrViewModel
<h1>Internal Server Error</h1>
<p>
We've experienced a malfunction in the core crystal cooling.
The ship will explode within five seconds.
</p>
<h3>Reason</h3>
<p>
@Model.Exception.Message
</p>
视图应以 .NET 中 `HttpStatusCode` `enum` 中定义的 HTTP 代码命名,并放置在 *Views/Errors* 文件夹中。
如果没有其他视图匹配,则显示 *Error.cshtml* 视图。
ErrorController
如果仅通过视图控制错误处理还不够,您可以创建自己的 `ErrorController`。像创建其他控制器一样,在 *Controllers* 文件夹中创建它。
操作方法的命名应与视图类似,例如 `public ActionResult InternalServer()`。
由 codeRR 提供的信息表示为 `CoderrViewModel`。将其作为参数传递给您的操作方法。
示例
public class ErrorController : Controller
{
public ActionResult Index(CoderrViewModel model)
{
return View("Error", model);
}
public ActionResult NotFound(CoderrViewModel model)
{
return View(model);
}
public ActionResult InternalServerError(CoderrViewModel model)
{
return View(model);
}
}
自定义格式
如果 HTTP 客户端请求 XML 或 JSON,将返回一个错误对象。这对于 ASP.NET `WebApi` 非常有用。
json
{
"error": {
"msg": "The error message",
"reportId": "Unique error id"
},
hint: "Use the report id when contacting us if you need further assistance."
}
xml
<Error ReportId="Unique error id"
hint="Use the report id when contacting us if you need further assistance">
Error message
</Error>
上下文集合
以下集合由 ASP.NET MVC 库提供。
控制器 (Controller)
收集控制器名称。
RouteData
收集有关 MVC 所采用路由的信息。
TempData
如果设置了 `TempData`,则会收集它。
示例
TempData["DemoKey"] = new {
Amount = 20000,
Expires = DateTime.UtcNow.AddMinutes(5)
};
结果
ViewData / ViewBag
如果指定了 `Viewbag` 和/或 `ViewData`,则会收集它们。
示例
ViewBag.Title = "Hello";
ViewBag.Model = new
{
state = "Running",
Collected = true
};
结果
log4net 客户端
log4net 库将 codeRR 注入到 log4net 的日志记录管道中。每次您记录某些内容并包含异常时,它都会报告给 codeRR。这无疑是在遗留应用程序(使用 log4net)中使用 codeRR 强大功能的最简单方法。
用法
如果您有这样的代码:
try
{
methodThatWillThrowAnException();
}
catch (Exception ex)
{
_logger.Warn("Failed doing some crazy stuff.", ex);
}
... 异常将被 codeRR 捕获。
另外,您还会在 codeRR 中看到有关日志条目的信息:
WinForms
WinForms 客户端库可以帮助您显示错误页面并收集有关打开窗体的信息。
错误页面
当检测到异常时会显示一个错误页面。默认情况下它看起来是这样的:
您可以使用以下属性对其进行配置:
Err.Configuration.UserInteraction.AskUserForDetails = true;
Err.Configuration.UserInteraction.AskUserForPermission = true;
Err.Configuration.UserInteraction.AskForEmailAddress = true;
示例
上下文信息
WinForms 有两个内置的上下文集合。
OpenForms
codeRR 使用反射从所有打开的窗体中收集信息。这些信息包括所有控件及其配置(位置、内容、可见性等)。
假设您打开了这个窗体:
... 这将为您提供以下信息:
屏幕截图
可以通过以下配置行之一激活屏幕截图功能:
//only of the active form
Err.Configuration.TakeScreenshotOfActiveFormOnly();
// of all forms
Err.Configuration.TakeScreenshots();
上下文集合将显示为:
codeRR 管道
这是一个小图表,展示了 codeRR 为确保您的异常被检测、捕获、包装上下文信息并最终上传到服务进行分析所采取的步骤。
它还会识别重要的方法调用,如果您想浏览代码并了解事物如何协同工作。
提示:在 Visual Studio 中右键单击代码行,然后使用“查找用法”/“查找引用”来查看方法调用的来源。
(CodeProject 对图片有宽度限制,在此处查看完整尺寸图片。)
入门
一份帮助您报告第一个错误的指南。
服务器安装
您可以下载服务器的源代码,编译并安装它,或者使用预编译版本。无论哪种方式,安装都是 xcopy 部署。
- 下载或编译。
- 将二进制文件复制到一个新的 Web 服务器文件夹,通常是 *c:\inetpub\wwwroot\coderr*。
- 转到 IIS 并使用上下文菜单选项,“添加应用程序...”或“添加网站...”。
- 将其命名为“`Coderr`”并指向您创建的文件夹。
- 在您的 SQL 服务器中创建一个新的空数据库,并修改 *web.config* 中的连接字符串。
- (使用您的 Web 浏览器)浏览到该网站。
- 按照设置向导操作
- 向导完成后,在 *web.config* 中将 `appKey` `Configured` 更改为 `true`。
服务器现已安装完毕。
创建一个新应用程序
一旦您使用上一步创建的帐户登录到 codeRR 服务器,您应该会看到应用程序向导:
输入一个应用程序名称并进入下一步。
安装客户端
我们的客户端库用于检测异常和收集上下文信息。目的是为您提供足够的信息,以真正理解异常发生的原因。我们不想做假设或编写权宜之计,对吧?
为您的应用程序选择正确的 nuget 包,然后点击“**配置**”。
配置您的应用程序
为了能够报告异常,您需要告诉您的应用程序 codeRR 应该检测异常并收集上下文信息。完成后,codeRR 还需要知道将错误报告上传到哪里。
为了能够做到这一点,codeRR 需要能够识别您的应用程序,以便错误能够正确地存储在您的帐户中。
应用程序向导的下一步将帮助您完成此操作。下面是一个示例。
该说明包含了正确的 `appKey/SharedSecret`,您需要做的就是将信息复制/粘贴到您的应用程序中。
应用程序向导仅在第一个应用程序时启动,但是,您可以为尚未报告任何错误的应用程序自行启动它。
手动报告异常
报告异常最简单的方法是这样:
try
{
somelogic();
}
catch(SomeException ex)
{
Err.Report(ex);
}
异常应在报告后不久出现在您的服务器安装中。
附加上下文信息
通常,仅凭一个异常不足以理解异常被抛出的原因。codeRR 总是会为您收集大量参数。然而,您可能拥有能让您直接理解异常为何被抛出的信息。
该信息可以在报告时附加:
try
{
//some stuff that generates an exception
}
catch (Exception ex)
{
Err.Report(ex, yourContextData);
}
使用匿名对象
如果您需要附加多个值,可以使用匿名对象:
try
{
//some stuff that generates an exception
}
catch (Exception ex)
{
Err.Report(ex, new { UserId = userId, UserState = state });
}
结果
自定义集合
我们还有一个对象扩展方法,可以将任何对象转换为上下文集合(我们网站上“**上下文数据**”菜单中的一个分组)。
下面我们正在使用 `.ToContextCollection()` 扩展方法。
try
{
<span spellcheck="true">//some stuff that generates an exception</span>
}
catch (Exception ex)
{
var modelCollection = viewModel.ToContextCollection("ViewModel");
var loggedInUser = User.ToContextCollection("User");
Err.Report(ex, new[]{modelCollection, loggedInUser});
}
结果
因此,您可以轻松地按您喜欢的方式附加和分组您的信息。
使用标签对异常进行分类
在分析异常时,我们会自动识别常见的 StackOverflow.com 标签(以帮助您通过搜索 StackOverflow.com 找到答案)。您也可以通过向任何上下文集合添加一个名为“`ErrTags`”的特殊属性来添加您自己的标签:
try
{
//some stuff that generates an exception
}
catch (Exception ex)
{
Err.Report(ex, new { ErrTags = "important,backend" });
}
当该报告到达 codeRR 服务器时,您将直接在事件页面上看到这些标签:
这是对错误进行分类的好方法。
摘要
codeRR 正在持续开发中。每周都有新功能推出。如果您想了解最新动态,请在 Twitter 或 Facebook 上关注我们。
服务器是使用消息传递、命令/查询、仓储模式、typescript 和其他好东西构建的。在 Github 上查看。
感谢您的阅读,我们非常期待在上述任何渠道听到您的想法。
历史
- 2016年9月8日:初始版本