[!!!][FEATURE] Introduce PSR-3 Logging
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Error / ErrorHandler.php
index bc4be49..e82acf5 100644 (file)
 <?php
 namespace TYPO3\CMS\Core\Error;
 
-/***************************************************************
- *  Copyright notice
+/*
+ * This file is part of the TYPO3 CMS project.
  *
- *  (c) 2009-2013 Ingo Renner <ingo@typo3.org>
- *  All rights reserved
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
  *
- *  This script is part of the TYPO3 project. The TYPO3 project is
- *  free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
  *
- *  The GNU General Public License can be found at
- *  http://www.gnu.org/copyleft/gpl.html.
- *
- *  This script is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  This copyright notice MUST APPEAR in all copies of the script!
- ***************************************************************/
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Log\LogLevel;
+use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\TimeTracker\TimeTracker;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
 /**
  * Global error handler for TYPO3
  *
- * This file is a backport from FLOW3
- *
- * @author Rupert Germann <rupi@gmx.li>
+ * This file is a backport from TYPO3 Flow
  */
-class ErrorHandler implements \TYPO3\CMS\Core\Error\ErrorHandlerInterface {
-
-       /**
-        * Error levels which should result in an exception thrown.
-        *
-        * @var array
-        */
-       protected $exceptionalErrors = array();
-
-       /**
-        * Registers this class as default error handler
-        *
-        * @param integer $errorHandlerErrors The integer representing the E_* error level which should be
-        * @return void
-        */
-       public function __construct($errorHandlerErrors) {
-                       // reduces error types to those a custom error handler can process
-               $errorHandlerErrors = $errorHandlerErrors & ~(E_COMPILE_WARNING | E_COMPILE_ERROR | E_CORE_WARNING | E_CORE_ERROR | E_PARSE | E_ERROR);
-               set_error_handler(array($this, 'handleError'), $errorHandlerErrors);
-       }
-
-       /**
-        * Defines which error levels should result in an exception thrown.
-        *
-        * @param integer $exceptionalErrors The integer representing the E_* error level to handle as exceptions
-        * @return void
-        */
-       public function setExceptionalErrors($exceptionalErrors) {
-               $this->exceptionalErrors = (int) $exceptionalErrors;
-       }
-
-       /**
-        * Handles an error.
-        * If the error is registered as exceptionalError it will by converted into an exception, to be handled
-        * by the configured exceptionhandler. Additionall the error message is written to the configured logs.
-        * If TYPO3_MODE is 'BE' the error message is also added to the flashMessageQueue, in FE the error message
-        * is displayed in the admin panel (as TsLog message)
-        *
-        * @param integer $errorLevel The error level - one of the E_* constants
-        * @param string $errorMessage The error message
-        * @param string $errorFile Name of the file the error occurred in
-        * @param integer $errorLine Line number where the error occurred
-        * @return void
-        * @throws \TYPO3\CMS\Core\Error\Exception with the data passed to this method if the error is registered as exceptionalError
-        */
-       public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine) {
-               // Don't do anything if error_reporting is disabled by an @ sign
-               if (error_reporting() == 0) {
-                       return TRUE;
-               }
-               $errorLevels = array(
-                       E_WARNING => 'Warning',
-                       E_NOTICE => 'Notice',
-                       E_USER_ERROR => 'User Error',
-                       E_USER_WARNING => 'User Warning',
-                       E_USER_NOTICE => 'User Notice',
-                       E_STRICT => 'Runtime Notice',
-                       E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
-                       E_DEPRECATED => 'Runtime Deprecation Notice'
-               );
-               $message = 'PHP ' . $errorLevels[$errorLevel] . ': ' . $errorMessage . ' in ' . $errorFile . ' line ' . $errorLine;
-               if ($errorLevel & $this->exceptionalErrors) {
-                               // handle error raised at early parse time
-                               // autoloader not available & built-in classes not resolvable
-                       if (!class_exists('stdClass', FALSE)) {
-                               $message = 'PHP ' . $errorLevels[$errorLevel] . ': ' . $errorMessage . ' in ' . basename($errorFile) .
-                                       'line ' . $errorLine;
-                               die($message);
-                       }
-                       // We need to manually require the exception classes in case the autoloader is not available at this point yet.
-                       // @see http://forge.typo3.org/issues/23444
-                       if (!class_exists('TYPO3\\CMS\\Core\\Error\\Exception', FALSE)) {
-                               require_once PATH_site . 'typo3/sysext/core/Classes/Exception.php';
-                               require_once PATH_site . 'typo3/sysext/core/Classes/Error/Exception.php';
-                       }
-                       throw new \TYPO3\CMS\Core\Error\Exception($message, 1);
-               } else {
-                       switch ($errorLevel) {
-                       case E_USER_ERROR:
-
-                       case E_RECOVERABLE_ERROR:
-                               $severity = 2;
-                               break;
-                       case E_USER_WARNING:
-
-                       case E_WARNING:
-                               $severity = 1;
-                               break;
-                       default:
-                               $severity = 0;
-                               break;
-                       }
-                       $logTitle = 'Core: Error handler (' . TYPO3_MODE . ')';
-                       // Write error message to the configured syslogs,
-                       // see: $TYPO3_CONF_VARS['SYS']['systemLog']
-                       if ($errorLevel & $GLOBALS['TYPO3_CONF_VARS']['SYS']['syslogErrorReporting']) {
-                               \TYPO3\CMS\Core\Utility\GeneralUtility::sysLog($message, $logTitle, $severity);
-                       }
-                       // Write error message to devlog extension(s),
-                       // see: $TYPO3_CONF_VARS['SYS']['enable_errorDLOG']
-                       if (TYPO3_ERROR_DLOG) {
-                               \TYPO3\CMS\Core\Utility\GeneralUtility::devLog($message, $logTitle, $severity + 1);
-                       }
-                       // Write error message to TSlog (admin panel)
-                       if (is_object($GLOBALS['TT'])) {
-                               $GLOBALS['TT']->setTSlogMessage($logTitle . ': ' . $message, $severity + 1);
-                       }
-                       // Write error message to sys_log table (ext: belog, Tools->Log)
-                       if ($errorLevel & $GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting']) {
-                               // Silently catch in case an error occurs before a database connection exists,
-                               // but DatabaseConnection fails to connect.
-                               try {
-                                       $this->writeLog($logTitle . ': ' . $message, $severity);
-                               } catch (\Exception $e) {
-                               }
-                       }
-                       // Add error message to the flashmessageQueue
-                       if (defined('TYPO3_ERRORHANDLER_MODE') && TYPO3_ERRORHANDLER_MODE == 'debug') {
-                               $flashMessage = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessage', $message, 'PHP ' . $errorLevels[$errorLevel], $severity);
-                               /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
-                               $flashMessageService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Messaging\\FlashMessageService');
-                               /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
-                               $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
-                               $defaultFlashMessageQueue->enqueue($flashMessage);
-                       }
-               }
-               // Don't execute PHP internal error handler
-               return TRUE;
-       }
-
-       /**
-        * Writes an error in the sys_log table
-        *
-        * @param string $logMessage Default text that follows the message (in english!).
-        * @param integer $severity The eror level of the message (0 = OK, 1 = warning, 2 = error)
-        * @return void
-        */
-       protected function writeLog($logMessage, $severity) {
-               if (is_object($GLOBALS['TYPO3_DB']) && !empty($GLOBALS['TYPO3_DB']->link)) {
-                       $userId = 0;
-                       $workspace = 0;
-                       if (is_object($GLOBALS['BE_USER'])) {
-                               if (isset($GLOBALS['BE_USER']->user['uid'])) {
-                                       $userId = $GLOBALS['BE_USER']->user['uid'];
-                               }
-                               if (isset($GLOBALS['BE_USER']->workspace)) {
-                                       $workspace = $GLOBALS['BE_USER']->workspace;
-                               }
-                       }
-                       $fields_values = array(
-                               'userid' => $userId,
-                               'type' => 5,
-                               'action' => 0,
-                               'error' => $severity,
-                               'details_nr' => 0,
-                               'details' => $logMessage,
-                               'IP' => \TYPO3\CMS\Core\Utility\GeneralUtility::getIndpEnv('REMOTE_ADDR'),
-                               'tstamp' => $GLOBALS['EXEC_TIME'],
-                               'workspace' => $workspace
-                       );
-                       $GLOBALS['TYPO3_DB']->exec_INSERTquery('sys_log', $fields_values);
-               }
-       }
+class ErrorHandler implements ErrorHandlerInterface, LoggerAwareInterface
+{
+    use LoggerAwareTrait;
 
-}
+    /**
+     * Error levels which should result in an exception thrown.
+     *
+     * @var int
+     */
+    protected $exceptionalErrors = 0;
+
+    /**
+     * Error levels which should be handled.
+     *
+     * @var int
+     */
+    protected $errorHandlerErrors = 0;
+
+    /**
+     * Whether to write a flash message in case of an error
+     *
+     * @var bool
+     */
+    protected $debugMode = false;
 
+    /**
+     * Registers this class as default error handler
+     *
+     * @param int $errorHandlerErrors The integer representing the E_* error level which should be
+     */
+    public function __construct($errorHandlerErrors)
+    {
+        $excludedErrors = E_COMPILE_WARNING | E_COMPILE_ERROR | E_CORE_WARNING | E_CORE_ERROR | E_PARSE | E_ERROR;
+        // reduces error types to those a custom error handler can process
+        $this->errorHandlerErrors = $errorHandlerErrors & ~$excludedErrors;
+        set_error_handler([$this, 'handleError'], $this->errorHandlerErrors);
+    }
 
-?>
\ No newline at end of file
+    /**
+     * Defines which error levels should result in an exception thrown.
+     *
+     * @param int $exceptionalErrors The integer representing the E_* error level to handle as exceptions
+     */
+    public function setExceptionalErrors($exceptionalErrors)
+    {
+        $exceptionalErrors = (int)$exceptionalErrors;
+        // We always disallow E_USER_DEPRECATED to generate exceptions as this may cause
+        // bad user experience specifically during upgrades.
+        $this->exceptionalErrors = $exceptionalErrors & ~E_USER_DEPRECATED;
+    }
+
+    /**
+     * @param bool $debugMode
+     */
+    public function setDebugMode($debugMode)
+    {
+        $this->debugMode = (bool)$debugMode;
+    }
+
+    /**
+     * Handles an error.
+     * If the error is registered as exceptionalError it will by converted into an exception, to be handled
+     * by the configured exceptionhandler. Additionally the error message is written to the configured logs.
+     * If TYPO3_MODE is 'BE' the error message is also added to the flashMessageQueue, in FE the error message
+     * is displayed in the admin panel (as TsLog message)
+     *
+     * @param int $errorLevel The error level - one of the E_* constants
+     * @param string $errorMessage The error message
+     * @param string $errorFile Name of the file the error occurred in
+     * @param int $errorLine Line number where the error occurred
+     * @return bool
+     * @throws Exception with the data passed to this method if the error is registered as exceptionalError
+     */
+    public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine)
+    {
+        // Don't do anything if error_reporting is disabled by an @ sign or $errorLevel is something we won't handle
+        $shouldHandleErrorLevel = (bool)($this->errorHandlerErrors & $errorLevel);
+        if (error_reporting() === 0 || !$shouldHandleErrorLevel) {
+            return true;
+        }
+        $errorLevels = [
+            E_WARNING => 'PHP Warning',
+            E_NOTICE => 'PHP Notice',
+            E_USER_ERROR => 'PHP User Error',
+            E_USER_WARNING => 'PHP User Warning',
+            E_USER_NOTICE => 'PHP User Notice',
+            E_STRICT => 'PHP Runtime Notice',
+            E_RECOVERABLE_ERROR => 'PHP Catchable Fatal Error',
+            E_USER_DEPRECATED => 'TYPO3 Deprecation Notice',
+            E_DEPRECATED => 'PHP Runtime Deprecation Notice'
+        ];
+        $message = $errorLevels[$errorLevel] . ': ' . $errorMessage . ' in ' . $errorFile . ' line ' . $errorLine;
+        if ($errorLevel & $this->exceptionalErrors) {
+            throw new Exception($message, 1476107295);
+        }
+        switch ($errorLevel) {
+            case E_USER_ERROR:
+            case E_RECOVERABLE_ERROR:
+                // no $flashMessageSeverity, as there will be no flash message for errors
+                $severity = 2;
+                break;
+            case E_USER_WARNING:
+            case E_WARNING:
+                $flashMessageSeverity = FlashMessage::WARNING;
+                $severity = 1;
+                break;
+            default:
+                $flashMessageSeverity = FlashMessage::NOTICE;
+                $severity = 0;
+        }
+        $logTitle = 'Core: Error handler (' . TYPO3_MODE . ')';
+        $message = $logTitle . ': ' . $message;
+
+        if ($errorLevel === E_USER_DEPRECATED) {
+            $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger('TYPO3.CMS.deprecations');
+            $logger->notice($message);
+            return true;
+        }
+        if ($this->logger) {
+            $this->logger->log(LogLevel::normalizeLevel(LogLevel::NOTICE) - $severity, $message);
+        }
+
+        // Write error message to TSlog (admin panel)
+        $timeTracker = $this->getTimeTracker();
+        if (is_object($timeTracker)) {
+            $timeTracker->setTSlogMessage($message, $severity + 1);
+        }
+        // Write error message to sys_log table (ext: belog, Tools->Log)
+        if ($errorLevel & $GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting']) {
+            // Silently catch in case an error occurs before a database connection exists.
+            try {
+                $this->writeLog($message, $severity);
+            } catch (\Exception $e) {
+            }
+        }
+        if ($severity === 2) {
+            // Let the internal handler continue. This will stop the script
+            return false;
+        }
+        if ($this->debugMode) {
+            /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
+            $flashMessage = GeneralUtility::makeInstance(
+                \TYPO3\CMS\Core\Messaging\FlashMessage::class,
+                $message,
+                $errorLevels[$errorLevel],
+                $flashMessageSeverity
+                    );
+            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
+            $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
+            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
+            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
+            $defaultFlashMessageQueue->enqueue($flashMessage);
+        }
+        // Don't execute PHP internal error handler
+        return true;
+    }
+
+    /**
+     * Writes an error in the sys_log table
+     *
+     * @param string $logMessage Default text that follows the message (in english!).
+     * @param int $severity The error level of the message (0 = OK, 1 = warning, 2 = error)
+     */
+    protected function writeLog($logMessage, $severity)
+    {
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('sys_log');
+        if ($connection->isConnected()) {
+            $userId = 0;
+            $workspace = 0;
+            $data = [];
+            $backendUser = $this->getBackendUser();
+            if (is_object($backendUser)) {
+                if (isset($backendUser->user['uid'])) {
+                    $userId = $backendUser->user['uid'];
+                }
+                if (isset($backendUser->workspace)) {
+                    $workspace = $backendUser->workspace;
+                }
+                if (!empty($backendUser->user['ses_backuserid'])) {
+                    $data['originalUser'] = $backendUser->user['ses_backuserid'];
+                }
+            }
+
+            $connection->insert(
+                'sys_log',
+                [
+                    'userid' => $userId,
+                    'type' => 5,
+                    'action' => 0,
+                    'error' => $severity,
+                    'details_nr' => 0,
+                    'details' => str_replace('%', '%%', $logMessage),
+                    'log_data' => empty($data) ? '' : serialize($data),
+                    'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
+                    'tstamp' => $GLOBALS['EXEC_TIME'],
+                    'workspace' => $workspace
+                ]
+            );
+        }
+    }
+
+    /**
+     * @return TimeTracker
+     */
+    protected function getTimeTracker()
+    {
+        return GeneralUtility::makeInstance(TimeTracker::class);
+    }
+
+    /**
+     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
+}