├── 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 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/views/layouts/email/text/default.ctp:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------