Skip to content

Commit

Permalink
Remove the dependency on Horde's YAML library.
Browse files Browse the repository at this point in the history
Add ability to specify the environment the app is running in.
Support v2 of the Hoptoad API.
  • Loading branch information
rich committed Jan 23, 2010
1 parent 9448129 commit 26a1e7a
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 130 deletions.
292 changes: 173 additions & 119 deletions Hoptoad.php
@@ -1,123 +1,177 @@
<?php
if (!class_exists('HTTP_Request')) require_once('HTTP/Request.php');
if (!class_exists('Horde_Yaml')) require_once('Horde/Yaml.php');
if (!class_exists('Horde_Yaml_Dumper')) require_once('Horde/Yaml/Dumper.php');
if (!class_exists('HTTP_Request2')) require_once('HTTP/Request2.php');
if (!class_exists('HTTP_Request2_Adapter_Socket')) require_once 'HTTP/Request2/Adapter/Socket.php';

class Hoptoad
{
/**
* Install the error and exception handlers that connect to Hoptoad
*
* @return void
* @author Rich Cavanaugh
*/
public static function installHandlers($api_key=NULL)
{
if (isset($api_key)) define('HOPTOAD_API_KEY', $api_key);

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);
}

/**
* 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);
}

/**
* 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;
}
}
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('<notice />');
$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;
}
}
15 changes: 5 additions & 10 deletions 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

Expand All @@ -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:
Expand All @@ -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.
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.
2 changes: 1 addition & 1 deletion example.php
@@ -1,4 +1,4 @@
<?php
require_once('Hoptoad.php');

Hoptoad::installHandlers("YOUR_HOPTOAD_API_KEY");
Hoptoad::installHandlers("YOUR_HOPTOAD_API_KEY", 'production');

0 comments on commit 26a1e7a

Please sign in to comment.