Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Many improvements:

- better way of merging similar errors & error context detection
- Debug_ErrorHook_Listener::triggerException() method to log & mail exceptions keeping their stacktraces
- incresed no-dups delay; count of errors happened between mails is included to mails
- tabs to spaces :-)
  • Loading branch information...
commit 2822de9dae20215ec9ce1b1a1f8911585804b6b5 1 parent 173f188
Dmitry authored
33 lib/Debug/ErrorHook/Catcher.php
View
@@ -14,13 +14,13 @@ class Debug_ErrorHook_Catcher
"E_USER_ERROR", "E_USER_WARNING", "E_USER_NOTICE", "E_STRICT",
"E_RECOVERABLE_ERROR", "E_DEPRECATED", "E_USER_DEPRECATED",
);
-
+
public function __construct()
{
$this->_prevHdl = set_error_handler(array($this, "_handleNotice"));
register_shutdown_function(array($this, "_handleFatal"));
}
-
+
public function remove()
{
restore_error_handler();
@@ -33,19 +33,34 @@ public function addNotifier(Debug_ErrorHook_INotifier $notifier)
{
$this->_notifiers[] = $notifier;
}
-
- public function _handleNotice($errno, $errstr, $errfile, $errline)
+
+ public function triggerException(Exception $e, $msg = null)
+ {
+ return $this->_handleNonFatal(
+ E_USER_ERROR,
+ "exception '" . get_class($e) . "' with message: '" . ($msg !== null? $msg : $e->getMessage()) . "'",
+ $e->getFile(), $e->getLine(),
+ $e->getTrace()
+ );
+ }
+
+ private function _handleNonFatal($errno, $errstr, $errfile, $errline, $trace)
{
if (!($errno & error_reporting())) {
return $this->_callPrevHdl($errno, $errstr, $errfile, $errline);
}
- $trace = debug_backtrace();
- array_shift($trace);
if ($this->_notify($errno, $errstr, $errfile, $errline, $trace) === false) {
return $this->_callPrevHdl($errno, $errstr, $errfile, $errline, $trace);
}
}
-
+
+ public function _handleNotice($errno, $errstr, $errfile, $errline)
+ {
+ $trace = debug_backtrace();
+ array_shift($trace);
+ return $this->_handleNonFatal($errno, $errstr, $errfile, $errline, $trace);
+ }
+
public function _handleFatal()
{
$error = error_get_last();
@@ -54,7 +69,7 @@ public function _handleFatal()
}
$this->_notify($error['type'], $error['message'], $error['file'], $error['line'], null);
}
-
+
/**
* Processes a notification.
*
@@ -87,7 +102,7 @@ private function _notify($errno, $errstr, $errfile, $errline, $trace)
}
return false;
}
-
+
private function _callPrevHdl()
{
if ($this->_prevHdl) {
24 lib/Debug/ErrorHook/INotifier.php
View
@@ -5,15 +5,17 @@
*/
interface Debug_ErrorHook_INotifier
{
- /**
- * Called when an error occurred.
- *
- * @param string $errno
- * @param string $errstr
- * @param string $errfile
- * @param string $errline
- * @param array $trace
- * @return void
- */
- public function notify($errno, $errstr, $errfile, $errline, $trace);
+ /**
+ * Called when an error occurred.
+ *
+ * @param string $errno
+ * @param string $errstr
+ * @param string $errfile
+ * @param string $errline
+ * @param array $trace
+ * @param string $hash
+ * @param string $prependText
+ * @return void
+ */
+ public function notify($errno, $errstr, $errfile, $errline, $trace, $hash = null, $prependText = null);
}
32 lib/Debug/ErrorHook/Listener.php
View
@@ -13,14 +13,14 @@
class Debug_ErrorHook_Listener
{
- private $_catcher = null;
-
- /**
- * Creates a new listener object.
- * When this object is destroyed, all hooks are removed.
- *
- * @return Debug_ErrorHook_Listener
- */
+ private $_catcher = null;
+
+ /**
+ * Creates a new listener object.
+ * When this object is destroyed, all hooks are removed.
+ *
+ * @return Debug_ErrorHook_Listener
+ */
public function __construct()
{
$this->_catcher = new Debug_ErrorHook_Catcher();
@@ -35,7 +35,7 @@ public function __destruct()
{
$this->_catcher->remove();
}
-
+
/**
* Adds a new notifier to the list. Notifiers are called in case
* of notices and even fatal errors.
@@ -45,6 +45,18 @@ public function __destruct()
*/
public function addNotifier(Debug_ErrorHook_INotifier $notifier)
{
- $this->_catcher->addNotifier($notifier);
+ $this->_catcher->addNotifier($notifier);
+ }
+
+ /**
+ * Works like trigger_error, but allows to pass stacktrace
+ * from the specified exception.
+ *
+ * @param Exception $e
+ * @param string $msg
+ */
+ public function triggerException(Exception $e, $msg = null)
+ {
+ $this->_catcher->triggerException($e, $msg);
}
}
61 lib/Debug/ErrorHook/MailNotifier.php
View
@@ -11,50 +11,55 @@
class Debug_ErrorHook_MailNotifier extends Debug_ErrorHook_TextNotifier
{
- private $_to;
- private $_charset;
- private $_whatToSend;
- private $_subjPrefix;
-
- public function __construct($to, $whatToSend, $subjPrefix = "[ERROR] ", $charset = "UTF-8")
- {
+ private $_to;
+ private $_from;
+ private $_charset;
+ private $_whatToSend;
+ private $_subjPrefix;
+
+ public function __construct($to, $whatToSend, $subjPrefix = "[ERROR] ", $charset = "UTF-8", $from = null)
+ {
parent::__construct($whatToSend);
- $this->_to = $to;
- $this->_subjPrefix = $subjPrefix;
- $this->_charset = $charset;
- }
-
+ $this->_to = $to;
+ $this->_from = $from? $from : $to;
+ $this->_subjPrefix = $subjPrefix;
+ $this->_charset = $charset;
+ }
+
protected function _notifyText($subject, $body)
{
- $this->_mail(
- $this->_to,
- $this->_encodeMailHeader($this->_subjPrefix . $subject),
- $body,
- join("\r\n", array(
- "From: {$this->_to}",
- "Content-Type: text/plain; charset={$this->_charset}"
- ))
- );
+ $msgId = md5(__CLASS__ . $this->_to . $this->_from . $this->_subjPrefix . $subject) . "@errorhook";
+ $this->_mail(
+ $this->_to,
+ $this->_encodeMailHeader($this->_subjPrefix . $subject),
+ $body,
+ join("\r\n", array(
+ "From: {$this->_from}",
+ "Content-Type: text/plain; charset={$this->_charset}",
+ "Message-Id: <$msgId>",
+ "In-Reply-To: <$msgId>",
+ ))
+ );
}
-
+
protected function _mail()
{
- $args = func_get_args();
- @call_user_func_array("mail", $args);
+ $args = func_get_args();
+ @call_user_func_array("mail", $args);
}
-
+
private function _encodeMailHeader($header)
{
return preg_replace_callback(
'/((?:^|>)\s*)([^<>]*?[^\w\s.][^<>]*?)(\s*(?:<|$))/s',
- array(__CLASS__, '_encodeMailHeaderCallback'),
+ array($this, '_encodeMailHeaderCallback'),
$header
);
}
- private function _encodeMailHeaderCallback($p)
+ public function _encodeMailHeaderCallback($p)
{
- $encoding = $this->_charset;
+ $encoding = $this->_charset;
return $p[1] . "=?$encoding?B?" . base64_encode($p[2]) . "?=" . $p[3];
}
}
172 lib/Debug/ErrorHook/RemoveDupsWrapper.php
View
@@ -12,78 +12,140 @@
class Debug_ErrorHook_RemoveDupsWrapper implements Debug_ErrorHook_INotifier
{
- const DEFAULT_PERIOD = 300;
- const ERROR_FILE_SUFFIX = ".error";
- const GC_PROBABILITY = 0.01;
-
- private $_notifier;
- private $_tmpPath;
- private $_period;
- private $_gcExecuted = false;
-
- public function __construct(Debug_ErrorHook_INotifier $notifier, $tmpPath = null, $period = null)
- {
- $this->_tmpPath = $tmpPath? $tmpPath : $this->_getDefaultTmpPath();
- $this->_period = $period? $period : self::DEFAULT_PERIOD;
- $this->_notifier = $notifier;
- if (!@is_dir($this->_tmpPath)) {
- if (!@mkdir($this->_tmpPath, 0777, true)) {
- $error = error_get_last();
- throw new Exception("Cannot create '{$this->_tmpPath}': {$error['message']}");
- }
- }
- }
-
- public function notify($errno, $errstr, $errfile, $errline, $trace)
+ const DEFAULT_NO_REPEAT_PERIOD = 7200; // in seconds
+ const ERROR_FILE_SUFFIX = ".error";
+ const GC_PROBABILITY = 0.001;
+
+ private $_notifier;
+ private $_tmpPath;
+ private $_period;
+ private $_gcExecuted = false;
+
+ public function __construct(Debug_ErrorHook_INotifier $notifier, $tmpPath = null, $period = null)
+ {
+ $this->_tmpPath = $tmpPath? $tmpPath : $this->_getDefaultTmpPath();
+ $this->_period = $period? $period : self::DEFAULT_NO_REPEAT_PERIOD;
+ $this->_notifier = $notifier;
+ if (!file_exists($this->_tmpPath) || !is_dir($this->_tmpPath)) {
+ if (!@mkdir($this->_tmpPath, 0777, true)) {
+ $error = error_get_last();
+ throw new Exception("Cannot create '{$this->_tmpPath}': {$error['message']}");
+ }
+ @chmod($this->_tmpPath, 0777);
+ }
+ }
+
+ public function notify($errno, $errstr, $errfile, $errline, $trace, $hash = null, $prependText = null)
{
- $hash = md5(join(":", array($errno, $errfile, $errline)));
- if ($this->_isExpired($hash)) {
- $this->_notifier->notify($errno, $errstr, $errfile, $errline, $trace);
- }
- // Touch always, even if we did not send anything. Else same errors will
- // be mailed again and again after $period (e.g. once per 5 minutes).
- $this->_touch($hash, $errfile, $errline);
+ $text = $errno . " in " . $errfile . " on line " . $errline . "\n";
+ if (is_array($trace)) {
+ $text .= "Stack trace:\n";
+ foreach ($trace as $i => $item) {
+ $text .= "#" . $i . " " . (@$item['file']? $item['file'] . "(" . $item['line'] . ")" : '[internal function]') . ": " . @$item['class'] . @$item['type'] . $item['function'] . "\n";
+ }
+ }
+ $hash = md5($text);
+ // Error message is NOT included in $hash, because it varies heavily!!
+ // But for debug reasons it's quite handy to have the text included
+ // in the lock file contents.
+ $text = "$errstr\n" . $text;
+ $lastNotifyTime = $this->_getLastTouchTime($hash);
+ if (time() - $lastNotifyTime > $this->_period) {
+ $cnt = $this->_getCnt($hash);
+ if ($cnt > 1 && $lastNotifyTime) {
+ $prependText =
+ sprintf("THIS ERROR HAPPENED %d TIMES WITHIN LAST %d MINUTES!\n", $cnt, (time() - $lastNotifyTime) / 60)
+ . ($prependText? "\n" . $prependText : "");
+ }
+ $this->_notifier->notify($errno, $errstr, $errfile, $errline, $trace, $hash, $prependText);
+ // Touch & reset counter when we've called a notification (e.g. sent a mail).
+ $this->_touch($hash, $text);
+ $this->_incCnt($hash, true);
+ $this->_gc();
+ } else {
+ // Just increment error counter.
+ $this->_incCnt($hash);
+ }
}
-
+
protected function _getDefaultTmpPath()
{
- return sys_get_temp_dir() . "/" . get_class($this);
+ return sys_get_temp_dir() . "/" . get_class($this);
}
-
+
protected function _getGcProbability()
{
- return self::GC_PROBABILITY;
+ // This method is "protected" for unit-test overrides.
+ return self::GC_PROBABILITY;
}
-
+
private function _getLockFname($hash)
{
- return $this->_tmpPath . '/' . $hash . self::ERROR_FILE_SUFFIX;
+ return $this->_tmpPath . '/' . $hash . self::ERROR_FILE_SUFFIX;
}
-
- private function _isExpired($hash)
+
+ private function _getCntFname($hash)
{
- $file = $this->_getLockFname($hash);
- return !file_exists($file) || filemtime($file) < time() - $this->_period;
+ return $this->_tmpPath . '/' . $hash . ".cnt" . self::ERROR_FILE_SUFFIX;
}
-
- private function _touch($hash, $errfile, $errline)
+
+ private function _getLastTouchTime($hash)
{
$file = $this->_getLockFname($hash);
- file_put_contents($file, "$errfile:$errline");
- @chmod($file, 0666);
- $this->_gc();
+ return file_exists($file)? filemtime($file) : 0;
}
-
+
+ private function _touch($hash, $text)
+ {
+ $file = $this->_getLockFname($hash);
+ file_put_contents($file, $text);
+ @chmod($file, 0666);
+ clearstatcache();
+ }
+
+ private function _getCnt($hash)
+ {
+ $file = $this->_getCntFname($hash);
+ $f = @fopen($file, "r");
+ if (!$f) {
+ return 1;
+ }
+ flock($f, LOCK_SH);
+ $cnt = trim(fgets($f));
+ flock($f, LOCK_UN);
+ fclose($f);
+ return $cnt;
+ }
+
+ private function _incCnt($hash, $resetCounter = false)
+ {
+ $file = $this->_getCntFname($hash);
+ fclose(fopen($file, "a+")); // create empty
+ $f = fopen($file, "r+");
+ flock($f, LOCK_EX);
+ $cnt = trim(fgets($f));
+ if ($resetCounter) {
+ $cnt = 0;
+ }
+ $cnt++;
+ fseek($f, 0, SEEK_SET);
+ ftruncate($f, 0);
+ fwrite($f, $cnt . "\n");
+ flock($f, LOCK_UN);
+ fclose($f);
+ @chmod($file, 0666);
+ }
+
private function _gc()
{
- if ($this->_gcExecuted || mt_rand(0, 10000) >= $this->_getGcProbability() * 10000) {
- return;
- }
- foreach (glob("{$this->_tmpPath}/*" . self::ERROR_FILE_SUFFIX) as $file) {
- if (filemtime($file) <= time() - $this->_period * 2) {
- @unlink($file);
- }
- }
- $this->_gcExecuted = true;
+ if ($this->_gcExecuted || mt_rand(0, 10000) >= $this->_getGcProbability() * 10000) {
+ return;
+ }
+ foreach (glob("{$this->_tmpPath}/*" . self::ERROR_FILE_SUFFIX) as $file) {
+ if (filemtime($file) <= time() - $this->_period * 2) {
+ @unlink($file);
+ }
+ }
+ $this->_gcExecuted = true;
}
}
68 lib/Debug/ErrorHook/TextNotifier.php
View
@@ -9,40 +9,43 @@
abstract class Debug_ErrorHook_TextNotifier implements Debug_ErrorHook_INotifier
{
- const LOG_SERVER = 1;
- const LOG_TRACE = 2;
- const LOG_COOKIE = 4;
- const LOG_GET = 8;
- const LOG_POST = 16;
+ const LOG_SERVER = 1;
+ const LOG_TRACE = 2;
+ const LOG_COOKIE = 4;
+ const LOG_GET = 8;
+ const LOG_POST = 16;
const LOG_SESSION = 32;
const LOG_ALL = 65535;
-
- private $_whatToLog;
+
+ private $_whatToLog;
private $_bodySuffix;
-
- public function __construct($whatToLog)
- {
- $this->_whatToLog = $whatToLog;
- }
-
+
+ public function __construct($whatToLog)
+ {
+ $this->_whatToLog = $whatToLog;
+ }
+
public function setBodySuffixTest($text)
{
$this->_bodySuffix = $text;
}
-
- public function notify($errno, $errstr, $errfile, $errline, $trace)
+
+ public function notify($errno, $errstr, $errfile, $errline, $trace, $hash = null, $prependText = null)
{
- $body = array();
+ $body = array();
+ if ($prependText) {
+ $body[] = $prependText;
+ }
$body[] = $this->_makeSection(
"",
join("\n", array(
- (@$_SERVER['GATEWAY_INTERFACE']? "//{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}" : ""),
+ (!empty($_SERVER['GATEWAY_INTERFACE'])? "//{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}" : ""),
"$errno: $errstr",
"at $errfile on line $errline",
))
- );
- if ($this->_whatToLog & self::LOG_TRACE && $trace) {
- $body[] = $this->_makeSection("TRACE", Debug_ErrorHook_Util::backtraceToString($trace));
+ );
+ if ($this->_whatToLog & self::LOG_TRACE && $trace) {
+ $body[] = $this->_makeSection("TRACE", Debug_ErrorHook_Util::backtraceToString($trace));
}
if ($this->_whatToLog & self::LOG_SERVER) {
$body[] = $this->_makeSection("SERVER", Debug_ErrorHook_Util::varExport($_SERVER));
@@ -57,25 +60,32 @@ public function notify($errno, $errstr, $errfile, $errline, $trace)
$body[] = $this->_makeSection("POST", Debug_ErrorHook_Util::varExport($_POST));
}
if ($this->_whatToLog & self::LOG_SESSION) {
- $body[] = $this->_makeSection("SESSION", Debug_ErrorHook_Util::varExport(@$_SESSION));
+ $body[] = $this->_makeSection("SESSION", Debug_ErrorHook_Util::varExport(isset($_SESSION)? $_SESSION : null));
}
// Append body suffix?
$suffix = $this->_bodySuffix && is_callable($this->_bodySuffix)? call_user_func($this->_bodySuffix) : $this->_bodySuffix;
if ($suffix) {
- $body[] = $this->_makeSection("ADDITIONAL INFO", $suffix);
+ $body[] = $this->_makeSection("ADDITIONAL INFO", $suffix);
}
// Remain only 1st line for subject.
$errstr = preg_replace("/\r?\n.*/s", '', $errstr);
- $this->_notifyText("$errno: $errstr at $errfile on line $errline", join("\n", $body));
+ $subject = "$errno: $errstr at $errfile on line $errline";
+ if ($hash) {
+ // If hash of stacktrace is passed, make the subject unique on this
+ // hash. It prevents by-conversation gluing of errors with same text,
+ // but happened in different places.
+ $subject .= " [" . substr(strtoupper($hash), 0, 6) . "]";
+ }
+ $this->_notifyText($subject, join("\n", $body));
}
-
+
private function _makeSection($name, $body)
{
- $body = rtrim($body);
- if ($name) $body = preg_replace('/^/m', ' ', $body);
- $body = preg_replace('/^([ \t\r]*\n)+/s', '', $body);
- return ($name? $name . ":\n" : "") . $body . "\n";
+ $body = rtrim($body);
+ if ($name) $body = preg_replace('/^/m', ' ', $body);
+ $body = preg_replace('/^([ \t\r]*\n)+/s', '', $body);
+ return ($name? $name . ":\n" : "") . $body . "\n";
}
-
+
abstract protected function _notifyText($subject, $body);
}
23 lib/Debug/ErrorHook/Util.php
View
@@ -14,7 +14,7 @@ public static function varExport($var, $maxLevel = 10, $level = 0)
{
$escapes = "\"\r\t\x00\$";
$tab = ' ';
-
+
if (is_bool($var)) {
return $var ? 'TRUE' : 'FALSE';
} elseif (is_string($var)) {
@@ -26,19 +26,19 @@ public static function varExport($var, $maxLevel = 10, $level = 0)
} elseif (is_resource($var)) {
return 'NULL /* ' . $var . ' */';
}
-
+
if ($maxLevel < $level) {
return 'NULL /* ' . (string) $var . ' MAX LEVEL ' . $maxLevel . " REACHED*/";
}
-
+
if (is_array($var)) {
$return = "array(\n";
} else {
$return = get_class($var) . "::__set_state(array(\n";
}
-
+
$offset = str_repeat($tab, $level + 1);
-
+
foreach ((array) $var as $key => $value) {
$return .= $offset;
if (is_int($key)) {
@@ -48,15 +48,16 @@ public static function varExport($var, $maxLevel = 10, $level = 0)
}
$return .= ' => ' . self::varExport($value, $maxLevel, $level + 1) . ",\n";
}
-
+
return $return
. str_repeat($tab, $level)
. (is_array($var) ? ')' : '))');
}
-
+
/**
* Analog for debug_print_backtrace(), but returns string.
*
+ * @param array
* @return string
*/
public static function backtraceToString($backtrace)
@@ -74,7 +75,7 @@ public static function backtraceToString($backtrace)
$function = (isset($call['class'])) ?
$call['class'] . (isset($call['type']) ? $call['type'] : '.') . $call['function'] :
$call['function'];
-
+
$params = '';
if (isset($call['args']) && is_array($call['args'])) {
$args = array();
@@ -89,14 +90,14 @@ public static function backtraceToString($backtrace)
}
$params = implode(', ', $args);
}
-
+
$calls[] = sprintf('#%d %s(%s) called at [%s]',
$i,
$function,
$params,
$location);
}
-
+
return implode("\n", $calls) . "\n";
- }
+ }
}
4 t/Debug_ErrorHook/Catcher/010_simple.phpt
View
@@ -15,6 +15,4 @@ Notification: array (
'errline' => '*',
'tracecount' => 1,
)
-
-Notice: test in * on line *
-
+Error [1024]: test in * on line *
6 t/Debug_ErrorHook/Catcher/020_notice.phpt
View
@@ -17,8 +17,7 @@ Notification: array (
'errline' => '*',
'tracecount' => 0,
)
-
-Notice: Undefined variable: non_existed in * on line *
+Error [8]: Undefined variable: non_existed in * on line *
Notification: array (
'errno' => 'E_WARNING',
'errstr' => 'fopen(non-existed): failed to open stream: No such file or directory',
@@ -26,5 +25,4 @@ Notification: array (
'errline' => '*',
'tracecount' => 1,
)
-
-Warning: fopen(non-existed): failed to open stream: No such file or directory in * on line *
+Error [2]: fopen(non-existed): failed to open stream: No such file or directory in * on line *
4 t/Debug_ErrorHook/Catcher/030_fatal.phpt
View
@@ -8,8 +8,8 @@ non_existed_function();
?>
---EXPECT--
-Fatal error: Call to undefined function non_existed_function() in * on line *
+--EXPECTF--
+%sCall to undefined function non_existed_function()%s
Notification: array (
'errno' => 'E_ERROR',
'errstr' => 'Call to undefined function non_existed_function()',
10 t/Debug_ErrorHook/Catcher/040_exception.phpt
View
@@ -13,12 +13,12 @@ f();
?>
---EXPECT--
-Fatal error: Uncaught exception 'Exception' with message 'unhandled' in *:*
+--EXPECTF--
+%sUncaught exception 'Exception' with message 'unhandled'%s
Stack trace:
-#0 *(*): f()
-#1 {main}
- thrown in * on line *
+#0 %s
+#1 %s
+ thrown in %s
Notification: array (
'errno' => 'E_ERROR',
'errstr' => 'Uncaught exception \'Exception\' with message \'unhandled\' in *:*
9 t/Debug_ErrorHook/MailNotifier/010_simple.phpt
View
@@ -10,7 +10,7 @@ f();
?>
---EXPECT--
+--EXPECTF--
array (
0 => 'test@example.com',
1 => '[ERROR] E_NOTICE: Undefined variable: a at * on line *',
@@ -18,8 +18,9 @@ array (
at * on line *
',
3 => 'From: test@example.com
-Content-Type: text/plain; charset=UTF-8',
+Content-Type: text/plain; charset=UTF-8
+Message-Id: <%s@errorhook>
+In-Reply-To: <%s@errorhook>',
)
-
-Notice: Undefined variable: a in * on line *
+Error [8]: Undefined variable: a in * on line *
10 t/Debug_ErrorHook/RemoveDupsWrapper/010_nodups.phpt
View
@@ -17,10 +17,6 @@ Notification: array (
'errline' => '*',
'tracecount' => 0,
)
-
-Notice: Undefined variable: a in * on line *
-
-Notice: Undefined variable: a in * on line *
-
-Notice: Undefined variable: a in * on line *
-
+Error [8]: Undefined variable: a in * on line *
+Error [8]: Undefined variable: a in * on line *
+Error [8]: Undefined variable: a in * on line *
7 t/Debug_ErrorHook/RemoveDupsWrapper/020_gc.phpt
View
@@ -23,6 +23,9 @@ $printListenerWithNoDups->addNotifier(new TestRemoveDups(new PrintNotifier(), 'f
echo $a;
+if (is_dir("fixture")) echo "Fixture dir exists. Its contents:\n";
+else echo "Fixture dir DOES NOT exist!\n";
+
print_r(glob("fixture/*"));
?>
@@ -34,8 +37,8 @@ Notification: array (
'errline' => '*',
'tracecount' => 0,
)
-
-Notice: Undefined variable: a in * on line *
+Error [8]: Undefined variable: a in * on line *
+Fixture dir exists. Its contents:
Array
(
)
33 t/Debug_ErrorHook/RemoveDupsWrapper/030_cnt.phpt
View
@@ -0,0 +1,33 @@
+--TEST--
+Debug_ErrorHook_RemoveDupsWrapper: no duplicated notifications should be sent
+--FILE--
+<?php
+define("NODUPS_DELAY", 6);
+require dirname(__FILE__) . '/init.php';
+
+for ($i = 0; $i < 3; $i++) {
+ if ($i) sleep(4);
+ echo $a;
+}
+
+?>
+--EXPECT--
+Notification: array (
+ 'errno' => 'E_NOTICE',
+ 'errstr' => 'Undefined variable: a',
+ 'errfile' => '030_cnt.php',
+ 'errline' => '*',
+ 'tracecount' => 0,
+)
+Error [8]: Undefined variable: a in * on line *
+Error [8]: Undefined variable: a in * on line *
+Notification: array (
+ 'errno' => 'E_NOTICE',
+ 'errstr' => 'Undefined variable: a',
+ 'errfile' => '030_cnt.php',
+ 'errline' => '*',
+ 'tracecount' => 0,
+ 'prependText' => 'THIS ERROR HAPPENED 2 TIMES WITHIN LAST 0 MINUTES!
+',
+)
+Error [8]: Undefined variable: a in * on line *
4 t/Debug_ErrorHook/RemoveDupsWrapper/init.php
View
@@ -2,6 +2,8 @@
require_once dirname(__FILE__) . "/../init.php";
require_once "Debug/ErrorHook/RemoveDupsWrapper.php";
+@define("NODUPS_DELAY", 100);
+
function cleanupTmp()
{
$dir = "fixture";
@@ -10,6 +12,6 @@ function cleanupTmp()
}
$printListenerWithNoDups = new Debug_ErrorHook_Listener();
-$printListenerWithNoDups->addNotifier(new Debug_ErrorHook_RemoveDupsWrapper(new PrintNotifier(), 'fixture', 100));
+$printListenerWithNoDups->addNotifier(new Debug_ErrorHook_RemoveDupsWrapper(new PrintNotifier(), 'fixture', NODUPS_DELAY));
register_shutdown_function("cleanupTmp");
3  t/Debug_ErrorHook/TextNotifier/010_all.phpt
View
@@ -55,6 +55,5 @@ SESSION:
array(
"session" => "var",
)
-
-Notice: Undefined variable: a in * on line *
+Error [8]: Undefined variable: a in * on line *
4 t/Debug_ErrorHook/TextNotifier/020_fatal.phpt
View
@@ -14,8 +14,8 @@ non_existed_function();
?>
---EXPECT--
-Fatal error: Call to undefined function non_existed_function() in * on line *
+--EXPECTF--
+%sCall to undefined function non_existed_function()%s
Text notification:
------------------
Subject: E_ERROR: Call to undefined function non_existed_function() at * on line *
15 t/Debug_ErrorHook/init.php
View
@@ -13,6 +13,7 @@ function printr($value, $comment=null)
if ($comment !== null) echo "$comment: ";
var_export($value);
echo "\n";
+ ob_flush(); // essential! else above echo may be flushed AFTER a notice to STDERR
}
function cleanupStdout($s)
@@ -22,9 +23,17 @@ function cleanupStdout($s)
return $s;
}
+function error_handler($errno, $errstr, $errfile, $errline)
+{
+ if ($errno & error_reporting()) {
+ echo "Error [$errno]: $errstr in $errfile on line $errline\n";
+ }
+ return true; // skip built-in error handler
+}
+
class PrintNotifier implements Debug_ErrorHook_INotifier
{
- public function notify($errno, $errstr, $errfile, $errline, $trace)
+ public function notify($errno, $errstr, $errfile, $errline, $trace, $hash = null, $prependText = null)
{
printr(
array(
@@ -33,7 +42,7 @@ public function notify($errno, $errstr, $errfile, $errline, $trace)
"errfile" => basename($errfile),
"errline" => "*",
"tracecount" => count($trace)
- ),
+ ) + ($prependText? array("prependText" => $prependText) : array()),
"Notification"
);
}
@@ -42,4 +51,4 @@ public function notify($errno, $errstr, $errfile, $errline, $trace)
if (function_exists("xdebug_disable")) xdebug_disable();
ob_start("cleanupStdout");
-
+set_error_handler('error_handler');
Please sign in to comment.
Something went wrong with that request. Please try again.