1<?php 2 3/* 4 * This file is part of the Monolog package. 5 * 6 * (c) Jordi Boggiano <j.boggiano@seld.be> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Monolog; 13 14use Psr\Log\LoggerInterface; 15use Psr\Log\LogLevel; 16use Monolog\Handler\AbstractHandler; 17 18/** 19 * Monolog error handler 20 * 21 * A facility to enable logging of runtime errors, exceptions and fatal errors. 22 * 23 * Quick setup: <code>ErrorHandler::register($logger);</code> 24 * 25 * @author Jordi Boggiano <j.boggiano@seld.be> 26 */ 27class ErrorHandler 28{ 29 private $logger; 30 31 private $previousExceptionHandler; 32 private $uncaughtExceptionLevel; 33 34 private $previousErrorHandler; 35 private $errorLevelMap; 36 private $handleOnlyReportedErrors; 37 38 private $hasFatalErrorHandler; 39 private $fatalLevel; 40 private $reservedMemory; 41 private $lastFatalTrace; 42 private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); 43 44 public function __construct(LoggerInterface $logger) 45 { 46 $this->logger = $logger; 47 } 48 49 /** 50 * Registers a new ErrorHandler for a given Logger 51 * 52 * By default it will handle errors, exceptions and fatal errors 53 * 54 * @param LoggerInterface $logger 55 * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling 56 * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling 57 * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling 58 * @return ErrorHandler 59 */ 60 public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) 61 { 62 //Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929 63 class_exists('\\Psr\\Log\\LogLevel', true); 64 65 /** @phpstan-ignore-next-line */ 66 $handler = new static($logger); 67 if ($errorLevelMap !== false) { 68 $handler->registerErrorHandler($errorLevelMap); 69 } 70 if ($exceptionLevel !== false) { 71 $handler->registerExceptionHandler($exceptionLevel); 72 } 73 if ($fatalLevel !== false) { 74 $handler->registerFatalHandler($fatalLevel); 75 } 76 77 return $handler; 78 } 79 80 public function registerExceptionHandler($level = null, $callPrevious = true) 81 { 82 $prev = set_exception_handler(array($this, 'handleException')); 83 $this->uncaughtExceptionLevel = $level; 84 if ($callPrevious && $prev) { 85 $this->previousExceptionHandler = $prev; 86 } 87 } 88 89 public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true) 90 { 91 $prev = set_error_handler(array($this, 'handleError'), $errorTypes); 92 $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); 93 if ($callPrevious) { 94 $this->previousErrorHandler = $prev ?: true; 95 } 96 97 $this->handleOnlyReportedErrors = $handleOnlyReportedErrors; 98 } 99 100 public function registerFatalHandler($level = null, $reservedMemorySize = 20) 101 { 102 register_shutdown_function(array($this, 'handleFatalError')); 103 104 $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); 105 $this->fatalLevel = $level; 106 $this->hasFatalErrorHandler = true; 107 } 108 109 protected function defaultErrorLevelMap() 110 { 111 return array( 112 E_ERROR => LogLevel::CRITICAL, 113 E_WARNING => LogLevel::WARNING, 114 E_PARSE => LogLevel::ALERT, 115 E_NOTICE => LogLevel::NOTICE, 116 E_CORE_ERROR => LogLevel::CRITICAL, 117 E_CORE_WARNING => LogLevel::WARNING, 118 E_COMPILE_ERROR => LogLevel::ALERT, 119 E_COMPILE_WARNING => LogLevel::WARNING, 120 E_USER_ERROR => LogLevel::ERROR, 121 E_USER_WARNING => LogLevel::WARNING, 122 E_USER_NOTICE => LogLevel::NOTICE, 123 E_STRICT => LogLevel::NOTICE, 124 E_RECOVERABLE_ERROR => LogLevel::ERROR, 125 E_DEPRECATED => LogLevel::NOTICE, 126 E_USER_DEPRECATED => LogLevel::NOTICE, 127 ); 128 } 129 130 /** 131 * @private 132 */ 133 public function handleException($e) 134 { 135 $this->logger->log( 136 $this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel, 137 sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), 138 array('exception' => $e) 139 ); 140 141 if ($this->previousExceptionHandler) { 142 call_user_func($this->previousExceptionHandler, $e); 143 } 144 145 exit(255); 146 } 147 148 /** 149 * @private 150 */ 151 public function handleError($code, $message, $file = '', $line = 0, $context = array()) 152 { 153 if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) { 154 return; 155 } 156 157 // fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries 158 if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) { 159 $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; 160 $this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line)); 161 } else { 162 // http://php.net/manual/en/function.debug-backtrace.php 163 // As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added. 164 // Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'. 165 $trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS); 166 array_shift($trace); // Exclude handleError from trace 167 $this->lastFatalTrace = $trace; 168 } 169 170 if ($this->previousErrorHandler === true) { 171 return false; 172 } elseif ($this->previousErrorHandler) { 173 return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); 174 } 175 } 176 177 /** 178 * @private 179 */ 180 public function handleFatalError() 181 { 182 $this->reservedMemory = null; 183 184 $lastError = error_get_last(); 185 if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { 186 $this->logger->log( 187 $this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel, 188 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], 189 array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace) 190 ); 191 192 if ($this->logger instanceof Logger) { 193 foreach ($this->logger->getHandlers() as $handler) { 194 if ($handler instanceof AbstractHandler) { 195 $handler->close(); 196 } 197 } 198 } 199 } 200 } 201 202 private static function codeToString($code) 203 { 204 switch ($code) { 205 case E_ERROR: 206 return 'E_ERROR'; 207 case E_WARNING: 208 return 'E_WARNING'; 209 case E_PARSE: 210 return 'E_PARSE'; 211 case E_NOTICE: 212 return 'E_NOTICE'; 213 case E_CORE_ERROR: 214 return 'E_CORE_ERROR'; 215 case E_CORE_WARNING: 216 return 'E_CORE_WARNING'; 217 case E_COMPILE_ERROR: 218 return 'E_COMPILE_ERROR'; 219 case E_COMPILE_WARNING: 220 return 'E_COMPILE_WARNING'; 221 case E_USER_ERROR: 222 return 'E_USER_ERROR'; 223 case E_USER_WARNING: 224 return 'E_USER_WARNING'; 225 case E_USER_NOTICE: 226 return 'E_USER_NOTICE'; 227 case E_STRICT: 228 return 'E_STRICT'; 229 case E_RECOVERABLE_ERROR: 230 return 'E_RECOVERABLE_ERROR'; 231 case E_DEPRECATED: 232 return 'E_DEPRECATED'; 233 case E_USER_DEPRECATED: 234 return 'E_USER_DEPRECATED'; 235 } 236 237 return 'Unknown PHP error'; 238 } 239} 240