Index: library/Zend/Log.php =================================================================== --- library/Zend/Log.php (revision 21081) +++ library/Zend/Log.php (working copy) @@ -70,6 +70,23 @@ */ protected $_defaultFilterNamespace = 'Zend_Log_Filter'; + /** + * + * @var callback + */ + protected $_origErrorHandler = null; + + /** + * + * @var boolean + */ + protected $_registeredErrorHandler = false; + + /** + * + * @var array + */ + protected $_errorHandlerMap = false; /** * Class constructor. Create a new logger @@ -422,4 +439,82 @@ public function setEventItem($name, $value) { $this->_extras = array_merge($this->_extras, array($name => $value)); } + + /** + * Register Logging system as an error handler to log php errors + * Note: it still calls the original error handler if set_error_handler is able to return it. + * + * Errors will be mapped as: + * E_NOTICE, E_USER_NOTICE => NOTICE + * E_WARNING, E_CORE_WARNING, E_USER_WARNING => WARN + * E_ERROR, E_USER_ERROR, E_CORE_ERROR, E_RECOVERABLE_ERROR => ERR + * E_DEPRECATED, E_STRICT, E_USER_DEPRECATED => DEBUG + * (unknown/other) => INFO + * + * @link http://www.php.net/manual/en/function.set-error-handler.php Custom error handler + * + * @return Zend_Log + */ + public function registerErrorHandler() + { + // Only register once. Avoids loop issues if it gets registered twice. + if ($this->_registeredErrorHandler) { return $this; } + + $this->_origErrorHandler = set_error_handler(array($this, 'errorHandler')); + + // Contruct a default map of phpErrors to Zend_Log priorities. + // Some of the errors are uncatchable, but are included for completeness + $this->_errorHandlerMap = array( + E_NOTICE => Zend_Log::NOTICE, + E_USER_NOTICE => Zend_Log::NOTICE, + E_WARNING => Zend_Log::WARN, + E_CORE_WARNING => Zend_Log::WARN, + E_USER_WARNING => Zend_Log::WARN, + E_ERROR => Zend_Log::ERR, + E_USER_ERROR => Zend_Log::ERR, + E_CORE_ERROR => Zend_Log::ERR, + E_RECOVERABLE_ERROR => Zend_Log::ERR, + E_STRICT => Zend_Log::DEBUG, + ); + // PHP 5.3.0+ + if (defined('E_DEPRECATED')) { + $this->_errorHandlerMap['E_DEPRECATED'] = Zend_Log::DEBUG; + } + if (defined('E_USER_DEPRECATED')) { + $this->_errorHandlerMap['E_USER_DEPRECATED'] = Zend_Log::DEBUG; + } + + $this->_registeredErrorHandler = true; + return $this; + } + + /** + * Error Handler will convert error into log message, and then call the original error handler + * + * @link http://www.php.net/manual/en/function.set-error-handler.php Custom error handler + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * @param array $errcontext + * @return boolean + */ + public function errorHandler($errno, $errstr, $errfile, $errline, $errcontext) + { + $errorLevel = error_reporting(); + + if ($errorLevel & $errno) { + if (isset($this->_errorHandlerMap[$errno])) { + $priority = $this->_errorHandlerMap[$errno]; + } else { + $priority = Zend_Log::INFO; + } + $this->log($errstr, $priority, array('errno'=>$errno, 'file'=>$errfile, 'line'=>$errline, 'context'=>$errcontext)); + } + + if ($this->_origErrorHandler !== null) { + return call_user_func($this->_origErrorHandler, $errno, $errstr, $errfile, $errline, $errcontext); + } + return false; + } } Index: tests/Zend/Log/LogTest.php =================================================================== --- tests/Zend/Log/LogTest.php (revision 21081) +++ tests/Zend/Log/LogTest.php (working copy) @@ -244,47 +244,98 @@ // Factory - public function testLogConstructFromConfigStream() + public function testLogConstructFromConfigStream() { $cfg = array('log' => array('memory' => array( - 'writerName' => "Stream", - 'writerNamespace' => "Zend_Log_Writer", + 'writerName' => "Stream", + 'writerNamespace' => "Zend_Log_Writer", 'writerParams' => array( 'stream' => "php://memory" - ) + ) ))); $logger = Zend_Log::factory($cfg['log']); $this->assertTrue($logger instanceof Zend_Log); } - public function testLogConstructFromConfigStreamAndFilter() + public function testLogConstructFromConfigStreamAndFilter() { $cfg = array('log' => array('memory' => array( - 'writerName' => "Stream", - 'writerNamespace' => "Zend_Log_Writer", + 'writerName' => "Stream", + 'writerNamespace' => "Zend_Log_Writer", 'writerParams' => array( 'stream' => "php://memory" - ), - 'filterName' => "Priority", + ), + 'filterName' => "Priority", 'filterParams' => array( - 'priority' => "Zend_Log::CRIT", + 'priority' => "Zend_Log::CRIT", 'operator' => "<=" - ), + ), ))); $logger = Zend_Log::factory($cfg['log']); $this->assertTrue($logger instanceof Zend_Log); } - public function testFactoryUsesNameAndNamespaceWithoutModifications() + public function testFactoryUsesNameAndNamespaceWithoutModifications() { $cfg = array('log' => array('memory' => array( - 'writerName' => "ZendMonitor", - 'writerNamespace' => "Zend_Log_Writer", + 'writerName' => "ZendMonitor", + 'writerNamespace' => "Zend_Log_Writer", ))); $logger = Zend_Log::factory($cfg['log']); $this->assertTrue($logger instanceof Zend_Log); } + + public function testUsingWithErrorHandler() + { + $writer = new Zend_Log_Writer_Mock(); + + $logger = new Zend_Log(); + $logger->addWriter($writer); + $this->errWriter = $writer; + + + $oldErrorLevel = error_reporting(); + + $this->expectingLogging = true; + error_reporting(E_ALL | E_STRICT); + + $oldHandler = set_error_handler(array($this, 'verifyHandlerData')); + $logger->registerErrorHandler(); + + trigger_error("Testing notice shows up in logs", E_USER_NOTICE); + trigger_error("Testing warning shows up in logs", E_USER_WARNING); + trigger_error("Testing error shows up in logs", E_USER_ERROR); + + $this->expectingLogging = false; + error_reporting(0); + + trigger_error("Testing notice misses logs", E_USER_NOTICE); + trigger_error("Testing warning misses logs", E_USER_WARNING); + trigger_error("Testing error misses logs", E_USER_ERROR); + + restore_error_handler(); // Pop off the Logger + restore_error_handler(); // Pop off the verifyHandlerData + error_reporting($oldErrorLevel); // Restore original reporting level + unset($this->errWriter); + } + + /** + * Used by testUsingWithErrorHandler + */ + public function verifyHandlerData($errno, $errstr, $errfile, $errline, $errcontext) + { + if ($this->expectingLogging) { + $this->assertFalse(empty($this->errWriter->events)); + $event = array_shift($this->errWriter->events); + $this->assertEquals($errstr, $event['message']); + $this->assertEquals($errno, $event['errno']); + $this->assertEquals($errfile, $event['file']); + $this->assertEquals($errline, $event['line']); + } else { + $this->assertTrue(empty($this->errWriter->events)); + } + } } Index: documentation/manual/en/module_specs/Zend_Log-Overview.xml =================================================================== --- documentation/manual/en/module_specs/Zend_Log-Overview.xml (revision 21081) +++ documentation/manual/en/module_specs/Zend_Log-Overview.xml (working copy) @@ -230,4 +230,65 @@ to learn more. + + + Log PHP Errors + + + Zend_Log can also be used to log php errors. Calling registerErrorHandler() will + add Zend_Log before the current error handler, and will pass the error along as well. + + + + Zend_Log events from php errors have the additional fields matching + <methodname>handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] )</methodname> + from <ulink url="http://us3.php.net/manual/en/function.set-error-handler.php">set_error_handler</ulink> + + + + + Name + Error Handler Paramater + Description + + + + + + message + errstr + Contains the error message, as a string. + + + + errno + errno + Contains the level of the error raised, as an integer. + + + + file + errfile + Contains the filename that the error was raised in, as a string. + + + + line + errline + Contains the line number the error was raised at, as an integer. + + + + context + errcontext + (optional) An array that points to the active symbol table at the point the + error occurred. In other words, errcontext will contain an array of every variable + that existed in the scope the error was triggered in. User error handler must not + modify error context. + + + + +
+