From 26a1e7a18bb15a21c34fa7b8ba15d89978a3a396 Mon Sep 17 00:00:00 2001 From: Rich Cavanaugh Date: Sat, 23 Jan 2010 14:59:47 -0500 Subject: [PATCH] Remove the dependency on Horde's YAML library. Add ability to specify the environment the app is running in. Support v2 of the Hoptoad API. --- Hoptoad.php | 292 ++++++++++++++++++++++++++++-------------------- README.markdown | 15 +-- example.php | 2 +- 3 files changed, 179 insertions(+), 130 deletions(-) diff --git a/Hoptoad.php b/Hoptoad.php index f49467e..cf914cc 100644 --- a/Hoptoad.php +++ b/Hoptoad.php @@ -1,123 +1,177 @@ getTrace()); - - Hoptoad::notifyHoptoad(HOPTOAD_API_KEY, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace, null); - } - - /** - * Pass the error and environment data on to Hoptoad - * - * @package default - * @author Rich Cavanaugh - */ - public static function notifyHoptoad($api_key, $message, $file, $line, $trace, $error_class=null) - { - $req =& new HTTP_Request("http://hoptoadapp.com/notices/", array("method" => "POST", "timeout" => 2)); - $req->addHeader('Accept', 'text/xml, application/xml'); - $req->addHeader('Content-type', 'application/x-yaml'); - - array_unshift($trace, "$file:$line"); - - if (isset($_SESSION)) { - $session = array('key' => session_id(), 'data' => $_SESSION); - } else { - $session = array(); - } - - $url = "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; - $body = array( - 'api_key' => $api_key, - 'error_class' => $error_class, - 'error_message' => $message, - 'backtrace' => $trace, - 'request' => array("params" => $_REQUEST, "url" => $url), - 'session' => $session, - 'environment' => $_SERVER - ); - - $req->setBody(Horde_Yaml::dump(array("notice" => $body))); - $req->sendRequest(); - } - - /** - * Build a trace that is formatted in the way Hoptoad expects - * - * @param string $trace - * @return void - * @author Rich Cavanaugh - */ - public static function tracer($trace = NULL) - { - $lines = Array(); - - $trace = $trace ? $trace : debug_backtrace(); - - $indent = ''; - $func = ''; - - foreach($trace as $val) { - if (isset($val['class']) && $val['class'] == 'Hoptoad') continue; - - $file = isset($val['file']) ? $val['file'] : 'Unknown file'; - $line_number = isset($val['line']) ? $val['line'] : ''; - $func = isset($val['function']) ? $val['function'] : ''; - $class = isset($val['class']) ? $val['class'] : ''; - - $line = $file; - if ($line_number) $line .= ':' . $line_number; - if ($func) $line .= ' in function ' . $func; - if ($class) $line .= ' in class ' . $class; - - $lines[] = $line; - } - - return $lines; - } -} \ No newline at end of file + const NOTIFIER_NAME = 'php-hoptoad-notifier'; + const NOTIFIER_VERSION = '0.2.0'; + const NOTIFIER_URL = 'http://github.com/rich/php-hoptoad-notifier'; + const NOTIFIER_API_VERSION = '2.0'; + + /** + * Install the error and exception handlers that connect to Hoptoad + * + * @return void + * @author Rich Cavanaugh + */ + public static function installHandlers($api_key=NULL, $environment=NULL) + { + if (isset($api_key)) define('HOPTOAD_API_KEY', $api_key); + if (isset($environment)) define('HOPTOAD_APP_ENVIRONMENT', $environment); + + set_error_handler(array("Hoptoad", "errorHandler")); + set_exception_handler(array("Hoptoad", "exceptionHandler")); + } + + /** + * Handle a php error + * + * @param string $code + * @param string $message + * @param string $file + * @param string $line + * @return void + * @author Rich Cavanaugh + */ + public static function errorHandler($code, $message, $file, $line) + { + if ($code == E_STRICT) return; + + $trace = Hoptoad::tracer(); + Hoptoad::notifyHoptoad(HOPTOAD_API_KEY, $message, $file, $line, $trace, null, HOPTOAD_APP_ENVIRONMENT); + } + + /** + * Handle a raised exception + * + * @param string $exception + * @return void + * @author Rich Cavanaugh + */ + public static function exceptionHandler($exception) + { + $trace = Hoptoad::tracer($exception->getTrace()); + + Hoptoad::notifyHoptoad(HOPTOAD_API_KEY, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace, null, HOPTOAD_APP_ENVIRONMENT); + } + + /** + * Pass the error and environment data on to Hoptoad + * + * @package default + * @author Rich Cavanaugh + */ + public static function notifyHoptoad($api_key, $message, $file, $line, $trace, $error_class=null, $environment='production') + { + array_unshift($trace, "$file:$line"); + + $adapter = new HTTP_Request2_Adapter_Socket; + $req = new HTTP_Request2("http://hoptoadapp.com/notifier_api/v2/notices", HTTP_Request2::METHOD_POST); + $req->setAdapter($adapter); + $req->setHeader(array( + 'Accept' => 'text/xml, application/xml', + 'Content-Type' => 'text/xml' + )); + $req->setBody(self::buildXmlNotice($api_key, $message, $trace, $error_class, $environment)); + echo $req->send()->getBody(); + } + + /** + * Build up the XML to post according to the documentation at: + * http://help.hoptoadapp.com/faqs/api-2/notifier-api-v2 + * @return string + * @author Rich Cavanaugh + **/ + public static function buildXmlNotice($api_key, $message, $trace, $error_class, $environment, $component='') + { + $doc = new SimpleXMLElement(''); + $doc->addAttribute('version', self::NOTIFIER_API_VERSION); + $doc->addChild('api-key', $api_key); + + $notifier = $doc->addChild('notifier'); + $notifier->addChild('name', self::NOTIFIER_NAME); + $notifier->addChild('version', self::NOTIFIER_VERSION); + $notifier->addChild('url', self::NOTIFIER_URL); + + $error = $doc->addChild('error'); + $error->addChild('class', $error_class); + $error->addChild('message', $message); + + $backtrace = $error->addChild('backtrace'); + foreach ($trace as $line) { + $line_node = $backtrace->addChild('line'); + list($file, $number) = explode(':', $line); + $line_node->addAttribute('file', $file); + $line_node->addAttribute('number', $number); + $line_node->addAttribute('method', ''); + } + + $request = $doc->addChild('request'); + $request->addChild('url', "http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"); + $request->addChild('component', $component); + + if (isset($_REQUEST) && !empty($_REQUEST)) { + $params = $request->addChild('params'); + foreach ($_REQUEST as $key => $val) { + $var_node = $params->addChild('var', $val); + $var_node->addAttribute('key', $key); + } + } + + if (isset($_SESSION) && !empty($_SESSION)) { + $session = $request->addChild('session'); + foreach ($_SESSION as $key => $val) { + $var_node = $session->addChild('var', $val); + $var_node->addAttribute('key', $key); + } + } + + $cgi_data = $request->addChild('cgi-data'); + foreach ($_SERVER as $key => $val) { + $var_node = $cgi_data->addChild('var', $val); + $var_node->addAttribute('key', $key); + } + + $env = $doc->addChild('server-environment'); + $env->addChild('project-root', $_SERVER['DOCUMENT_ROOT']); + $env->addChild('environment-name', $environment); + + return $doc->asXML(); + } + + /** + * Build a trace that is formatted in the way Hoptoad expects + * + * @param string $trace + * @return void + * @author Rich Cavanaugh + */ + public static function tracer($trace = NULL) + { + $lines = Array(); + + $trace = $trace ? $trace : debug_backtrace(); + + $indent = ''; + $func = ''; + + foreach($trace as $val) { + if (isset($val['class']) && $val['class'] == 'Hoptoad') continue; + + $file = isset($val['file']) ? $val['file'] : 'Unknown file'; + $line_number = isset($val['line']) ? $val['line'] : ''; + $func = isset($val['function']) ? $val['function'] : ''; + $class = isset($val['class']) ? $val['class'] : ''; + + $line = $file; + if ($line_number) $line .= ':' . $line_number; + if ($func) $line .= ' in function ' . $func; + if ($class) $line .= ' in class ' . $class; + + $lines[] = $line; + } + + return $lines; + } +} diff --git a/README.markdown b/README.markdown index 306eb64..5007382 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,6 @@ # Introduction -This is a simple [Hoptoad](http://hoptoadapp.com) notifier for PHP. It's been used in a few production sites now with success. It's not quite as fully featured as the official Ruby notifier but it works well. +This is a simple [Hoptoad](http://hoptoadapp.com) notifier for PHP. It's been used in a few production sites now with success. It's not quite as fully featured as the official Ruby notifier but it works well. # Limitations @@ -10,18 +10,13 @@ For deploy tracking, since I use Capistrano to deploy my PHP apps, I simply use # Requirements -The notifier uses the Horde_Yaml class. You can install this class using the commands below. +Install Pear's HTTP_Request2: - pear channel-discover pear.horde.org - pear install horde/yaml - -It also uses Pear's HTTP_Request: - - pear install HTTP_Request + pear install HTTP_Request2 # License -Copyright (c) 2009, Rich Cavanaugh +Copyright (c) 2010, Rich Cavanaugh All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -30,4 +25,4 @@ Redistribution and use in source and binary forms, with or without modification, * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/example.php b/example.php index bdd0b52..83fe8a7 100644 --- a/example.php +++ b/example.php @@ -1,4 +1,4 @@