├── LICENSE ├── README.md ├── config └── schema │ └── schema.sql ├── controllers └── components │ └── whistle.php ├── libs ├── db_referee_listener.php ├── email_referee_listener.php ├── referee_listener.php ├── referee_mailer.php ├── referee_whistle.php └── syslog_referee_listener.php ├── models └── referee_log.php ├── referee_app_model.php ├── tests ├── cases │ ├── components │ │ └── whistle.test.php │ ├── libs │ │ └── referee_whistle.test.php │ └── models │ │ └── referee_log.test.php ├── fixtures │ └── referee_log_fixture.php └── libs │ ├── custom_test_listener.php │ └── test_referee_listener.php └── views ├── elements └── email │ ├── html │ └── default.ctp │ └── text │ └── default.ctp └── layouts └── email ├── html └── default.ctp └── text └── default.ctp /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Curtis (Joe) Beeson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Referee Plugin for CakePHP 1.3+ 2 | 3 | Referee plugin catches errors and dispatches them to custom listeners. 4 | 5 | ## Installation 6 | 7 | * Download the plugin 8 | 9 | $ cd /path/to/your/app/plugins && git clone git://github.com/joebeeson/referee.git 10 | 11 | * Create the schema 12 | 13 | $ cake schema create Referee.schema 14 | 15 | * Add the component and attach your listeners 16 | 17 | public $components = array( 18 | 'Referee.Whistle' => array( 19 | 'listeners' => array( 20 | 'DbLog', 21 | 'SysLog' 22 | ) 23 | ) 24 | ); 25 | 26 | ## Listeners 27 | 28 | Listeners perform the actual leg work in handling an error. Their only requirement is that they have a public function that the `WhistleComponent` can trigger when it needs to notify the listener of an error. 29 | 30 | You can configure listeners in the `$components` declaration for the `WhistleComponent`, here's an example of some configuration options... 31 | 32 | public $components = array( 33 | 'Referee.Whistle' => array( 34 | 'listeners' => array( 35 | 36 | 'YourLogger' => array( 37 | 38 | // The error type(s) that should trigger this listener. Defaults to E_ALL 39 | 'levels' => E_ERROR, 40 | 41 | // The method to call to pass an error, defaults to 'error' 42 | 'method' => 'customMethod', 43 | 44 | // The class to instantiate, defaults to (name)Listener, YourLoggerListener in this case 45 | 'class' => 'yourCustomListenerClass', 46 | 47 | 'parameters' => array( 48 | /** 49 | * Anything in here will be passed to the listener's 50 | * error method when an error occurs. 51 | */ 52 | ) 53 | 54 | ) 55 | 56 | ) 57 | ) 58 | ); 59 | 60 | If you'd like to instantiate multilpe instances of the `YourLogger` listener, in this instance, simply nest multiple arrays, one for each instantiation, with the configuration for each specific instance -- [similar to how you do multiple validations on models][2]. 61 | 62 | You can also attach listeners using the `attachListener` method. It will return a boolean to indicate success in attaching the listener. 63 | 64 | $this->Whistle->attachListener( 65 | 'YourLogger', 66 | array( 67 | 'levels' => E_ERROR, 68 | 'method' => 'customMethod', 69 | 'class' => 'yourCustomListenerClass', 70 | 'parameters' => array( 71 | // Optional parameters to pass 72 | ) 73 | ) 74 | ); 75 | 76 | The method that is invoked by the `WhistleComponent` should accept two parameters: `$error` and `$parameters` -- the `$error` is an associative array describing the error that occurred and `$parameters` is an array containing the parameters (*if any*) that were declared in the `$components` declaration for the listener. 77 | 78 | ## Notes 79 | 80 | Previous versions of the plugin handled the recording of all errors to the database, there is no longer such automatic functionality. If you'd like something similar there is a `DbLog` listener available. 81 | 82 | [1]: http://www.codinghorror.com/blog/2009/04/exception-driven-development.html 83 | [2]: http://book.cakephp.org/view/133/Multiple-Rules-per-Field 84 | -------------------------------------------------------------------------------- /config/schema/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `referee_logs`; 2 | 3 | CREATE TABLE `referee_logs` ( 4 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 5 | `level` varchar(32) NOT NULL DEFAULT '', 6 | `file` varchar(255) NOT NULL DEFAULT '', 7 | `line` int(4) unsigned NOT NULL DEFAULT '0', 8 | `class` varchar(32) DEFAULT NULL, 9 | `function` varchar(32) DEFAULT NULL, 10 | `args` text, 11 | `type` varchar(8) DEFAULT NULL COMMENT 'method, static, function', 12 | `message` text NOT NULL, 13 | `trace` text, 14 | `request_method` varchar(6) DEFAULT '', 15 | `request_plugin` varchar(32) DEFAULT NULL, 16 | `request_controller` varchar(32) DEFAULT '', 17 | `request_action` varchar(32) DEFAULT '', 18 | `request_ext` varchar(8) DEFAULT NULL, 19 | `request_parameters` text, 20 | `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', 21 | PRIMARY KEY (`id`) 22 | ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 23 | -------------------------------------------------------------------------------- /controllers/components/whistle.php: -------------------------------------------------------------------------------- 1 | 12 | * @author Joshua McNeese 13 | * @see http://blog.joebeeson.com/monitoring-your-applications-health/ 14 | * @uses RefereeWhistle 15 | * @todo Add method to manually log errors (->error()) 16 | */ 17 | class WhistleComponent extends RefereeWhistle { 18 | 19 | /** 20 | * @var Controller 21 | */ 22 | protected $_controller; 23 | 24 | /** 25 | * Include HTTP request information, if present 26 | * 27 | * @var boolean 28 | */ 29 | public $includeRequest = true; 30 | 31 | /** 32 | * Get relevant controller parameters 33 | * 34 | * @return array 35 | */ 36 | protected function _getControllerParams() { 37 | $params = array( 38 | 'request_method' => env('REQUEST_METHOD'), 39 | 'request_plugin' => !empty($this->_controller->params['plugin']) ? $this->_controller->params['plugin'] : null, 40 | 'request_controller' => !empty($this->_controller->params['controller']) ? $this->_controller->params['controller'] : Inflector::underscore($this->_controller->name), 41 | 'request_action' => !empty($this->_controller->params['action']) ? $this->_controller->params['action'] : Inflector::underscore($this->_controller->action), 42 | 'request_ext' => !empty($this->_controller->params['url']['ext']) ? $this->_controller->params['url']['ext'] : null 43 | ); 44 | $data = !empty($this->_controller->data) ? $this->_controller->data : array(); 45 | if (!empty($data['_Token'])) { 46 | unset($data['_Token']); 47 | } 48 | $params['request_parameters'] = array_filter(array( 49 | 'url' => $this->_controller->here, 50 | 'data' => $data, 51 | 'pass' => !empty($this->_controller->params['pass']) ? $this->_controller->params['pass'] : array(), 52 | 'named' => !empty($this->_controller->params['named']) ? $this->_controller->params['named'] : array(), 53 | 'form' => !empty($this->_controller->params['form']) ? $this->_controller->params['form'] : array() 54 | )); 55 | return array_filter($params); 56 | } 57 | 58 | /** 59 | * Get/parse error data 60 | * 61 | * @param array $error 62 | * @return void 63 | */ 64 | protected function _getErrorData($error = array()) { 65 | $error = parent::_getErrorData($error); 66 | if($this->includeRequest) { 67 | $error += $this->_getControllerParams(); 68 | } 69 | return $error; 70 | } 71 | 72 | /** 73 | * Initialization method executed prior to the controller's beforeFilter 74 | * method but after the model instantiation. 75 | * 76 | * @param Controller $controller 77 | * @param array $config 78 | * @return void 79 | */ 80 | public function initialize(&$controller, $config = array()) { 81 | $this->_controller = $controller; 82 | $this->_initialize($config); 83 | } 84 | 85 | } 86 | 87 | ?> -------------------------------------------------------------------------------- /libs/db_referee_listener.php: -------------------------------------------------------------------------------- 1 | 11 | * @author Joshua McNeese 12 | * @see http://blog.joebeeson.com/monitoring-your-applications-health/ 13 | * @uses RefereeListener 14 | */ 15 | class DbRefereeListener extends RefereeListener { 16 | 17 | /** 18 | * Holds our model instance 19 | * 20 | * @var Model 21 | */ 22 | private $_model; 23 | 24 | /** 25 | * This is the model we will attempt to use when saving the error 26 | * record to the database. 27 | * 28 | * @var string 29 | */ 30 | protected $model = 'Referee.RefereeLog'; 31 | 32 | /** 33 | * The key represents the key value we get from the error and the 34 | * value represents the columns we will attempt to look for when 35 | * saving the error to the database. 36 | * 37 | * @var array 38 | */ 39 | protected $mapping = array( 40 | 'level' => array( 41 | 'level', 42 | 'severity', 43 | 'type' 44 | ), 45 | 'file' => array( 46 | 'file', 47 | 'location', 48 | ), 49 | 'message' => array( 50 | 'message', 51 | 'error', 52 | 'string' 53 | ), 54 | 'line' => array( 55 | 'line', 56 | ), 57 | 'url' => array( 58 | 'url', 59 | 'address', 60 | 'location' 61 | ) 62 | ); 63 | 64 | /** 65 | * Constructor 66 | * 67 | * @param array $config 68 | */ 69 | public function __construct($config = array()) { 70 | parent::__construct($config); 71 | $this->_model = ClassRegistry::init($this->model); 72 | } 73 | 74 | /** 75 | * Triggered when we're passed an error from the `WhistleComponent` 76 | * 77 | * @param array $error 78 | * @return void 79 | */ 80 | public function error($error) { 81 | $error['level'] = $this->_translateError($error['level']); 82 | $data = array(); 83 | if ($this->_model->name == 'RefereeLog') { 84 | if(!empty($error['args'])) { 85 | $error['args'] = serialize($error['args']); 86 | } 87 | if(!empty($error['trace'])) { 88 | $error['trace'] = serialize($error['trace']); 89 | } 90 | if(!empty($error['request_parameters'])) { 91 | $error['request_parameters'] = serialize($error['request_parameters']); 92 | } 93 | $data = $error; 94 | } else { 95 | $schema = array_keys($this->_model->schema()); 96 | $mapping = $this->_config['mapping']; 97 | foreach ($error as $key=>$value) { 98 | if (!empty($mapping[$key])) { 99 | if (is_array($mapping[$key])) { 100 | $column = array_pop( 101 | array_intersect( 102 | $mapping[$key], 103 | $schema 104 | ) 105 | ); 106 | } else { 107 | $column = (in_array($mapping[$key], $schema) 108 | ? $mapping[$key] 109 | : null 110 | ); 111 | } 112 | if (!empty($column)) { 113 | $data[$column] = $value; 114 | } 115 | } 116 | } 117 | } 118 | $this->_model->save($data); 119 | } 120 | 121 | } 122 | 123 | ?> -------------------------------------------------------------------------------- /libs/email_referee_listener.php: -------------------------------------------------------------------------------- 1 | 12 | * @uses RefereeListener 13 | */ 14 | class EmailRefereeListener extends RefereeListener { 15 | 16 | /** 17 | * @var RefereeMailer 18 | */ 19 | protected $_mailer; 20 | 21 | /** 22 | * Level to log errors at 23 | * 24 | * @var integer 25 | */ 26 | public $errorLevels = E_FATAL; 27 | 28 | /** 29 | * @var array 30 | */ 31 | public $mailerConfig = array( 32 | 'to' => null, 33 | 'cc' => array(), 34 | 'bcc' => array(), 35 | 'replyTo' => null, 36 | 'return' => null, 37 | 'from' => 'referee@localhost', 38 | 'subject' => 'Referee Notification', 39 | 'template' => 'default', 40 | 'layout' => 'default', 41 | 'lineLength' => 180, 42 | 'sendAs' => 'text', 43 | 'attachments' => array(), 44 | 'delivery' => 'mail', 45 | 'smtpOptions' => array( 46 | 'port' => 25, 47 | 'host' => '127.0.0.1', 48 | 'timeout' => 30, 49 | 'username' => null, 50 | 'password' => null, 51 | 'client' => null 52 | ) 53 | ); 54 | 55 | /** 56 | * Constructor 57 | * 58 | * Overridden to instantiate custom mailer component 59 | * 60 | * @param array $config 61 | * @return void 62 | */ 63 | public function __construct($config = array()) { 64 | $config['mailerConfig'] = array_merge($this->mailerConfig, $config['mailerConfig']); 65 | parent::__construct($config); 66 | $this->_mailer = new RefereeMailer($this->mailerConfig); 67 | } 68 | 69 | 70 | /** 71 | * @param array $error 72 | * @return void 73 | */ 74 | public function error($error) { 75 | $error['level'] = $this->_translateError($error['level']); 76 | $this->_mailer->subject .= ': '.$error['level']; 77 | $this->_mailer->send(print_r($error, true)); 78 | } 79 | 80 | } 81 | 82 | ?> -------------------------------------------------------------------------------- /libs/referee_listener.php: -------------------------------------------------------------------------------- 1 | 9 | * @uses Object 10 | */ 11 | abstract class RefereeListener extends Object { 12 | 13 | /** 14 | * Mapping of our error levels to their names. 15 | * 16 | * @var array 17 | */ 18 | protected $_errorLevels = array( 19 | E_ERROR => 'E_ERROR', 20 | E_WARNING => 'E_WARNING', 21 | E_PARSE => 'E_PARSE', 22 | E_NOTICE => 'E_NOTICE', 23 | E_CORE_ERROR => 'E_CORE_ERROR', 24 | E_CORE_WARNING => 'E_CORE_WARNING', 25 | E_COMPILE_ERROR => 'E_COMPILE_ERROR', 26 | E_COMPILE_WARNING => 'E_COMPILE_WARNING', 27 | E_USER_ERROR => 'E_USER_ERROR', 28 | E_USER_WARNING => 'E_USER_WARNING', 29 | E_USER_NOTICE => 'E_USER_NOTICE', 30 | E_STRICT => 'E_STRICT', 31 | E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', 32 | E_DEPRECATED => 'E_DEPRECATED', 33 | ); 34 | 35 | /** 36 | * Level to log errors at 37 | * 38 | * @var integer 39 | */ 40 | public $errorLevels = E_ALL; 41 | 42 | /** 43 | * Method to call on error 44 | * 45 | * @var string 46 | */ 47 | public $method = 'error'; 48 | 49 | /** 50 | * Constructor 51 | * 52 | * @param array $config 53 | */ 54 | public function __construct($config = array()) { 55 | $this->_set($config); 56 | } 57 | 58 | /** 59 | * Translates the `$level` integer into its human readable form. 60 | * 61 | * @param integer $level 62 | * @return string 63 | */ 64 | protected function _translateError($level) { 65 | return !empty($this->_errorLevels[$level]) ? $this->_errorLevels[$level] : 'E_UNKNOWN'; 66 | } 67 | 68 | /** 69 | * Triggered when we're passed an error from the `WhistleComponent` 70 | * 71 | * @param array $error 72 | * @return void 73 | */ 74 | abstract public function error($error); 75 | 76 | } 77 | 78 | ?> -------------------------------------------------------------------------------- /libs/referee_mailer.php: -------------------------------------------------------------------------------- 1 | 11 | * @uses Controller 12 | */ 13 | class RefereeMailerController extends Controller {} 14 | 15 | /** 16 | * RefereeMailer 17 | * 18 | * Overridden to support plugin templates/layouts for mail messages. 19 | * 20 | * @author Joshua McNeese 21 | * @uses EmailComponent 22 | */ 23 | class RefereeMailer extends EmailComponent { 24 | 25 | /** 26 | * @var View 27 | */ 28 | private $_view; 29 | 30 | /** 31 | * Constructor 32 | * 33 | * @param array $config 34 | * @return void 35 | */ 36 | public function __construct($config = array()) { 37 | if (Configure::read('App.encoding') !== null) { 38 | $this->charset = Configure::read('App.encoding'); 39 | } 40 | $this->_set($config); 41 | $this->_view = new View(new RefereeMailerController(), false); 42 | $this->_view->layout = $this->layout; 43 | $this->_view->plugin = 'Referee'; 44 | } 45 | 46 | /** 47 | * Render the contents using the current layout and template. 48 | * Overridden to support plugin templates 49 | * 50 | * @param string $content Content to render 51 | * @return array Email ready to be sent 52 | */ 53 | public function _render($content) { 54 | $msg = array(); 55 | $content = implode("\n", $content); 56 | if ($this->sendAs === 'both') { 57 | $htmlContent = $content; 58 | if (!empty($this->attachments)) { 59 | $msg[] = '--' . $this->__boundary; 60 | $msg[] = 'Content-Type: multipart/alternative; boundary="alt-' . $this->__boundary . '"'; 61 | $msg[] = ''; 62 | } 63 | $msg[] = '--alt-' . $this->__boundary; 64 | $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; 65 | $msg[] = 'Content-Transfer-Encoding: 7bit'; 66 | $msg[] = ''; 67 | $content = $this->_view->element('email' . DS . 'text' . DS . $this->template, array( 68 | 'content' => $content, 69 | 'plugin' => 'Referee' 70 | ), true); 71 | $this->_view->layoutPath = 'email' . DS . 'text'; 72 | $content = explode("\n", $this->textMessage = str_replace(array("\r\n", "\r"), "\n", $this->_view->renderLayout($content))); 73 | $msg = array_merge($msg, $content); 74 | $msg[] = ''; 75 | $msg[] = '--alt-' . $this->__boundary; 76 | $msg[] = 'Content-Type: text/html; charset=' . $this->charset; 77 | $msg[] = 'Content-Transfer-Encoding: 7bit'; 78 | $msg[] = ''; 79 | $htmlContent = $this->_view->element('email' . DS . 'html' . DS . $this->template, array( 80 | 'content' => $htmlContent, 81 | 'plugin' => 'Referee' 82 | ), true); 83 | $this->_view->layoutPath = 'email' . DS . 'html'; 84 | $htmlContent = explode("\n", $this->htmlMessage = str_replace(array("\r\n", "\r"), "\n", $this->_view->renderLayout($htmlContent))); 85 | $msg = array_merge($msg, $htmlContent); 86 | $msg[] = ''; 87 | $msg[] = '--alt-' . $this->__boundary . '--'; 88 | $msg[] = ''; 89 | return $msg; 90 | } 91 | if (!empty($this->attachments)) { 92 | if ($this->sendAs === 'html') { 93 | $msg[] = ''; 94 | $msg[] = '--' . $this->__boundary; 95 | $msg[] = 'Content-Type: text/html; charset=' . $this->charset; 96 | $msg[] = 'Content-Transfer-Encoding: 7bit'; 97 | $msg[] = ''; 98 | } else { 99 | $msg[] = '--' . $this->__boundary; 100 | $msg[] = 'Content-Type: text/plain; charset=' . $this->charset; 101 | $msg[] = 'Content-Transfer-Encoding: 7bit'; 102 | $msg[] = ''; 103 | } 104 | } 105 | $content = $this->_view->element('email' . DS . $this->sendAs . DS . $this->template, array( 106 | 'content' => $content, 107 | 'plugin' => 'Referee' 108 | ), true); 109 | $this->_view->layoutPath = 'email' . DS . $this->sendAs; 110 | $content = explode("\n", $rendered = str_replace(array("\r\n", "\r"), "\n", $this->_view->renderLayout($content))); 111 | if ($this->sendAs === 'html') { 112 | $this->htmlMessage = $rendered; 113 | } else { 114 | $this->textMessage = $rendered; 115 | } 116 | $msg = array_merge($msg, $content); 117 | return $msg; 118 | } 119 | 120 | } 121 | 122 | ?> -------------------------------------------------------------------------------- /libs/referee_whistle.php: -------------------------------------------------------------------------------- 1 | 18 | * @author Joshua McNeese 19 | * @see http://blog.joebeeson.com/monitoring-your-applications-health/ 20 | * @uses Object 21 | * @todo Add method to manually log errors (->error()) 22 | */ 23 | class RefereeWhistle extends Object { 24 | 25 | /** 26 | * Holds the actual objects that represent our $listeners -- this way we 27 | * reuse the objects instead of instantiating new ones for everything. 28 | * 29 | * @var array 30 | */ 31 | protected $_listeners = array(); 32 | 33 | /** 34 | * Disables the reporting of errors. 35 | * 36 | * @var boolean 37 | */ 38 | protected $_enabled = true; 39 | 40 | /** 41 | * Include stacktrace, if possible 42 | * 43 | * @var boolean 44 | */ 45 | public $includeTrace = true; 46 | 47 | /** 48 | * How many levels to return from backtrace 49 | * 50 | * @var integer 51 | */ 52 | public $traceDepth = 3; 53 | 54 | /** 55 | * Error level to trigger our custom handler 56 | * 57 | * @var integer 58 | */ 59 | public $errorLevels = E_DEFAULT; 60 | 61 | /** 62 | * What error levels we consider fatal (this is bitwise added levels) 63 | * E.g. E_ERROR | E_PARSE 64 | * 65 | * @var integer 66 | */ 67 | public $fatal = E_FATAL; 68 | 69 | /** 70 | * The listeners to relay errors to 71 | * 72 | * @var array 73 | */ 74 | public $listeners = array( 75 | 'Syslog' 76 | ); 77 | 78 | /** 79 | * Constructor 80 | * 81 | * @param array $config 82 | * @return void 83 | */ 84 | public function __construct($config = array()) { 85 | if(!empty($config)) { 86 | $this->_initialize($config); 87 | } 88 | } 89 | 90 | /** 91 | * Initialize 92 | * 93 | * @param array $config 94 | * @return void 95 | */ 96 | protected function _initialize($config = array()) { 97 | // Set config 98 | $this->_set($config); 99 | // We don't want to execute when testing 100 | if ( 101 | $this->_enabled === false || 102 | Configure::read('Referee') === false 103 | ) { 104 | $this->enable(false); 105 | } else { 106 | // Attach any passed listeners... 107 | $this->_loadListeners(); 108 | // Register our handler functions 109 | $this->registerHandlers(); 110 | } 111 | } 112 | 113 | /** 114 | * Triggered when an error occurs during execution. We handle the process 115 | * of looping through our listener configurations and seeing if there is 116 | * anyone that matches our current error level and if so we will trigger 117 | * the listener's error method. 118 | * 119 | * @param integer $level 120 | * @param string $string 121 | * @param string $file 122 | * @param integer $line 123 | * @return void 124 | */ 125 | public function __error($level, $message, $file, $line) { 126 | $data = $this->_getErrorData(compact('level', 'message', 'file', 'line')); 127 | foreach ($this->_listeners as $listener) { 128 | if ($level & $listener->errorLevels) { 129 | $listener->{$listener->method}($data); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Executed via register_shutdown_function() in an attempt to catch any 136 | * fatal errors before we stop execution. If we find one we kick it back 137 | * out to our __error method to handle accordingly. 138 | * 139 | * @return void 140 | */ 141 | public function __shutdown() { 142 | $error = error_get_last(); 143 | if (!empty($error['type']) && ((int)$error['type'] & $this->fatal)) { 144 | $this->__error($error['type'], $error['message'], $error['file'], $error['line']); 145 | } 146 | } 147 | 148 | /** 149 | * Get/parse error data 150 | * 151 | * @param array $error 152 | * @return void 153 | * @todo Improve stacktrace mechanism to not die on large traces 154 | */ 155 | protected function _getErrorData($error = array()) { 156 | $trace = debug_backtrace(false); 157 | if(!empty($trace[3])) { 158 | $first = $trace[3]; 159 | if(empty($first['args'])) { 160 | unset($first['args']); 161 | } 162 | $error += $first; 163 | if($this->includeTrace) { 164 | $error['trace'] = array_slice($trace, 3, $this->traceDepth); 165 | } 166 | } 167 | return $error; 168 | } 169 | 170 | /** 171 | * Load up any configured listeners 172 | * 173 | * @return void 174 | */ 175 | protected function _loadListeners() { 176 | if (!empty($this->listeners)) { 177 | foreach ($this->listeners as $listener=>$configs) { 178 | // Just in case they pass us a listener with no configuration 179 | if (is_numeric($listener)) { 180 | $listener = $configs; 181 | $configs = array(); 182 | } 183 | if (!Set::numeric(array_keys($configs))) { 184 | $configs = array($configs); 185 | } 186 | $class = $listener; 187 | foreach ($configs as $config) { 188 | if (!class_exists($class)) { 189 | if (!empty($config['class']) && !empty($config['file'])) { 190 | require($config['file']); 191 | $class = $config['class']; 192 | } else { 193 | $class = $listener.'RefereeListener'; 194 | App::import('Lib', 'Referee.'.$class); 195 | } 196 | } 197 | } 198 | if (class_exists($class)) { 199 | foreach ($configs as $config) { 200 | $this->_listeners[] = new $class($config); 201 | } 202 | } 203 | } 204 | } 205 | } 206 | 207 | /** 208 | * Set object to be enabled or not 209 | * 210 | * @param boolean $enabled 211 | * @return void 212 | */ 213 | public function enable($enabled = true) { 214 | $this->_enabled = $enabled; 215 | } 216 | 217 | /** 218 | * Set our error handlers to be the default 219 | * 220 | * @return void 221 | */ 222 | public function registerHandlers() { 223 | // Attach our error handler for requested errors 224 | set_error_handler(array($this, '__error'), $this->errorLevels); 225 | // Register a shutdown function to catch fatal errors 226 | register_shutdown_function(array($this, '__shutdown')); 227 | } 228 | 229 | } 230 | 231 | ?> -------------------------------------------------------------------------------- /libs/syslog_referee_listener.php: -------------------------------------------------------------------------------- 1 | 11 | * @author Joshua McNeese 12 | * @see http://blog.joebeeson.com/monitoring-your-applications-health/ 13 | * @uses RefereeListener 14 | */ 15 | class SyslogRefereeListener extends RefereeListener { 16 | 17 | /** 18 | * @var string 19 | */ 20 | public $ident = 'CakePHP Application'; 21 | 22 | /** 23 | * @var string 24 | */ 25 | public $format = 'Caught an %s error, "%s" in %s at line %s'; 26 | 27 | /** 28 | * Triggered when we're passed an error from the `WhistleComponent` 29 | * 30 | * @param array $error 31 | * @return void 32 | */ 33 | public function error($error) { 34 | syslog( 35 | LOG_INFO, 36 | $this->ident . ': ' . sprintf( 37 | $this->format, 38 | $this->_translateError($error['level']), 39 | $error['message'], 40 | $error['file'], 41 | $error['line'] 42 | ) 43 | ); 44 | } 45 | 46 | } 47 | 48 | ?> -------------------------------------------------------------------------------- /models/referee_log.php: -------------------------------------------------------------------------------- 1 | array( 19 | 'notempty' => array( 20 | 'rule' => array('notempty') 21 | ) 22 | ), 23 | 'file' => array( 24 | 'notempty' => array( 25 | 'rule' => array('notempty') 26 | ) 27 | ), 28 | 'line' => array( 29 | 'numeric' => array( 30 | 'rule' => array('numeric') 31 | ) 32 | ) 33 | ); 34 | 35 | } 36 | 37 | ?> -------------------------------------------------------------------------------- /referee_app_model.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/cases/components/whistle.test.php: -------------------------------------------------------------------------------- 1 | 15 | * @uses WhistleComponent 16 | */ 17 | class WhistleProxyComponent extends WhistleComponent { 18 | 19 | /** 20 | * @return array 21 | */ 22 | public function getListeners() { 23 | return $this->_listeners; 24 | } 25 | 26 | /** 27 | * @param string $ident 28 | * @return mixed 29 | */ 30 | public function getListener($ident = null) { 31 | foreach($this->_listeners as $listener) { 32 | if($listener->ident == $ident) { 33 | return $listener; 34 | } 35 | } 36 | return false; 37 | } 38 | } 39 | 40 | /** 41 | * @package referee 42 | * @subpackage referee.tests.components 43 | * @uses Controller 44 | * @author Joshua McNeese 45 | */ 46 | class RefereeTestController extends Controller { 47 | 48 | /** 49 | * @var boolean 50 | */ 51 | public $autoRender = false; 52 | 53 | /** 54 | * @var array 55 | */ 56 | public $uses = array(); 57 | 58 | /** 59 | * @var array 60 | */ 61 | public $data = array( 62 | '_Token' => true 63 | ); 64 | 65 | /** 66 | * @return void 67 | */ 68 | public function __construct() { 69 | 70 | $this->components = array( 71 | 'WhistleProxy' => array( 72 | 'listeners' => array( 73 | 'CustomTestListener' => array( 74 | 'class' => 'CustomTestListener', 75 | 'file' => App::pluginPath('Referee') . 'tests' . DS . 'libs' . DS . 'custom_test_listener.php' 76 | ) 77 | ) 78 | ) 79 | ); 80 | 81 | parent::__construct(); 82 | 83 | } 84 | 85 | } 86 | 87 | /** 88 | * WhistleComponentTest 89 | * 90 | * Tests the Whistle component for the Referee plugin. 91 | * 92 | * @package referee 93 | * @subpackage referee.tests.components 94 | * @author Joe Beeson 95 | * @author Joshua McNeese 96 | */ 97 | class WhistleComponentTest extends CakeTestCase { 98 | 99 | /** 100 | * @return void 101 | */ 102 | public function startTest() { 103 | $this->WhistleTest = new RefereeTestController(); 104 | $this->WhistleTest->constructClasses(); 105 | $this->WhistleTest->startupProcess(); 106 | } 107 | 108 | /** 109 | * @return void 110 | */ 111 | public function endTest() { 112 | unset($this->WhistleTest); 113 | ClassRegistry::flush(); 114 | } 115 | 116 | /** 117 | * @return void 118 | */ 119 | public function testListenersLoaded() { 120 | $this->assertTrue($this->WhistleTest->WhistleProxy); 121 | $this->assertTrue($this->WhistleTest->WhistleProxy->getListener('Custom')); 122 | } 123 | 124 | /** 125 | * @return void 126 | */ 127 | public function testHandlers() { 128 | 129 | $this->WhistleTest->WhistleProxy->registerHandlers(); 130 | 131 | trigger_error('testing notice', E_USER_NOTICE); 132 | 133 | $listener = $this->WhistleTest->WhistleProxy->getListener('Custom'); 134 | $this->assertTrue($listener->error['message'] == 'testing notice'); 135 | $this->assertTrue($listener->error['level'] == E_USER_NOTICE); 136 | $this->assertTrue($listener->error['request_controller']); 137 | 138 | restore_error_handler(); 139 | 140 | } 141 | 142 | } 143 | 144 | ?> -------------------------------------------------------------------------------- /tests/cases/libs/referee_whistle.test.php: -------------------------------------------------------------------------------- 1 | 13 | * @uses RefereeWhistle 14 | */ 15 | class RefereeWhistleProxy extends RefereeWhistle { 16 | 17 | /** 18 | * @return array 19 | */ 20 | public function getListeners() { 21 | return $this->_listeners; 22 | } 23 | 24 | /** 25 | * @param string $ident 26 | * @return mixed 27 | */ 28 | public function getListener($ident = null) { 29 | foreach($this->_listeners as $listener) { 30 | if($listener->ident == $ident) { 31 | return $listener; 32 | } 33 | } 34 | return false; 35 | } 36 | } 37 | 38 | /** 39 | * RefereeWhistleTest 40 | * 41 | * Tests the Whistle component for the Referee plugin. 42 | * 43 | * @package referee 44 | * @subpackage referee.tests.components 45 | * @author Joe Beeson 46 | * @author Joshua McNeese 47 | */ 48 | class RefereeWhistleTest extends CakeTestCase { 49 | 50 | /** 51 | * @return void 52 | */ 53 | public function startTest($method) { 54 | 55 | Configure::write('Referee', $method != 'testDisabled'); 56 | 57 | $path = App::pluginPath('Referee') . 'tests' . DS . 'libs' . DS; 58 | 59 | $this->RefereeWhistleProxy = new RefereeWhistleProxy(array( 60 | 'listeners' => array( 61 | 'Syslog', 62 | 'TestRefereeListener' => array( 63 | array( 64 | 'class' => 'TestRefereeListener', 65 | 'file' => $path . 'test_referee_listener.php', 66 | 'ident' => 'Standard 1' 67 | ), 68 | array( 69 | 'class' => 'TestRefereeListener', 70 | 'file' => $path . 'test_referee_listener.php', 71 | 'ident' => 'Standard 2' 72 | ) 73 | ), 74 | 'CustomTestListener' => array( 75 | 'class' => 'CustomTestListener', 76 | 'file' => $path . 'custom_test_listener.php' 77 | ) 78 | ) 79 | )); 80 | } 81 | 82 | /** 83 | * @return void 84 | */ 85 | public function endTest() { 86 | unset($this->RefereeWhistleProxy); 87 | ClassRegistry::flush(); 88 | } 89 | 90 | /** 91 | * @return void 92 | */ 93 | public function testListenersLoaded() { 94 | $this->assertTrue($this->RefereeWhistleProxy->getListener('Standard 1')); 95 | $this->assertTrue($this->RefereeWhistleProxy->getListener('Standard 2')); 96 | $this->assertTrue($this->RefereeWhistleProxy->getListener('Custom')); 97 | } 98 | 99 | /** 100 | * @return void 101 | */ 102 | public function testDisabled() { 103 | 104 | $this->assertFalse($this->RefereeWhistleProxy->getListeners()); 105 | 106 | } 107 | 108 | /** 109 | * @return void 110 | */ 111 | public function testHandlers() { 112 | 113 | $this->RefereeWhistleProxy->registerHandlers(); 114 | 115 | trigger_error('testing notice', E_USER_NOTICE); 116 | 117 | $listener = $this->RefereeWhistleProxy->getListener('Standard 1'); 118 | $this->assertTrue($listener->error['message'] == 'testing notice'); 119 | $this->assertTrue($listener->error['level'] == E_USER_NOTICE); 120 | 121 | $listener = $this->RefereeWhistleProxy->getListener('Standard 2'); 122 | $this->assertTrue($listener->error['message'] == 'testing notice'); 123 | $this->assertTrue($listener->error['level'] == E_USER_NOTICE); 124 | 125 | $listener = $this->RefereeWhistleProxy->getListener('Custom'); 126 | $this->assertTrue($listener->error['message'] == 'testing notice'); 127 | $this->assertTrue($listener->error['level'] == E_USER_NOTICE); 128 | 129 | restore_error_handler(); 130 | 131 | } 132 | 133 | } 134 | 135 | ?> -------------------------------------------------------------------------------- /tests/cases/models/referee_log.test.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class RefereeLogTestCase extends CakeTestCase { 14 | 15 | /** 16 | * @var array 17 | */ 18 | public $fixtures = array( 19 | 'plugin.referee.referee_log' 20 | ); 21 | 22 | /** 23 | * @return void 24 | */ 25 | public function startTest() { 26 | $this->RefereeLog = ClassRegistry::init('Referee.RefereeLog'); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function testInstantiation() { 33 | $this->assertTrue(is_a($this->RefereeLog, 'Model')); 34 | } 35 | 36 | /** 37 | * @return void 38 | */ 39 | public function testValidation() { 40 | $data = array( 41 | 'level' => null, 42 | 'file' => null, 43 | 'line' => null 44 | ); 45 | $this->RefereeLog->create(); 46 | $result = $this->RefereeLog->save($data); 47 | $this->assertFalse($result, 'Should not be able to pass validation with errors.'); 48 | $this->assertEqual(count($data), count($this->RefereeLog->invalidFields())); 49 | } 50 | 51 | } 52 | 53 | ?> -------------------------------------------------------------------------------- /tests/fixtures/referee_log_fixture.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class RefereeLogFixture extends CakeTestFixture { 12 | 13 | /** 14 | * @var string 15 | */ 16 | public $name = 'RefereeLog'; 17 | 18 | /** 19 | * @var array 20 | */ 21 | public $fields = array( 22 | 'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), 23 | 'level' => array('type' => 'string', 'null' => false, 'length' => 32), 24 | 'file' => array('type' => 'string', 'null' => false), 25 | 'line' => array('type' => 'integer', 'null' => false, 'default' => '0', 'length' => 4), 26 | 'class' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 32), 27 | 'function' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 32), 28 | 'args' => array('type' => 'text', 'null' => true, 'default' => NULL), 29 | 'type' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 8), 30 | 'message' => array('type' => 'text', 'null' => false, 'default' => NULL), 31 | 'trace' => array('type' => 'text', 'null' => true, 'default' => NULL), 32 | 'request_method' => array('type' => 'string', 'null' => true, 'length' => 6), 33 | 'request_plugin' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 32), 34 | 'request_controller' => array('type' => 'string', 'null' => true, 'length' => 32), 35 | 'request_action' => array('type' => 'string', 'null' => true, 'length' => 32), 36 | 'request_ext' => array('type' => 'string', 'null' => true, 'default' => NULL, 'length' => 8), 37 | 'request_parameters' => array('type' => 'text', 'null' => true, 'default' => NULL), 38 | 'created' => array('type' => 'datetime', 'null' => false, 'default' => '0000-00-00 00:00:00'), 39 | 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) 40 | ); 41 | 42 | } 43 | 44 | ?> -------------------------------------------------------------------------------- /tests/libs/custom_test_listener.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CustomTestListener { 14 | 15 | /** 16 | * @var array 17 | */ 18 | public $error; 19 | 20 | /** 21 | * @var string 22 | */ 23 | public $ident = 'Custom'; 24 | 25 | /** 26 | * @var integer 27 | */ 28 | public $errorLevels = E_ALL; 29 | 30 | /** 31 | * @var string 32 | */ 33 | public $method = 'customError'; 34 | 35 | /** 36 | * A custom method for handling errors 37 | * 38 | * @param array $error 39 | * @return void 40 | */ 41 | public function customError($error) { 42 | $this->error = $error; 43 | } 44 | 45 | } 46 | 47 | ?> -------------------------------------------------------------------------------- /tests/libs/test_referee_listener.php: -------------------------------------------------------------------------------- 1 | 11 | * @uses RefereeListener 12 | */ 13 | class TestRefereeListener extends RefereeListener { 14 | 15 | /** 16 | * @var string 17 | */ 18 | public $ident; 19 | 20 | /** 21 | * @var array 22 | */ 23 | public $error; 24 | 25 | /** 26 | * @param array $error 27 | * @return void 28 | */ 29 | public function error($error) { 30 | $this->error = $error; 31 | } 32 | 33 | } 34 | 35 | ?> -------------------------------------------------------------------------------- /views/elements/email/html/default.ctp: -------------------------------------------------------------------------------- 1 | ' . $line . '

'; 5 | } 6 | ?> -------------------------------------------------------------------------------- /views/elements/email/text/default.ctp: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /views/layouts/email/html/default.ctp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <?php echo $title_for_layout;?> 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /views/layouts/email/text/default.ctp: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------