Home > PHP > Error reporting with CodeIgniter

Error reporting with CodeIgniter

February 23rd, 2009 Leave a comment Go to comments

While CodeIgniter does come with a reasonable error logging tool its simple nature of just dumping a single line report to a file on a distant server does not seem too pro-active for my tastes.

The following helper replaces the default CI error reporting with a new error reporting interface that also emails any errors that occur to a nominated developer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
<? if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Sensibly report of errors that occur in the system by sending that
* error to the developer
* Author: Matt Carter <m@ttcarter.com>
* Info: http://hash-bang.net/2009/02/error-reporting-with-cierror-reporting-with-ci/
*/
 
// Where to send the error report. Leave blank for nowhere
define('ERR_MAIL_TO', 'someone@somewhere.com');
 
 // Literal name of that person
define('ERR_MAIL_TO_NAME', 'Joe Random');
define('ERR_MAIL_FROM', 'root@freesweetsite.com');
define('ERR_MAIL_FROM_NAME', 'Error Daemon');
define('ERR_MAIL_METHOD', 'SMTP'); // Supported: SMTP
define('ERR_MAIL_HOST', 'localhost');
define('ERR_MAIL_SENDER', 'root@somewhere.com');
 
 // The subject of the mail. Supported tags: [TYPE], [FILE], [LINE], [STRING], [BASENAME]
define('ERR_MAIL_SUBJECT', 'Error detected: [TYPE] @ [BASENAME]:[LINE]');
 // Other things of interest to include in the email. CSV of supported values: POST, GET, SERVER, GLOBALS, SESSION
define('ERR_MAIL_FOOTERS', 'POST,GET,SESSION');
 
// Relative location of PHP mailer to this file
// (Not relative to the working directory because that doesn't specify correctly with fatal errors).
// By default this assumes that the 'phpMailer' folder is located in the same directory as this script.
define('ERR_PATH_PHPMAILER', 'phpMailer/class.phpmailer.php');
 
function err($errno, $errstr, $errfile, $errline, $errcontext) {
	$errtable = array(
		1 => 'Fatal',
		2 => 'Warning',
		4 => 'Parse Error',
		8 => 'Notice',
		16 => 'Core Error',
		32 => 'Core Warning',
		64 => 'Compile Error',
		128 => 'Compile Warning',
		256 => 'User Error',
		512 => 'User Warning',
		1024 => 'User Notice',
		2048 => 'Strict Notice',
		4096 => 'Recoverable Error',
		8192 => 'Deprecated',
		16384 => 'User Deprecated',
	);
	$message = "<style>";
	$message .= ".err-box {border: 1px solid #4A98AF}";
	$message .= ".err-table th {background: #4A98AF; text-align: right; white-space: nowrap}";
	$message .= ".err-table td, .err-table th {padding: 5px}";
	$message .= ".err-table-stack th {background: #4A98AF; text-align: left}";
	$message .= ".err-table-stack th, .err-table-stack td {font-size: 12px}";
	$message .= "</style>";
	$message .= "<div class=\"err-box\">";
	$message .= "<table class=\"err-table\">";
	$message .= "<tr><th width=\"100px\">Type:</th><td>{$errtable[$errno]}</td></tr>";
	$message .= "<tr><th>Error:</th><td>$errstr</td></tr>";
	$message .= "<tr><th>File:</th><td>$errfile</td></tr>";
	$message .= "<tr><th>Line:</th><td>$errline</td></tr>";
	$message .= "<tr><th>Context:</th><td>$errcontext</td></tr>";
	$message .= "</table>";
	$traces = debug_backtrace();
	array_shift($traces);
	if ( (count($traces) > 1) && ($traces[0]['function'] != 'err_shutdown') ) { // Ignore fatal shutdown traces
		$message .= "<table width=\"100%\" class=\"err-table-stack\"><tr><th width=\"50px\">Line</th><th>Function</th>";
		foreach ($traces as $offset => $trace) {
			// Calculate line number
			if ($offset == 0) { // First in trace
				$message .= "<tr><td style=\"text-align: center\">$errline</td><td>";
			} else // Nth in trace
				$message .= "<tr><td style=\"text-align: center\">" . (isset($trace['line']) ? $trace['line'] : '&nbsp;') . "</td><td>";
			// Calculate arg stack
			$trace['argstack'] = '';
			if (isset($trace['args']) && $trace['args']) {
				foreach ($trace['args'] as $arg)
					$trace['argstack'] .= _err_human($arg) . ' , ';
				if ($trace['argstack']) $trace['argstack'] = substr($trace['argstack'], 0, -3);
			}
			// Output context
			if (isset($trace['object'])) { // Object error
				$message .= "{$trace['class']}->{$trace['function']}({$trace['argstack']})";
			} else // Function error
				$message .= "{$trace['function']}({$trace['argstack']})";
			$message .= "</td></tr>";
		}
		$message .= "</table>";
	}
	$message .= "</div>";
	if (ERR_MAIL_TO) {
		if (!file_exists($p = dirname(__FILE__) . '/' . ERR_PATH_PHPMAILER)) {
			echo "Cannot find PHP mailer at the relative path '" . ERR_PATH_PHPMAILER ."'. Make sure it is located there to send mails";
		} else {
			require_once($p);
			$mailobj = new PhpMailer();
			if (ERR_MAIL_METHOD == 'SMTP') $mailobj->IsSMTP();
			$mailobj->IsHTML(TRUE);
			$mailobj->CharSet = 'utf-8';
			$mailobj->Host = ERR_MAIL_HOST;
			$mailobj->Sender = ERR_MAIL_SENDER;
			$mailobj->From = ERR_MAIL_FROM;
			$mailobj->FromName = ERR_MAIL_FROM_NAME;
			$mailobj->AddAddress(ERR_MAIL_TO, ERR_MAIL_TO_NAME);
			$mailobj->Subject = strtr(ERR_MAIL_SUBJECT, array(
				'[TYPE]' => $errtable[$errno],
				'[FILE]' => $errfile,
				'[BASENAME]' => basename($errfile),
				'[LINE]' => $errline,
				'[STRING]' => $errstr,
			));
			$extras = preg_split('/\s*,\s*/', ERR_MAIL_FOOTERS);
			if (in_array('GET', $extras)) $message .= _err_dump_array($_GET, 'Get');
			if (in_array('POST', $extras)) $message .= _err_dump_array($_POST, 'Post');
			if (in_array('SESSION', $extras)) $message .= _err_dump_array(isset($_SESSION) ? $_SESSION : array(), 'Session');
			if (in_array('SERVER', $extras)) $message .= _err_dump_array($_SERVER, 'Server');
			if (in_array('GLOBALS', $extras)) $message .= _err_dump_array($_GLOBALS, 'Globals');
			$mailobj->Body = $message;
			$status = $mailobj->Send();
		}
	}
	echo $message;
	return TRUE;
}
 
// Catch fatal errors
function err_shutdown() {
	$err = error_get_last();
	if (in_array($err['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR)))
		err($err['type'], $err['message'], $err['file'], $err['line'], 'Fatal error');
}
 
function _err_human($what) {
	if (is_object($what)) {
		return get_class($what);
	} elseif (is_array($what)) {
		return "Array[" . count($what) . "]";
	} else
		return $what;
}
 
function _err_dump_array($array, $title) {
	if ($array) {
		$out = '<table class="err-table-stack">';
		$out .= "<tr><th colspan=\"2\">Dump of $title array</th></tr><tr><th>Key</th><th>Value</th></tr>";
		foreach (array_keys($array) as $key)
			$out .= "<tr><th>$key</th><td>" . _err_human($array[$key]) . "</td></tr>";
		$out .= '</table>';
	} else {
		$out = "<div>$title is empty</div>";
	}
	return $out;
}
set_error_handler('err', E_ALL);
register_shutdown_function('err_shutdown');
?>

Installation of the component is similar to any CI helper: drop the file into the application/system/helpers directory and create the associated entry in the application/system/config/autoload.php file like so:

1
$autoload['helper'] = array('err');

Then open the above and change the constants at the top of the file to specify where the error messages are sent.

In order to send mail the above component needs the truly excellent PHPMailer package which can be stashed in the same helpers directory. If you do decide to move it somewhere else change the corresponding constant that specifies the location of the library. A small note of warning though – the path to the PHP mailer libraries must be relative to the current file NOT the current working directory. This is due to how PHP handles fatal errors which for some reason sets the working directory to ‘/’.

Should there be any interest I may extend the above with other useful features such as RSS, SMS (text messaging) or other non-SMTP methods of emailing.

Categories: PHP Tags: , ,
  1. November 25th, 2009 at 23:56 | #1

    Nice, this is very helpful. :)

    By the way, download button of the wp-codebox is not working.

  2. April 30th, 2010 at 00:00 | #2

    nice one erwin! this is very helpful!

  3. May 25th, 2010 at 18:41 | #3

    Nice class. I am not using CI, but my custom MVC framework, and I modified your class for my framework.

  4. September 29th, 2010 at 05:33 | #4

    Very cool work dude :)

  5. Valoo
    April 27th, 2012 at 00:44 | #5

    Very good job, this is going to be implemented in all my present and future projects. Thank you very mutch !
    I had to make a small modification to make it fit though.

    in recent versions of CI (at least), you need to use the session library to get session content ($_SESSION is never set).

    first load the lib at the beginning of the function:
    function err($errno, $errstr, $errfile, $errline, $errcontext) {
    $CI =& get_instance();
    $CI->load->library(array(‘session’));
    $errtable = array(…

    then replace :
    (114) : if (in_array(‘SESSION’, $extras)) $message .= _err_dump_array(isset($_SESSION) ? $_SESSION : array(), ‘Session’);
    by :
    if (in_array(‘SESSION’, $extras)) $message .= _err_dump_array($CI->session->all_userdata(), ‘Session’);

    have fun !

  1. September 29th, 2010 at 06:00 | #1
*