‪TYPO3CMS  ‪main
ErrorHandler.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
16 namespace ‪TYPO3\CMS\Core\Error;
17 
18 use Psr\Http\Message\ServerRequestInterface;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
21 use Psr\Log\LogLevel;
29 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
30 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
34 
40 class ‪ErrorHandler implements ‪ErrorHandlerInterface, LoggerAwareInterface
41 {
42  use LoggerAwareTrait;
43 
47  protected int ‪$exceptionalErrors = 0;
48 
52  protected int ‪$errorHandlerErrors = 0;
53 
57  protected bool ‪$debugMode = false;
58 
59  protected const ‪ERROR_LEVEL_LABELS = [
60  E_WARNING => 'PHP Warning',
61  E_NOTICE => 'PHP Notice',
62  E_USER_ERROR => 'PHP User Error',
63  E_USER_WARNING => 'PHP User Warning',
64  E_USER_NOTICE => 'PHP User Notice',
65  E_STRICT => 'PHP Runtime Notice',
66  E_RECOVERABLE_ERROR => 'PHP Catchable Fatal Error',
67  E_USER_DEPRECATED => 'TYPO3 Deprecation Notice',
68  E_DEPRECATED => 'PHP Runtime Deprecation Notice',
69  ];
70 
77  {
78  $excludedErrors = E_COMPILE_WARNING | E_COMPILE_ERROR | E_CORE_WARNING | E_CORE_ERROR | E_PARSE | E_ERROR;
79  // reduces error types to those a custom error handler can process
80  $this->errorHandlerErrors = (int)‪$errorHandlerErrors & ~$excludedErrors;
81  }
82 
89  {
91  // We always disallow E_USER_DEPRECATED to generate exceptions as this may cause
92  // bad user experience specifically during upgrades.
93  $this->exceptionalErrors = ‪$exceptionalErrors & ~E_USER_DEPRECATED;
94  }
95 
99  public function ‪setDebugMode(‪$debugMode)
100  {
101  $this->debugMode = (bool)‪$debugMode;
102  }
103 
104  public function ‪registerErrorHandler()
105  {
106  set_error_handler([$this, 'handleError']);
107  }
108 
123  public function ‪handleError($errorLevel, $errorMessage, $errorFile, $errorLine)
124  {
125  // Filter all errors, that should not be reported/ handled from current error reporting
126  $reportingLevel = $this->errorHandlerErrors & error_reporting();
127  // Since symfony does this:
128  // @trigger_error('...', E_USER_DEPRECATED), and we DO want to log these,
129  // we always enforce deprecation messages to be handled, even when they are silenced
130  $reportingLevel |= E_USER_DEPRECATED;
131  $shouldHandleError = (bool)($reportingLevel & $errorLevel);
132  if (!$shouldHandleError) {
133  return ‪self::ERROR_HANDLED;
134  }
135 
136  $message = self::ERROR_LEVEL_LABELS[$errorLevel] . ': ' . $errorMessage . ' in ' . $errorFile . ' line ' . $errorLine;
137  if ($errorLevel & $this->exceptionalErrors) {
138  throw new ‪Exception($message, 1476107295);
139  }
140 
141  $message = $this->‪getFormattedLogMessage($message);
142 
143  if ($errorLevel === E_USER_DEPRECATED || $errorLevel === E_DEPRECATED) {
144  $logger = GeneralUtility::makeInstance(LogManager::class)->getLogger('TYPO3.CMS.deprecations');
145  $logger->notice($message);
146  return ‪self::ERROR_HANDLED;
147  }
148 
149  switch ($errorLevel) {
150  case E_USER_ERROR:
151  case E_RECOVERABLE_ERROR:
152  $logLevel = LogLevel::ERROR;
153  break;
154  case E_USER_WARNING:
155  case E_WARNING:
156  $logLevel = LogLevel::WARNING;
157  break;
158  default:
159  $logLevel = LogLevel::NOTICE;
160  }
161 
162  if ($this->logger) {
163  $this->logger->log($logLevel, $message);
164  }
165 
166  try {
167  // Write error message to TSlog (admin panel)
168  $this->‪getTimeTracker()->setTSlogMessage($message, $logLevel);
169  } catch (\Throwable $e) {
170  // Silently catch in case an error occurs before the DI container is in place
171  }
172  // Write error message to sys_log table (ext: belog, Tools->Log)
173  if ($errorLevel & (‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting'] ?? 0)) {
174  // Silently catch in case an error occurs before a database connection exists.
175  try {
176  $this->‪writeLog($message, $logLevel);
177  } catch (\‪Exception $e) {
178  }
179  }
180  if ($logLevel === LogLevel::ERROR) {
181  // Let the internal handler continue. This will stop the script
183  }
184  if ($this->debugMode) {
185  $this->‪createAndEnqueueFlashMessage($message, $errorLevel);
186  }
187  // Don't execute PHP internal error handler
188  return ‪self::ERROR_HANDLED;
189  }
190 
191  protected function ‪createAndEnqueueFlashMessage(string $message, int $errorLevel): void
192  {
193  switch ($errorLevel) {
194  case E_USER_WARNING:
195  case E_WARNING:
196  $flashMessageSeverity = ContextualFeedbackSeverity::WARNING;
197  break;
198  default:
199  $flashMessageSeverity = ContextualFeedbackSeverity::NOTICE;
200  }
201  $flashMessage = GeneralUtility::makeInstance(
202  FlashMessage::class,
203  $message,
204  self::ERROR_LEVEL_LABELS[$errorLevel],
205  $flashMessageSeverity
206  );
207  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
208  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
209  $defaultFlashMessageQueue->enqueue($flashMessage);
210  }
211 
218  protected function ‪writeLog($logMessage, string $logLevel)
219  {
220  // Avoid ConnectionPool usage prior boot completion (see #96291).
221  if (!GeneralUtility::getContainer()->get('boot.state')->complete) {
222  if ($this->logger) {
223  // Log via debug(), the original message has already been logged with the original serverity in handleError().
224  // This log entry is targeted for users that try to debug why a log record is missing in sys_log
225  // while it has been logged to the logging framework.
226  $this->logger->debug(
227  'An error could not be logged to database as it appeared during early bootstrap (TCA or ext_localconf.php loading).',
228  ['original_message' => $logMessage, 'original_loglevel' => $logLevel]
229  );
230  }
231  return;
232  }
233  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
234  ->getConnectionForTable('sys_log');
235  if ($connection->isConnected()) {
236  $userId = 0;
237  $workspace = 0;
238  $data = [];
239  $backendUser = $this->‪getBackendUser();
240  if (is_object($backendUser)) {
241  if (isset($backendUser->user['uid'])) {
242  $userId = $backendUser->user['uid'];
243  }
244  $workspace = $backendUser->workspace;
245  if ($backUserId = $backendUser->getOriginalUserIdWhenInSwitchUserMode()) {
246  $data['originalUser'] = $backUserId;
247  }
248  }
249 
250  $connection->insert(
251  'sys_log',
252  [
253  'userid' => $userId,
254  'type' => SystemLogType::ERROR,
255  'channel' => SystemLogType::toChannel(SystemLogType::ERROR),
256  'action' => SystemLogGenericAction::UNDEFINED,
257  'error' => SystemLogErrorClassification::SYSTEM_ERROR,
258  'level' => $logLevel,
259  'details_nr' => 0,
260  'details' => str_replace('%', '%%', $logMessage),
261  'log_data' => empty($data) ? '' : json_encode($data),
262  'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
263  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
264  'workspace' => $workspace,
265  ]
266  );
267  }
268  }
269 
270  protected function ‪getFormattedLogMessage(string $message): string
271  {
272  // String 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
273  $applicationType = (‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
274  && ‪ApplicationType::fromRequest(‪$GLOBALS['TYPO3_REQUEST'])->isFrontend() ? 'FE' : 'BE';
275  $logPrefix = 'Core: Error handler (' . $applicationType . ')';
276  return $logPrefix . ': ' . $message;
277  }
278 
279  protected function ‪getTimeTracker(): ‪TimeTracker
280  {
281  return GeneralUtility::makeInstance(TimeTracker::class);
282  }
283 
285  {
286  return ‪$GLOBALS['BE_USER'] ?? null;
287  }
288 }
‪TYPO3\CMS\Core\SysLog\Action
Definition: Cache.php:18
‪TYPO3\CMS\Core\Error\ErrorHandler\$debugMode
‪bool $debugMode
Definition: ErrorHandler.php:57
‪TYPO3\CMS\Core\Error\ErrorHandler\getBackendUser
‪getBackendUser()
Definition: ErrorHandler.php:284
‪TYPO3\CMS\Core\Error\ErrorHandler\$errorHandlerErrors
‪int $errorHandlerErrors
Definition: ErrorHandler.php:52
‪TYPO3\CMS\Core\Error\ErrorHandlerInterface\PROPAGATE_ERROR
‪const PROPAGATE_ERROR
Definition: ErrorHandlerInterface.php:27
‪TYPO3\CMS\Core\Error\ErrorHandler\$exceptionalErrors
‪int $exceptionalErrors
Definition: ErrorHandler.php:47
‪TYPO3\CMS\Core\Error\ErrorHandler\setExceptionalErrors
‪setExceptionalErrors($exceptionalErrors)
Definition: ErrorHandler.php:88
‪TYPO3\CMS\Core\Error\ErrorHandler\createAndEnqueueFlashMessage
‪createAndEnqueueFlashMessage(string $message, int $errorLevel)
Definition: ErrorHandler.php:191
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Core\Error\ErrorHandlerInterface
Definition: ErrorHandlerInterface.php:24
‪TYPO3\CMS\Core\Error\ErrorHandler\__construct
‪__construct($errorHandlerErrors)
Definition: ErrorHandler.php:76
‪TYPO3\CMS\Core\Error\ErrorHandler\setDebugMode
‪setDebugMode($debugMode)
Definition: ErrorHandler.php:99
‪TYPO3\CMS\Core\Error\ErrorHandlerInterface\ERROR_HANDLED
‪const ERROR_HANDLED
Definition: ErrorHandlerInterface.php:26
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Error\ErrorHandler\registerErrorHandler
‪registerErrorHandler()
Definition: ErrorHandler.php:104
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Error\ErrorHandler\handleError
‪bool handleError($errorLevel, $errorMessage, $errorFile, $errorLine)
Definition: ErrorHandler.php:123
‪TYPO3\CMS\Core\Error\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Log\LogManager
Definition: LogManager.php:33
‪TYPO3\CMS\Core\Error\ErrorHandler\getFormattedLogMessage
‪getFormattedLogMessage(string $message)
Definition: ErrorHandler.php:270
‪TYPO3\CMS\Core\Error
Definition: AbstractExceptionHandler.php:16
‪TYPO3\CMS\Core\Error\ErrorHandler\writeLog
‪writeLog($logMessage, string $logLevel)
Definition: ErrorHandler.php:218
‪TYPO3\CMS\Core\Http\fromRequest
‪@ fromRequest
Definition: ApplicationType.php:66
‪TYPO3\CMS\Core\Error\ErrorHandler\getTimeTracker
‪getTimeTracker()
Definition: ErrorHandler.php:279
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\TimeTracker\TimeTracker
Definition: TimeTracker.php:34
‪TYPO3\CMS\Core\Error\ErrorHandler\ERROR_LEVEL_LABELS
‪const ERROR_LEVEL_LABELS
Definition: ErrorHandler.php:59
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Core\Http\ApplicationType
‪ApplicationType
Definition: ApplicationType.php:55
‪TYPO3\CMS\Core\Error\ErrorHandler
Definition: ErrorHandler.php:41