65.9K
CodeProject 正在变化。 阅读更多。
Home

ErrZure - 自管理错误捕获器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (6投票s)

2013年4月25日

MIT

33分钟阅读

viewsIcon

38269

ErrZure 是一款用于收集和管理移动客户端错误的工具。

链接 

快速跳转

背景 - 介绍 

在过去两年里,我为不同的平台发布了几个应用程序。Android、Windows Phone、Windows 8 和 Blackberry10。其中一些是我在业余时间完成的,另一些是为我工作的公司完成的。有些是我完全独立完成的,有些是在更大的团队中完成的。有些是由代理公司完成的,我们只是部署和发布它们。但我不仅仅是一名开发人员,我还是一个用户。我整天都在使用应用程序。但无论是 Android 上的 Twitter,iPad 上的 AppStore 应用程序,还是我在 Windows Phone 上的创作,总会有一些没有人测试过的边缘情况。但有一件事比应用程序中存在 bug 更糟糕:不知道它的存在!

ErrZure 将是一个自管理的错误存储。所有未捕获的异常都将记录在云端。

为什么不直接使用...?

市场内置解决方案 

有些市场会自动记录异常。Windows 8 和 Android 都在这方面有所尝试。但有时您只是通过电子邮件将您的 apk1) 发送给 Beta 测试人员。有时您会在像俄罗斯的 Yandex 这样的第三方商店发布它们。当没有互联网连接时,异常会发生什么?您真的知道吗?

1) Android 应用的文件扩展名 

分析工具 

我使用过的两个分析工具,Google AnalyticsFlurry Analytics,都内置了记录异常的解决方案。但这有两个问题。

如果您对分析不感兴趣,这会造成巨大的开销。结果并不总是有帮助。

Google Analytics 

图片来源:链接  

Flurry 

Flurry

图片来源: 链接

有了这些信息,至少我知道出了问题。

现有服务 

BugSense 

BugSense 确实正是我们想要的。他们提供 500 个“免费异常”。bug 越多,付费越多。许多大公司都在使用他们的服务。如果您需要一个简单的解决方案,这可能是一个好主意。他们专注于应用程序:支持 iOS、Android、Windows Phone 和 HTML5 应用程序。

图片来源:BugSense 

Airbrake   

Airbrake 看起来也非常专业。他们专注于 Ruby on Rails 和 iOS。通过第三方支持几乎所有东西,这也是让其他人为我们完成工作的好方法。

图片来源: AirBrake  

那么开源解决方案呢?   

ErrBit  

ErrBit 是 Airbrake 的(或多或少)开源克隆。它使用 Ruby on Rails 制作,MongoDB 存储所有异常。ErrBit 将在其中一个挑战(第四个挑战:虚拟机)中使用,以便与我们在此挑战中想要创建的自己的服务进行很好的比较。

图片来源: gitHub 

Azure 能为我做什么?

Azure 消除了设置服务器、服务、数据库和网站的痛苦。同时,它也消除了在经典存储解决方案中进行扩展或缩减时必须签订固定合同的痛苦。

挑战(初步想法) 

第一个挑战:入门

注册一个免费账户和预期的一样简单。需要信用卡,但在前 3 个月内不会收费。没有风险:如果达到限制(例如:每月 25GB 出站数据),服务将被禁用直到下个月。如果您计划投入生产模式,则应将订阅从免费更改为付费。

写这篇文章比较难,英语不是我的母语,是我必须学习的第三种语言(波兰语、德语、英语)。希望您能原谅我的一些语法错误。

第二个挑战:搭建一个网站 

最终的“产品”应该是完全开源的。因此,在这个挑战中,我们将准备一个 WordPress 博客,可以在比赛结束后用于宣布更新并与潜在用户交流。
这个博客将在比赛期间用于宣布变化、进展等( naast 本文)

第三个挑战:在 Azure 上使用 SQL   

我们将在此挑战中编写我们服务的后端解决方案。更多细节将及时跟进。然而,该项目不会像 BugSense 或 ErrBit 那样复杂。(时间、金钱、人员)。在此挑战中还将包含一些移动代码/移动集成来测试所有内容。此移动代码将不属于第五个挑战,将有其他内容。

第四个挑战:虚拟机

在此挑战中,我们将在 Linux Ubuntu VM 中安装并运行开源解决方案:ErrBit。我们将安装 Ruby、Rails、mongoDB 以及所有其他必要的组件。

第五个挑战:移动访问 

在第五个挑战中,我们将创建一个移动应用程序,以便随时随地检查我们记录的异常。如果时间允许,我们将修改第三个解决方案中的服务,使其能够在新错误发生时向我们的应用程序发送推送通知(每个错误只发送一次)。

挑战(项目概述)

系统

System

在这个项目中,我们将使用 Android 设备强制生成一些异常。我们还将使用现有的解决方案来捕获异常并将其传输到我们的服务。这有两个原因。

  1. 我们的服务将支持 ErrBit 和 AirBrake 支持的所有内容(不仅是移动设备,还包括 Ruby on Rails、php、erlang、.NET) 
  2. 我们将在第二个挑战中节省一些时间,这可能是这个项目中最大的挑战。

我们将使用 ErrBit 通知器,来自 Matteo Piotto,来捕获、准备和传输我们的异常。他从 James Smith 处分叉了该项目,James Smith 创建了 Android 版的原始 AirBrake 通知器
这些通知器的代码不应纳入本次挑战的考量!

它们最棒的地方在于实现起来超级简单。

ErrbitNotifier.register(this, "errbit.domain.com", 
  "your-api-key-goes-here");

我们希望保留此功能,因此我们的服务应该处理此实现并为应用程序提供一个 API 密钥,但我们将在稍后的挑战中检查 errbit 的数据库结构。

所有“通知器”都有严格的 URL 结构

ErrbitNotifier.errbit_endpoint = 
  "http://" + endpoint + "/notifier_api/v2/notices";

我们必须保留此功能,以支持所有现有通知器!

UI 线框图 

我们在挑战 3 中的服务将是最困难的部分。以下是一些 UI 的草图。

移动客户端线框图(挑战 5)  

AppUI

数据结构 

此项目应与所有当前可用的 ErrBit 错误通知器兼容。因此,此项目需要相同的数据结构。
ErrBit 和 AirBrake 都使用 XML。
这可能不是最好的解决方案,因为与 JSON 相比,XML 总是存在一些开销,但这是我们必须处理的限制。

AirBrake 和 ErrBit 都使用以下 XML 结构

<?xml version="1.0" encoding="UTF-8"?>
<notice version="2.0">
   <api-key>ff0621fe7eaad6d6f4afbfaf3635fe79</api-key>
   <notifier>
      <name>Android Errbit Notifier</name>
      <version>0.2.0</version>
      <url>http://welaika.com</url>
   </notifier>
   <error>
      <class>java.lang.RuntimeException</class>
      <message>[1.0] java.lang.RuntimeException: Unable to start activity 
        ComponentInfo{com.errbit.testapp/com.errbit.testapp.MainActivity}: 
        java.lang.NullPointerException</message>
      <backtrace>
         <line method="android.app.ActivityThread.performLaunchActivity" 
           file="ActivityThread.java" number="2121" />
         <line method="android.app.ActivityThread.handleLaunchActivity" 
           file="ActivityThread.java" number="2146" />
         <line method="android.app.ActivityThread.access$700" 
           file="ActivityThread.java" number="140" />
         <line method="android.app.ActivityThread$H.handleMessage" 
           file="ActivityThread.java" number="1238" />
         <line method="android.os.Handler.dispatchMessage" 
           file="Handler.java" number="99" />
         <line method="android.os.Looper.loop" 
           file="Looper.java" number="137" />
         <line method="android.app.ActivityThread.main" 
           file="ActivityThread.java" number="4947" />
         <line method="java.lang.reflect.Method.invokeNative" 
           file="Method.java" number="-2" />
         <line method="java.lang.reflect.Method.invoke" 
           file="Method.java" number="511" />
         <line method="com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run" 
           file="ZygoteInit.java" number="1038" />
         <line method="com.android.internal.os.ZygoteInit.main" 
           file="ZygoteInit.java" number="805" />
         <line method="dalvik.system.NativeStart.main" 
           file="NativeStart.java" number="-2" />
         <line file="### CAUSED BY ###: java.lang.NullPointerException" number="" />
         <line method="com.errbit.testapp.MainActivity.onCreate" 
           file="MainActivity.java" number="17" />
         <line method="android.app.Activity.performCreate" 
           file="Activity.java" number="5207" />
         <line method="android.app.Instrumentation.callActivityOnCreate" 
           file="Instrumentation.java" number="1094" />
         <line method="android.app.ActivityThread.performLaunchActivity" 
           file="ActivityThread.java" number="2085" />
         <line method="android.app.ActivityThread.handleLaunchActivity" 
           file="ActivityThread.java" number="2146" />
         <line method="android.app.ActivityThread.access$700" 
           file="ActivityThread.java" number="140" />
         <line method="android.app.ActivityThread$H.handleMessage" 
           file="ActivityThread.java" number="1238" />
         <line method="android.os.Handler.dispatchMessage" 
           file="Handler.java" number="99" />
         <line method="android.os.Looper.loop" 
           file="Looper.java" number="137" />
         <line method="android.app.ActivityThread.main" 
           file="ActivityThread.java" number="4947" />
         <line method="java.lang.reflect.Method.invokeNative" 
           file="Method.java" number="-2" />
         <line method="java.lang.reflect.Method.invoke" 
           file="Method.java" number="511" />
         <line method="com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run" 
           file="ZygoteInit.java" number="1038" />
         <line method="com.android.internal.os.ZygoteInit.main" 
           file="ZygoteInit.java" number="805" />
         <line method="dalvik.system.NativeStart.main" 
           file="NativeStart.java" number="-2" />
      </backtrace>
   </error>
   <request>
      <url />
      <component />
      <action />
      <cgi-data>
         <var key="Manufacturer">samsung</var>
         <var key="Device">GT-N5100</var>
         <var key="Brand">samsung</var>
         <var key="Android Version">4.1.2</var>
         <var key="App Version">1.0</var>
      </cgi-data>
   </request>
   <server-environment>
      <environment-name>production</environment-name>
      <app-version>1.0</app-version>
   </server-environment>
</notice> 

根元素是“通知”。每个通知都有

  1. API 密钥
  2. 错误 
    • Message
    • 回溯
  3. 通知器
  4. 附加请求参数
  5. 服务器环境 

第二个挑战:搭建一个网站 

最初的想法是建立一个博客,在所有挑战之后可以用于开源项目。

Azure 有一些预配置的解决方案,包括 WordPress,这完美地符合我们的需求。

第二个挑战:WordPress 

要设置 WordPress,我们首先需要登录 https://manage.windowsazure.com 并从左侧菜单中选择“网站”选项卡。使用左下角的“新建”按钮开始设置。

这将从底部弹出一个菜单。选择“从图库”

Azure 将显示所有预配置的解决方案。从 CMS 解决方案、图像画廊到维基百科。我们将坚持使用 WordPress。
设置 WordPress 是一个 3 步过程。实际安装 WordPress 需要 2 步。最后一步是设置管理员用户、密码、标题和电子邮件等内容。

  • 步骤 1:选择 URL、区域和可能的数据库 (MySQL)
  • 步骤 2:选择数据库区域/名称
  • 步骤 3:配置您的 WordPress(用户、密码)

博客现在可在 http://errzure.azurewebsites.net  访问

第二个挑战:使用 Git 的自定义网站

在 Azure 中设置 WordPress 太简单了,不配称为挑战。我们必须找到一个新的。
这次我们将使用 Git,编写一些 PHP 代码连接到数据库,并触及免费 Azure 试用版的第一道限制。
再次按下“新建”按钮后,我们将选择“自定义创建”选项。

指南将再次弹出并帮助我们正确设置所有内容。请务必选中“从源代码控制发布”复选框。

使用箭头继续设置后,Azure 将弹出警告

因为我们不打算在这里发布任何“重要”的东西,也不打算使用 MySQL 数据库,所以我们可以回去完全禁用数据库。

但我们也可以使用在 WordPress 安装期间创建的现有数据库。**(在生产环境中,如果非必要,请避免这样做!!这可能很危险。)**

在最后一步,我们可以选择不同的“源代码管理”。也许“Dropbox”不应该被称为源代码管理,但 **git** 绝对是其中之一。

Azure 应该会要求您提供 Git 凭据。选择一个用户名和密码(记住它们!每次发布/部署代码时都需要您的密码)。

我们的第一次设置已完成。访问第一步中选择的 URL,我们应该会看到欢迎消息。

要获取有关设置 Git 以及如何使用它的所有信息,请选择您的网站,然后使用“部署”选项卡。

现在我们将切换到控制台并设置所有内容,以便轻松部署我们的“网站”。我所有这些都是使用 Ubuntu/Linux 完成的,但也可以在 Windows 或 Mac 中使用 GIT。我们现在将运行一系列 GIT/Linux 命令

  1. 为我们的项目创建一个新文件夹并切换到该文件夹 
    • mkdir phpErrbit
    • cd  phpErrbit 
  2. 克隆我们的项目并切换到项目文件夹
    • git clone https://podkowik@errzurephp.scm.azurewebsites.net/ErrZurePhP.git 
    • cd ErrZurePhP  
  3.  创建一个新的 index.php 文件并使用 nano 添加一些 php 代码
    • nano index.php 
  4. 将我们的文件添加到 Git 索引,提交我们的更改并设置我们的远程仓库
    • git add .
    • git commit -m "check for db connection"
    • git remote add azure https://podkowik@errzurephp.scm.azurewebsites.net/ErrZurePhP.git
  5.  最后一步会将所有内容推送到我们的远程 Azure 服务器并部署我们的新代码。

index.php 中的 PHP 代码将创建与 MySQL 服务器的连接。

<?php
// Create connection
$con=mysqli_connect("eu-cdbr-azure-north-a.cloudapp.net",
  "USERNAME","PASSWORD","DB");
// Check connection
if (mysqli_connect_errno($con))
  {
  echo "Failed to connect to MySQL: " . mysqli_connect_error();
  }else{
   echo "Connected";
  } 
?> 

回到我们的 Azure 管理网站,我们应该在部署选项卡中触发了新的构建。您可以在此处轻松检查所有部署,如果您的当前版本中发现 bug,可以简单地回滚。

在浏览器中访问我们的项目 URL 应该会显示“已连接”字符串(如果一切正常!)

危险部分 1 

在不同的项目中,使用一个彼此不连接的数据库,是非常危险的。如果您为不同的客户在同一个数据库上托管服务,这可能会导致灾难。WordPress 足够聪明,将其所有数据库表命名为 wp_XXX,例如:wp_users。因此,我们可以通过拥有自己的结构(如 ez_users)来避免使用相同的表名。但是我们也可以简单地访问 WordPress 用户数据库。想象一下,一个网站上有包含信用卡信息等的在线商店,而另一个网站上有一个非常不稳定的发布版(无论是什么)。两者都连接到同一个数据库……我们也会在此处(使用 Git)设置另一个 WordPress 实例时失败。

我们将在这里进行一个小测试。我们在项目中创建一个名为“print.php”的新文件,并向其中添加一些代码,以查找数据库中的所有表及其名称。我在这里找到了代码:链接

<?php
/****************
* File: displaytables.php
* Date: 1.13.2009
* Author: design1online.com, LLC
* Purpose: display all table structure for a specific database
****************/
//connection variables
$host = "localhost";
$database = "your_db_name";
$user = "your_username";
$pass = "your_pass";
//connection to the database
mysql_connect($host, $user, $pass)
or die ('cannot connect to the database: ' . mysql_error());
//select the database
mysql_select_db($database)
or die ('cannot select database: ' . mysql_error());
//loop to show all the tables and fields
$loop = mysql_query("SHOW tables FROM $database")
or die ('cannot select tables');
while($table = mysql_fetch_array($loop))
{
    echo "
        <table cellpadding=\"2\" cellspacing=\"2\" 
          border=\"0\" width=\"75%\">
            <tr bgcolor=\"#666666\">
                <td colspan=\"5\" align=\"center\"><b>
                  ;<font color=\"#FFFFFF\">" . $table[0] . "</font></td>
            </tr>
            <tr>
                <td>Field</td>
                <td>Type</td>
                <td>Key</td>
                <td>Default</td>
                <td>Extra</td>
            </tr>";
    $i = 0; //row counter
    $row = mysql_query("SHOW columns FROM " . $table[0])
    or die ('cannot select table fields');
    while ($col = mysql_fetch_array($row))
    {
        echo "<tr";
        if ($i % 2 == 0)
            echo " bgcolor=\"#CCCCCC\"";
        echo ">
            <td>" . $col[0] . "</td>
            <td>" . $col[1] . "</td>
            <td>" . $col[2] . "</td>
            <td>" . $col[3] . "</td>
            <td>" . $col[4] . "</td>
        </tr>";
        $i++;
    } //end row loop
    echo "</table><br/><br/>";
} //end table loop
?> 

结果与我们预期的完全一致。所有 WordPress 表都弹出来了。

我认为这张截图再次说明了潜在的风险。

危险,第二部分

这个小问题分析的第二部分将是更“危险”的部分。我们将数据库链接到两个项目。然而,第二个项目只是为了玩玩并探索 Azure 中的 Git 功能。我们想再次删除它。

如果您在概览中选择删除按钮,您会看到这个小的确认对话框

在这里,您必须百分百确定是否记得数据库是否链接到其他地方。在我的第一次尝试中,我粗心地选中了复选框,这破坏了我的 WordPress 安装。两个项目的 MySQL 数据库都因选中一个复选框而消失了。没有关于可能存在的附加链接项目的额外警告。

第二个挑战:带有 git、sql 和一些(预期)错误的自定义网站

对于我们的第三个项目,我们需要像在第二个项目中那样设置所有内容,但我们将选择 SQL 而不是 MySQL。在通过 PHP 连接到数据库之前,我们想获取更多关于当前 PHP 设置的信息。

<?php 
      phpinfo();
?>   

但是,让我们在这里放置一个语法错误,例如使用 `php_info();`。当然,这不起作用。但是我们想查看日志文件,更多信息会很好!

我们导航到

https://errzurephponsql.scm.azurewebsites.net

Azure 需要用户名和密码,请使用您的 git 凭据。它们也适用于我们的 WordPress 安装,但我不太确定如果您从未设置过它们会发生什么!请自行尝试!

您将最终进入 Kudu,这可能非常有用。

  • “环境变量”链接将显示您可能需要设置某些项目的所有环境变量,您猜对了。
  • 诊断转储链接将允许您下载大量日志文件。所有您的 git 部署、所有错误日志,所有内容都在一个 zip 文件中。这里还包含一个 `php_errors.log` 文件,其中显示
    “在第 2 行调用未定义的函数 `php_info()`。”  
  • “诊断日志”链接将弹出一个新窗口,显示日志文件的实时流。现在执行的任何操作(例如:git push)都将在此处显示。

为了测试诊断日志,我们将部署一个新文件 `test.php`,并通过 PHP 连接到 SQL Server。

<?php
$serverName = "BLAURL.database.windows.net, 1433"; 
//serverName\instanceName, portNumber (default is 1433)

$connectionInfo = array( "Database"=>"DATABASE", 
  "UID"=>"USERNAME", "PWD"=>"PASSWORD");
$conn = sqlsrv_connect( $serverName, $connectionInfo);
if( $conn ) {
     echo "Connection established.<br />";
}else{
     echo "Connection could not be established.<br />";
     die( print_r( sqlsrv_errors(), true));
}
?> 

从 PHP 连接到 (MS-) SQL 与连接到 MySQL 略有不同,但这不应成为本文的一部分。当然,连接成功了,我们在浏览器中打开 test.php 时会得到“连接已建立”的提示。但这是实时日志中发生的情况: 

正如我们已经使用MS-SQL一样,我们也可以使用SQL选项卡中的“管理”按钮创建表格。

然而,这最终会到一个使用 Silverlight 的页面。Linux 的 Silverlight 插件无法正常工作,所以我们最终会看到一个空白页面。我们能做的就是右键单击页面并检查我们的插件,但它不起作用。

从挑战 2 中学到的经验 

  1. 非常容易安装像 WordPress 这样配置好的流行系统
  2. 对 Git 和其他源代码版本控制的良好支持
  3. 安全使用免费试用。达到限制时会发出警告,而不是要求付费。
  4. 链接资源可能很危险 
  5. Azure 网站部分需要 Silverlight,这对 Linux 用户来说很糟糕。

第三个挑战:在 Azure 上使用 SQL

初步考虑

在这次挑战中,我们将为 ErrZure 创建主服务。ErrZure 将完全用 PHP 编写,MySQL 将用作存储数据的技术。使用这两种技术有几个原因。

  1. ErrBit,我们想要从头重写的服务,是用 Ruby on Rails 编写的。我们不会使用相同的技术。
  2. ASP.NET 在 Azure 上运行完美,但这会限制我们的服务。ErrBit 已经非常受限(Rails、MongoDB 等)。我们需要一个几乎可以在任何地方运行的东西!
  3. 好几年没用 PHP 了。第三个原因绝对是:找乐子和温故知新! 
  4. Microsoft 的 WebMatrix 工具支持 PHP 开发,“Starter Site”提供了很多好处。  

开始 

一旦我们在 Azure 管理网站上设置了一个新网站,我们就会切换到 WebMatrix 并从模板创建一个新的 PHP 项目。“Starter Site”已经带来了 MySQL 连接以及一套典型的操作,如:登录、注销、会话、用户管理等等。

以下截图展示了“Starter Site”项目结构

WebMatrix

从现在开始,我们可以切换到我们喜欢的网页浏览器,并通过导航到: https://:26579/ 来查看进度

最初的网站看起来已经相当不错

分析代码

最重要的改变将影响创建表函数。默认站点包含 4 个表:users、roles、users_in_roles 和 pages。这是来自 `database.php` 文件的代码

function create_tables($databaseConnection)
{
    $query_users = "CREATE TABLE IF NOT EXISTS users (id INT NOT 
      NULL AUTO_INCREMENT, username VARCHAR(50), password CHAR(40), PRIMARY KEY (id))";
    $databaseConnection->query($query_users);

    $query_roles = "CREATE TABLE IF NOT EXISTS roles 
      (id INT NOT NULL, name VARCHAR(50), PRIMARY KEY (id))";
    $databaseConnection->query($query_roles);

    $query_users_in_roles = "CREATE TABLE IF NOT EXISTS users_in_roles 
      (id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, role_id INT NOT NULL, ";
    $query_users_in_roles .= " PRIMARY KEY (id), FOREIGN KEY (user_id) 
      REFERENCES users(id), FOREIGN KEY (role_id) REFERENCES roles(id))";
    $databaseConnection->query($query_users_in_roles);

    $query_pages = "CREATE TABLE IF NOT EXISTS pages (id INT NOT 
      NULL AUTO_INCREMENT, menulabel VARCHAR(50), content TEXT, PRIMARY KEY (id))";
    $databaseConnection->query($query_pages);
}

当然,我们没有/不需要页面。但我们有应用和错误,所以我们需要在这里更改数据库设置

$query_apps = "CREATE TABLE IF NOT EXISTS apps (
  id INT NOT NULL AUTO_INCREMENT, appkey VARCHAR(50), appname TEXT, PRIMARY KEY (id))";
$databaseConnection->query($query_apps);

$query_error = "CREATE TABLE IF NOT EXISTS error (id INT NOT NULL 
  AUTO_INCREMENT, appkey VARCHAR(50), errorclass TEXT, errormessage TEXT, 
  errormanufacturer VARCHAR(20), errordevice VARCHAR(20), errorbrand VARCHAR(50), 
  errorandroidversion VARCHAR(10), errorappversion VARCHAR(10), 
  errorbacktrace TEXT,  submissiondate DATETIME, PRIMARY KEY (id))";
$databaseConnection->query($query_error); 

以下是新数据库表的概述

创建 API

一旦我们设置好数据库表,就需要创建 API 来接收崩溃报告。正如已经解释的,我们希望与所有现有 ErrBit/AirBrake 的“错误通知器”兼容,因此我们必须采用相同的行为。

  1. 使用相同的 URL 结构:http:/.../notifier_api/v2/notices 
  2. 使用相同的 XML 布局(参见介绍)并能够正确解析它。 

对于 URL 结构,我们只需要创建所需的文件夹并在其中创建一个 `index.php` 文件。如果没有提供文件,PHP 默认会使用 `index.php` 文件,所以这会很好用。

在 PHP 中解析 XML 并不是什么大问题。这是一个来自 `index.php` 文件的代码片段,它展示了如何在 PHP 中处理 XML

$appkey = array_shift($xml->xpath('/notice/api-key'));
$errorclass = array_shift($xml->xpath('/notice/error/class'));
$errormessage = array_shift($xml->xpath('/notice/error/message'));
$errormanufacturer = array_shift($xml->xpath('/notice/request/cgi-data/var[@key = "Manufacturer"]'));
$errordevice = array_shift($xml->xpath('/notice/request/cgi-data/var[@key = "Device"]'));
$errorbrand = array_shift($xml->xpath('/notice/request/cgi-data/var[@key = "Brand"]'));  

最后,我们只需要将所有内容存储到数据库中。

$query = "INSERT INTO error (appkey, errorclass, 
  errormessage, errormanufacturer, errordevice, errorbrand, errorandroidversion, 
  errorappversion, errorbacktrace, submissiondate) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
	
$statement = $databaseConnection->prepare($query);
$statement->bind_param('ssssssssss', $appkey, $errorclass, $errormessage, 
  $errormanufacturer, $errordevice, $errorbrand, $errorandroidversion, 
  $errorappversion, $errorbacktraceToDB, $submissiondate);
$statement->execute();
$statement->store_result(); 

网站

默认模板仍然为我们提供了添加、删除和编辑页面的可能性。由于我们没有页面,而是有应用程序,我们只需删除所有包含“Page”一词的内容,并创建一些新文件以便能够添加/编辑/查看和删除应用程序。我们还希望能够查看崩溃报告,所以这里也需要更多的逻辑。

添加应用

创建新应用程序应该尽可能简单。我们只需要输入应用程序名称。AppKey 应该自动生成。

以下代码片段将为我们创建一个唯一的 ID

$appkey = uniqid (rand (),true); 

一旦选择好 AppName,以下 PHP 代码将把应用程序保存到我们的数据库中

if (isset($_POST['submit']))
{
    $appname = $_POST['app-name'];
    $query = "INSERT INTO apps (appname, appkey) VALUES (?, ?)";

    $statement = $databaseConnection->prepare($query);
    $statement->bind_param('ss', $appname, $appkey);
    $statement->execute();
    $statement->store_result();

    if ($statement->error)
    {
        die('Database query failed: ' . $statement->error);
    }

    $creationWasSuccessful = $statement->affected_rows == 1 ? true : false;
    if ($creationWasSuccessful)
    {
        header ("Location: index.php");
    }
    else
    {
        echo 'Failed adding new app';
    }
}  

创建后,我们应该在主概览页面上看到我们所有的应用程序。

我们只需要调整 `apps.php` 文件,以获取所有应用程序和报告的错误数量。

<table class='apps'>
<thead>
    <tr>

        <td class='name' >Name</td>
        <td class='count'>Count</td> 
    </tr>
</thead>
<tbody>
<?php

$statement = $databaseConnection->prepare("select apps.appkey, 
  apps.appname, count(error.id) as count from apps LEFT JOIN error ON 
  apps.appkey=error.appkey group by apps.appkey  ORDER BY count desc;");
$statement->

使用 Android-ErrBit 通知器提交崩溃报告

为了测试我们的服务,我们在 Eclipse 中设置了一个空白 Android 应用程序,从 GitHub 下载了 ErrBit 通知器,并用这一行代码在我们的项目中设置它

ErrbitNotifier.register(this, "errzureservice.azurewebsites.net", "1262151a1ea8dcc1088.55035136"); 

第一个参数“this”是必需的,以确保 ErrbitNotifier 可以访问系统的整个上下文(例如:将异常保存到文件,以确保在没有互联网连接时不会丢失)。第二个参数是我们的端点,这里是新创建的 Azure 网站。最后,第三个参数是我们服务生成的唯一 App Key。

为了测试一切,我们还需要做两件事。Android 几乎所有操作都需要权限。我们的应用程序将无法访问互联网或写入文件,因此我们需要在 `AndroidManifest.xml` 中添加两行代码,告诉系统我们需要这些权限。

<uses-permission android:name="android.permission.INTERNET" /> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 

第二件事是抛出一个异常

List l = null;
l.add("Hello Exception");  

此时,一切应该都正常,我们应该在 Azure 服务中看到我们的第一个错误日志。它在装有 Apache + PHP + MySQL 的 Ubuntu VM 上进行了测试,一切都很好。

然而,IIS 服务器在处理 PHP 方面似乎有其他规则。经过数小时与日志文件和 Google 的搏斗,我找到了问题所在。

Azure 不接受我 API 中的 HTTP POST 方法:http://.../notifier_api/v2/notices/。如果我们将 API 端点更改为 `http://.../notifier_api/v2/notices/index.php`,一切都会正常工作。

由于我浪费了太多时间来寻找这个问题,我将在这次挑战之后进行更改,并写下所有步骤,以便其他人可以从中学习。

但有了“新”API 端点,一切似乎都正常了。

如果我们点击应用程序名称,我们会看到该应用程序的所有异常。

最后,点击错误后面的链接,我们将看到所有详细信息。

那么源代码在哪里? 

我不得不承认,我低估了这个项目。在中断 2-3 年后使用一门编程语言并不像我预期的那么容易。我也从未使用过 Windows 上的 PHP/MySQL。这与我在 Linux 上的体验并非百分百相同,但也可能只是情况发生了变化。最后,有两种开源心态。

  1. 尽快开源所有内容。人们如果使用我的代码,则自行负责。
  2. 开源一个人们可以学习的优秀产品 

我属于第二类人。我的代码能用,但还不足以作为开源项目发布到 CodeProject。我必须放弃第三个挑战!代码将在接下来的 1-2 周内发布!

第四个挑战,虚拟机 

在此挑战中,我们将在 Azure Ubuntu VM 中设置开源错误报告解决方案“ErrBit”。我们还将创建一个开源库,即适用于 Windows Phone 的 ErrBit 通知器。

我还没有找到任何“即用型”的 Windows Phone 库,所以这是一个自己创建的好机会。

设置 ErrBit

对于某些人来说,设置 ErrBit 似乎非常复杂。ErrBit 是用 Ruby on Rails 编写的,需要机器上运行 Ruby on Rails 才能运行。Ruby on Rails 是用 Ruby 编写的,因此也需要 Ruby。为了方便地在 VM 上安装和维护 Ruby,我们还将安装 rvm(Ruby 版本管理器)。这不是必需的,但我现在不再会不使用 rvm 安装 Ruby。使用 rvm,您可以安装不同版本的 Ruby 并在它们之间轻松切换。一些 Ruby on Rails Web 应用程序要求特定的 Ruby 版本。如果您不得不在没有 rvm 的情况下在同一 VM 上设置两个或更多 Rails 应用程序,这可能会非常痛苦。ErrBit 将所有数据存储在 MongoDB (NoSQL) 数据库中,当然这并非预装。我们还将安装 bundler 来管理所需的 Gems。在 Ruby 中,Gem 与我们在所有其他编程语言中称之为库的东西非常相似。而 bundler 有点像 .NET 世界中的 NUGET。然而,Gem 也可以是一个独立的程序,所以它不仅仅是一个库。我们还将在我们的 VM 中安装 git,以便我们可以轻松地从 github 克隆最新的 errbit 版本。

要设置我们的虚拟机,我们只需登录管理中心并选择虚拟机选项卡。

在这个选项卡中,我们需要点击页面底部的“新建”按钮。

这将从底部弹出一个菜单。选择“从图库”以获取可以在 VM 中使用的操作系统的巨大列表,

我们将使用 Ubuntu Server 12.04 LTS 作为我们的操作系统。

LTS 后缀是 Long Term Support 的缩写,它将保证我们能够获得该操作系统的更新。此版本 12.04 的支持将持续到 2017 年 4 月 26 日,这是一个相当长的时间。

在接下来的 4 个步骤中,我们必须设置不同的内容。

我们操作系统的发布日期(使用最新的),我们虚拟机的名称,一个新的用户名、密码,虚拟机大小、位置和 DNS 名称。所有这些都是不言自明的,Azure 向导将引导我们完成这些步骤。

一旦我们完成了最后一步并创建了 VM,我们的 VM 选项卡将更新,我们可以看到我们的 VM 正在启动,最终在几分钟后准备就绪并运行。

连接到虚拟机

我们的 VM 现在已设置好,正在运行,但什么也没做。我们现在无法访问网页,因为还没有运行 HTTP 服务器,即使有,端口也已被阻止。要连接到我们的 VM,我们需要知道应该连接哪个端口。SSH 通常在端口 22 上运行,然而,出于安全原因,将其移动到另一个端口是个好主意,这样那些扫描互联网以寻找“典型”开放端口的机器人就会跳过这个主机。要获取正确的端口,我们需要转到当前创建的 VM 中的“ENDPOINT”选项卡。

我们看到 SSH 守护程序确实在端口 22 上运行,但是公共端点被重定向到端口 58317。

有了这些信息,我们现在可以连接到我们的虚拟机了。本教程使用 Linux 编写,因此可以轻松地从控制台使用 ssh。Windows 用户需要下载并使用 Putty(只是一种可能性,但可能是最好的)。

使用以下命令,我们可以连接到我们的虚拟机。

ssh errbitserver.cloudapp.net -p 58317 -l ErrBitUser 
  1. **errbitserver.cloudapp.net** 是我们的主机名,我们也可以使用服务器的 IP 地址。
  2. 使用 **-p 58317** 指定要连接的端口。(默认值为 22)
  3. 使用 **-l ErrBitUser**,我们使用另一个用户名(l 表示登录),而不是我们本地操作系统中当前使用的用户名。

Linux 在您第一次尝试通过 SSH 连接到另一台机器时会有些抱怨,并询问您是否真的要这样做。一旦您通过输入 yes(或只需“y”)接受了这一点,服务器指纹将被添加到已知主机列表中,并且此问题将不再出现。

现在我们只需输入我们在 Azure 管理中心设置虚拟机时选择的密码即可连接。

Linux 入门  

在本节中,我们将安装运行 ErrBit 服务所需的所有必需应用程序、库和工具。这将是一个完整的指南,我们不会跳过任何步骤,因为并非所有人都熟悉 Linux!每当我们连接到新的、干净的 Ubuntu 安装时,都应该运行 `apt-get update`。

sudo apt-get update

这不会更新操作系统本身或所有已安装的组件/工具。它只会更新引用和包列表,其中包含指向我们可能想要安装的工具的最新版本的链接。

  1. apt 是 **Advanced Packaging Tool** 的简称。
  2. apt-get 是一种前端工具,用于获取或下载各种其他工具。
  3. update 用于从其来源重新同步包索引文件(引用自 Wikipedia) 
  4. sudo 是一个程序,它以 root / 管理员权限运行以下工具 / 程序 / 命令   

注意:APT 在全新安装后并非在所有 Linux 发行版上都可用。

运行此命令几分钟后,我们应该已更新并能够继续。

我们将首先用一个命令安装大量工具和库。

sudo apt-get -y install curl git-core patch 
\build-essential bison zlib1g-dev libssl-dev libxml2-dev 
\libxml2-dev sqlite3 libsqlite3-dev autotools-dev 
\libxslt1-dev libyaml-0-2 autoconf automake libreadline6-dev 
\libyaml-dev libtool

其中一些应该是已知的

  1. curl
  2. git-core
  3. sqlite3

... 其他只是依赖项、库或开发工具,例如:automake。

然而,这个命令会花费一些时间。幸运的是,我们在命令中使用了 **-y**,它会在安装过程中回答所有问题(例如“您真的要下载这 25MB 的数据吗”或“git 还需要安装 BLABLA,您要一起安装吗”)。

几分钟后,我们应该准备就绪,最终能够开始真正的第一步。

安装 rvm,Ruby 版本管理器。

我们将使用此命令从 GitHub 下载并运行一个“即用型”rvm 安装程序。

bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)    

在 Linux 中,通常将一堆命令组合在一起,这使得非 Linux 用户有时难以理解发生了什么。实际上,我们在这里使用了两个工具。

  1. bash,它是 Brian Fox 编写的 Unix shell(在 Linux 上可以并行拥有不同的 shell)
  2. curl,一个命令行工具,用于传输文件。

我们也可以在浏览器中打开此脚本的 URL,并获取有关其工作原理的一些详细信息。但这不应是本教程的一部分。

这条命令将以明智的词语结束。

会的!会的!谢谢韦恩!

但是为了确保 rvm 可以在我们的终端(shell)中使用,我们需要设置更多东西。每个人在生命中的某个时刻都应该安装过 Java 并正确设置了 PATH,我们在这里也需要做同样的事情。

我们正在使用“nano”,这是一个 Linux 控制台文本编辑器来设置它。

nano ~/.bashrc

一旦我们打开了 bashrc 文件,我们只需要添加这两行代码。

PATH=$PATH:~/.rvm/bin

[[ -s "$HOME/.rvm/scripts/rvm" ]] && . “$HOME/.rvm/scripts/rvm”

将这些行添加到第一个注释和第二个注释之间(注释以 **# ** 开头)。

要关闭窗口,我们需要按下

[ctrl] + [o] to save our file ( + hit enter to overwrite the existing file)  
[ctrl] + [x] to leave the nano editor 

我们现在应该能够使用 rvm。让我们用以下命令检查当前版本。

rvm -v 

当然,它没有奏效!

通常,对路径文件的更改之后会重启机器。

但我们可以通过运行此命令来重新启动(更好地说:重新加载)我们的终端设置。

. ~/.bashrc

再次运行相同的 `rvm -v` 命令现在应该会打印以下消息:

最后我们现在可以安装 Ruby 了。我们将使用 ruby-1.9.2,这是一个非常稳定的 Ruby 版本。
有了

rvm install ruby-1.9.2 

魔法会自动发生(我们只需要再次输入密码)

此时我们需要明白,我们刚刚从源代码编译了 Ruby。这也是它花费如此长时间的原因(当然,在 CPU 更强的 VM 上会更快)。

我们应该检查我们的 Azure 管理界面,并为我们的 VM 选择监视器选项卡。CPU 使用率应该非常高,编译 Ruby 需要一些性能! 

为了能够使用 ruby,我们需要输入另外两个命令。第三个命令再次只是检查一切是否正常。

/bin/bash --login
rvm default 1.9.2
ruby -v 

第一个命令告诉我们的终端从现在开始使用 bash 解释器。第二个命令告诉我们的 ruby 版本管理器默认应该使用哪个 ruby 版本。这当然是 1.9.2,我们安装的唯一一个。第三个命令应该打印 ruby 版本。

在开始 Ruby on Rails 安装之前,我们将更新一些 Gems 和 Gem 管理系统本身。

rvm rubygems current

gem update 

将为我们完成这一切。再次,这将需要一些时间。但我们不想因为旧的 Gems/Libs 而产生冲突,所以这应该在每次新的 Ruby 设置后都完成。

最后,我们可以运行命令来安装 Ruby on Rails。

gem install rails

这又一次花费了一些时间。

但最终我们可以运行 **rails -v** 来测试一切是否顺利。

rails -v

一切都比预期好。我们成功安装了 Rails 3.2.13!

MongoDB

不幸的是,Ubuntu 不附带最稳定的 MongoDB 源/包列表,因此我们需要在运行安装之前添加自己的源。

有了

sudo nano /etc/apt/sources.list.d/10gen.list

我们将在 `sources.list.d` 目录中创建一个名为 `10get.list` 的文件(需要密码,因为我们将更改系统中的一些内容)。

将这些行添加到其中

##10gen package locationdeb 
deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen 

按 [ctrl] + [o] 保存文件,然后按 [ctrl] + [x] 退出。

10gen 仓库(10gen 是 MongoDB 背后的公司)需要 GPG 密钥。这使得他们的仓库非常安全。只需运行此命令即可确保您以后不会遇到任何错误。

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv 7F0CEB10 

由于我们通过添加新的包源更改了我们的包源,因此再次更新列表可能是一个好主意。

sudo apt-get update  

将获取新数据(实际上,它会再次检查包源列表并告诉您它们都是最新的。只有新添加的会被刷新)。

现在我们终于可以运行将 MongoDB NOSQL 数据库安装到我们的 VM 上的命令了。

sudo apt-get install mongodb-10gen 

如上图第 10 行所示,安装正在使用我们新添加的源来获取最新稳定版本。安装将通过启动 mongodb 进程完成。

更多所需工具...  

我们到目前为止所做的就是设置一个虚拟机,它为开始 Ruby on Rails 开发做好了充分准备。然而,ErrBit 还有一些其他要求。GitHub 页面告诉了我们所有要求,我们只需遵循这些步骤。

sudo apt-get -y install libxml2 libxml2-dev libxslt-dev libcurl4-openssl-dev 

安装完这些库后,我们通过安装 bundler 来完成。

gem install bundler  

获取 ErrBit 并运行它! 

我们需要将 ErrBit 从 GitHub 克隆到我们的本地机器。为了清晰和结构化,我们首先创建一个新文件夹,切换到该文件夹,然后克隆 Git 仓库。

mkdir ErrBit
cd ErrBit
git clone https://github.com/K2DaC/errbit.git

为了最终完成,我们切换到名为 errbit 的 git 仓库,并告诉 bundler 获取 errbit 所需的所有 gems。

cd errbit
bundle install

我们现在正在为 ErrBit 安装诸如 JSON 解析器、HTTP 身份验证库以及许多其他东西。

安装 HTTParty Gem 总是很有趣的 微笑 | <img src= 

安装完所有所需组件后,我们需要启动 ErrBit。

这只意味着我们需要复制一些配置文件并准备数据库(创建索引等)。请记住,我们使用的是 NOSQL 数据库。我们这里没有创建表...

rake errbit:bootstrap  

最后,我们可以运行 Rails 服务器了!

script/rails server -d 

Ruby on Rails 自带一个名为 Thin 的 Web 服务器,可用于调试。该服务器默认在端口 3000 上运行。但是如果我们尝试连接到该端口,我们将看不到我们的 ErrBit 服务。防火墙正在阻止我们的连接。我们需要到 Azure 的管理系统并打开所需的端口。

我们现在可以访问我们的网站并打开它(在端口 3000 上)

默认用户名和密码应该在 `seeds.rb` 文件中设置。由于我们还没有这样做,我们只需使用为我们创建的默认用户名和密码。

http://errbitserverv2.cloudapp.net:3000/ 

User : errbit@errbit.example.com
Password:  password  

我们可以用它登录,然后看到我们“即用型”的解决方案。

ErrBit 测试

我们的服务已经启动并运行,但我们还没有使用它。我们需要一些东西来测试并查看 ErrBit 为我们提供了什么。

  1. 我们需要一个新的应用程序。  

只需点击右上角的“新建应用”按钮并为其命名。

我们可以在这里设置很多东西。例如,每次发现新错误时,我们都可以收到电子邮件。我们还可以使用问题跟踪器,这样每个错误(当然,只在首次报告时)都会在 Redmine(一个票务/错误跟踪工具)上创建一个工单。

但我们只给我们的应用起个名字:AndroidApp

点击保存按钮后,我们会得到一个信息页面,说明我们现在如何在 Ruby on Rails 中使用我们的应用程序。

Airbrake.configure do |config|
   config.api_key = '52c9ca0c3b462037759ece317ba8d790'
   config.host = 'errbitserver.cloudapp.net'
   config.port = 3000
   config.secure = config.port == 443
end   

我们不会在 Ruby on Rails 应用程序中使用它。我们现在将在 Android 中使用它(几乎可以在任何编程语言中使用)。

首先,我们需要从 GitHub 下载并包含现有 ErrBit 通知器库。我们在 Eclipse 中设置一个新的 Android 项目,正确地将我们的库链接到它,并调用这个方法。

ErrbitNotifier.register(this, "errbitserverv2.cloudapp.net:3000","52c9ca0c3b462037759ece317ba8d790");  

既然我们在这里要报告错误/崩溃,我们需要在这里强制抛出一个异常。

我们只需这样做

List l = null;
l.add("CRASH"); 

现在我们只需运行它,看看会发生什么。当然,我们的应用程序崩溃了,但我们也在 ErrBit 服务中得到了结果。

如果我们点击我们的应用程序,我们会得到更多信息。

最后,我们可以追踪这个错误以获取更多信息。

为 Windows Phone 7 创建我们自己的通知器 

几乎每个平台/语言都有通知器。

  1. Android
  2. iOS 
  3. PHP
  4. ...

也有一个 .NET 解决方案,但我还没有找到一个真正的 Windows Phone 解决方案,它真正关心不同的可能场景。例如:没有互联网连接时发生崩溃!

要求

  1. 能够向 ErrBit 和 ErrZure 报告错误
    1. 使用相同的 XML 布局
    2. 使用相同的 API 结构
    3. 将 HTTP POST 错误发送到服务 
  2. 将错误保存到本地存储,以便在没有互联网连接时能够稍后发送它们。
  3. 能够轻松设置 API-KEY / 主机

我们的目标是通过这一行代码初始化我们的库。

ErrBitNotify.Register("API-KEY", "ENDPOINT", this); 

这就是为什么我们需要一个带有 3 个参数的注册方法。

  1. API KEY,用于在我们的服务器中识别应用程序
  2. 端点,使其可以在任何地方使用
  3. Application 类,这样我们就可以附加到 UnhandledExceptionEvent

该方法主要在一切正常时准备事物。但该方法也负责发送上次会话中的异常。例如,在 Android 上,抛出异常后,您有足够的时间来做各种事情。只需要一个新的线程。而在 Windows Phone 上,您只有几秒钟的时间。这可能不足以将异常发送到服务器,所以我们首先存储它们,以确保我们永远不会丢失它们。

public static void Register(String apiKey, String endpoint, Application app)
{
   mApiEndpoint = "http://" + endpoint + "/notifier_api/v2/notices";
   mApiKey = apiKey;
   mAppVersion = 
     System.Reflection.Assembly.GetExecutingAssembly().FullName.Split('=')[1].Split(',')[0];
   app.UnhandledException += 
     new EventHandler<ApplicationUnhandledExceptionEventArgs>(app_UnhandledException);
   SendAllExceptionsToServer();
}

如您所见,我们准备了主机 URL 并检测了应用程序版本。这些信息可以为您节省大量时间。只需对您的 QA 部门说“已修复”;)。我们还为 `UnhandledException` 创建了一个新的 `EventHandler`。最后,我们尝试将所有异常发送到服务器。在大多数情况下,此方法将不做任何事情(我们希望如此),但如果您的上次会话中出现了错误,并且库无法将所有信息传输到服务器,我们现在就会这样做。

我们的回调函数主要做两件事。

  1. 以正确的格式/布局创建 XML 文件。
  2. 将异常传输到服务器(或至少尝试)
static void app_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    mException = e.ExceptionObject;
    WriteXMLToFile();
    SendAllExceptionsToServer();
}

在 Windows Phone 中使用 XML 文件非常直接。在设备上保存文件也是如此。

public static void WriteXMLToFile()
{
   using (IsolatedStorageFile myIsolatedStorage = 
             IsolatedStorageFile.GetUserStoreForApplication())
   {
       if (!myIsolatedStorage.DirectoryExists(DIR))
            myIsolatedStorage.CreateDirectory(DIR);

       String time = "" + DateTime.Now.Ticks;
       String filename = mAppVersion + "-" + time + ".xml";
       using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream(
         DIR + "\\" + filename, FileMode.Create, myIsolatedStorage))
       {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(isoStream, settings))
            {
                List<ParsedException> list = SplitException();
                writer.WriteStartElement("notice", "");
                writer.WriteAttributeString("version", "2.0");
                writer.WriteStartElement("api-key", "");
                writer.WriteString(mApiKey);
                writer.WriteEndElement();
                writer.WriteStartElement("notifier", "");
                //More meta-info here
                writer.WriteString(mException.GetType().FullName);
                writer.WriteEndElement();
                //More details here, e.g. stacktrace ...                       
                writer.Flush();
           }
       }
   }
}

如上面代码所示,我们有一个唯一的根据 `DateTime.Now.Ticks` 生成的文件名。我们还将它们存储在单独的文件夹中,以便以后只能访问我们自己的文件,并确保不会与其他应用程序发生任何冲突。

在我们“处理”了异常并将其存储在设备内存中之后,我们最终可以将其传输到服务器。

SendAllExceptionsToServer() 方法只是遍历我们文件夹中的所有文件,将它们一个接一个地传输到我们的服务器,并在本地删除它们。

string searchPattern = DIR + "\\*";
string[] fileNames = myIsolatedStorage.GetFileNames(searchPattern);
if (fileNames.Length > 0)
{ // DO THE REAL WORK } 

“真正的工作”只是这些 POST 请求的准备。

HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(mApiEndpoint);
httpWebRequest.Method = "POST";
httpWebRequest.ContentType = "application/xml; charset=utf-8";
httpWebRequest.BeginGetRequestStream(result =>
{
    PostWebRequest(result, httpWebRequest, fileNames[0]);
}, null); 

使用 fileNames[0],我们总是只获取第一个并用它发送...。

private static void PostWebRequest(IAsyncResult result,
                              HttpWebRequest request,
                              string filename)
{
    Stream postStream = request.EndGetRequestStream(result);
    String post;
    using (IsolatedStorageFile myIsolatedStorage = 
      IsolatedStorageFile.GetUserStoreForApplication())
    {
        IsolatedStorageFileStream isoFileStream = 
          myIsolatedStorage.OpenFile(DIR + "//" + filename, FileMode.Open);
        using (StreamReader reader = new StreamReader(isoFileStream))
        {
            post = reader.ReadToEnd();
        }
    }
    byte[] postBytes = Encoding.UTF8.GetBytes(post);
    postStream.Write(postBytes, 0, postBytes.Length);
    postStream.Close();

    request.BeginGetResponse(res =>
    {
        GetResponseCallback(res, request, filename);
    }, null);

}

我们的最终回调处理服务器响应。

private static void GetResponseCallback(IAsyncResult asynchronousResult, 
          HttpWebRequest request, string filename)
{
    HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asynchronousResult);
    HttpStatusCode rcode = response.StatusCode;
    if (rcode.Equals((HttpStatusCode.OK)))
    {
        using (IsolatedStorageFile myIsolatedStorage = 
                 IsolatedStorageFile.GetUserStoreForApplication())
        {
            myIsolatedStorage.DeleteFile(DIR + "\\" + filename);
        }
        SendAllExceptionsToServer(); //Keep sending files until all are submitted!
    }
} 

如果一切顺利,我们将收到服务器返回的 200 OK 响应代码。(这可能不是使用 http 协议的最佳实践,201 CREATED 会更好)。

如果是这种情况,我们只需删除已提交的文件并再次调用 `SendAllExceptionsToServer()`。

为了测试所有内容,我们在 ErrBit 服务中创建了一个新应用,并强制在 Windows Phone 应用中抛出一个异常。

结果可以在 ErrBit 中看到:我们新创建的应用程序现在有一个错误。

如果我们点击链接到下一页,我们会看到我很高兴测试所有场景:没有互联网连接,缓慢的互联网连接,...我强制产生了 28 个错误。

最终的详细信息页面提供了最有用的信息:最终导致崩溃的方法。

历史

  • 2013 年 4 月 26 日,启动 
  • 2013 年 4 月 26 日,格式   
  • 2013 年 4 月 27 日,添加了更多关于系统的细节。修正了 AirBrake 的拼写错误。 
  • 2013 年 4 月 27 日,修正了历史记录中的错误日期。  
  • 2013 年 4 月 28 日,第一个 UI 草图 
  • 2013 年 4 月 30 日,添加了 XML 布局  
  • 2013 年 4 月 30 日,替换了丑陋的线框图,添加了移动线框图 
  • 2013 年 5 月 1 日,开始挑战 2 
  • 2013 年 5 月 2 日,完成挑战 2  
  • 2013 年 5 月 10 日,添加了一个彩蛋
  • 2013 年 5 月 11 日,修正了错别字(感谢 roschler)  
  • 2013 年 5 月 26 日,挑战 3 
  • 2013 年 6 月 1 日,挑战 4 
  • 2013 年 6 月 3 日,更新了挑战 4,修复了缺失图片,修复了错别字 
  • 2013 年 6 月 3 日,更新了挑战 4,将 URL 添加到本文顶部 
  • 2013 年 6 月 6 日,更新了挑战 4,添加了 Windows Phone 通知器  
  • 2013 年 6 月 9 日,修正了一些错别字 
  • 2013 年 6 月 14 日,更改了 ErrBit VM/服务的 URL 
  • 2013 年 6 月 14 日,添加了快速跳转并再次修复了链接!  
© . All rights reserved.