Source of file LoggerBridge.php
Size: 18,915 Bytes - Last Modified: 2021-12-24T06:35:34+00:00
/var/www/docs.ssmods.com/process/src/src/Camspiers/LoggerBridge/LoggerBridge.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 | <?php namespace Camspiers\LoggerBridge; use Camspiers\LoggerBridge\BacktraceReporter\BacktraceReporter; use Camspiers\LoggerBridge\BacktraceReporter\BasicBacktraceReporter; use Camspiers\LoggerBridge\EnvReporter\DirectorEnvReporter; use Camspiers\LoggerBridge\EnvReporter\EnvReporter; use Camspiers\LoggerBridge\ErrorReporter\DebugErrorReporter; use Camspiers\LoggerBridge\ErrorReporter\ErrorReporter; use Psr\Log\LoggerInterface; /** * Enables global SilverStripe logging with a PSR-3 logger like Monolog. * * The logger is attached by using a Request Processor filter. This behaviour is required * so the logger is attached after the environment only and except rules in yml are applied. * * @author Cam Spiers <camspiers@gmail.com> */ class LoggerBridge implements \RequestFilter { /** * @var \Psr\Log\LoggerInterface */ protected $logger; /** * @var \Camspiers\LoggerBridge\ErrorReporter\ErrorReporter */ protected $errorReporter; /** * @var \Camspiers\LoggerBridge\EnvReporter\EnvReporter */ protected $envReporter; /** * @var \Camspiers\LoggerBridge\BacktraceReporter\BacktraceReporter */ protected $backtraceReporter; /** * @var bool|null */ protected $registered; /** * @var bool */ protected $showErrors = true; /** * @var int */ protected $reserveMemory = 5242880; // 5M /** * @var int|null */ protected $reportLevel; /** * @var null|callable */ protected $errorHandler; /** * @var null|callable */ protected $exceptionHandler; /** * @var bool */ protected $reportBacktrace = false; /** * If an ErrorException should be included in the log context when handling errors * @var bool */ protected $exceptionInContext = true; /** * Defines the way error types are logged * @var */ protected $errorLogGroups = array( 'error' => array( E_ERROR, E_CORE_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR, E_RECOVERABLE_ERROR ), 'warning' => array( E_WARNING, E_CORE_WARNING, E_USER_WARNING, E_NOTICE, E_USER_NOTICE, E_DEPRECATED, E_USER_DEPRECATED, E_STRICT ) ); /** * Defines what errors should terminate */ protected $terminatingErrors = array( E_ERROR, E_CORE_ERROR, E_USER_ERROR, E_PARSE, E_COMPILE_ERROR, E_RECOVERABLE_ERROR ); /** * @param \Psr\Log\LoggerInterface $logger * @param bool $showErrors If false stops the display of SilverStripe errors * @param null $reserveMemory The amount of memory to reserve for out of memory errors * @param null|int $reportLevel Allow the specification of a reporting level */ public function __construct( LoggerInterface $logger, $showErrors = true, $reserveMemory = null, $reportLevel = null ) { $this->logger = $logger; $this->showErrors = (bool) $showErrors; if ($reserveMemory !== null) { $this->setReserveMemory($reserveMemory); } // If a specific reportLevel isn't set use error_reporting // It can be useful to set a reportLevel when you want to override SilverStripe live settings $this->reportLevel = $reportLevel !== null ? $reportLevel : error_reporting(); } /** * @param \Psr\Log\LoggerInterface $logger */ public function setLogger($logger) { $this->logger = $logger; } /** * @return \Psr\Log\LoggerInterface */ public function getLogger() { return $this->logger; } /** * @param \Camspiers\LoggerBridge\ErrorReporter\ErrorReporter $errorReporter */ public function setErrorReporter(ErrorReporter $errorReporter) { $this->errorReporter = $errorReporter; } /** * @return \Camspiers\LoggerBridge\ErrorReporter\ErrorReporter */ public function getErrorReporter() { $this->errorReporter = $this->errorReporter ? : new DebugErrorReporter($this->getEnvReporter()); return $this->errorReporter; } /** * @param \Camspiers\LoggerBridge\EnvReporter\EnvReporter $envReporter */ public function setEnvReporter(EnvReporter $envReporter) { $this->envReporter = $envReporter; } /** * @return \Camspiers\LoggerBridge\EnvReporter\EnvReporter */ public function getEnvReporter() { $this->envReporter = $this->envReporter ? : new DirectorEnvReporter(); return $this->envReporter; } /** * @param \Camspiers\LoggerBridge\BacktraceReporter\BacktraceReporter $backtraceReporter */ public function setBacktraceReporter(BacktraceReporter $backtraceReporter) { $this->backtraceReporter = $backtraceReporter; } /** * @return \Camspiers\LoggerBridge\BacktraceReporter\BacktraceReporter */ public function getBacktraceReporter() { $this->backtraceReporter = $this->backtraceReporter ? : new BasicBacktraceReporter(); return $this->backtraceReporter; } /** * @return boolean */ public function isRegistered() { return $this->registered; } /** * @param array $errorLogGroups */ public function setErrorLogGroups($errorLogGroups) { if (is_array($errorLogGroups)) { $this->errorLogGroups = $errorLogGroups; } } /** * @return mixed */ public function getErrorLogGroups() { return $this->errorLogGroups; } /** * @param boolean $showErrors */ public function setShowErrors($showErrors) { $this->showErrors = (bool) $showErrors; } /** * @return boolean */ public function isShowErrors() { return $this->showErrors; } /** * @param int|null $reportLevel */ public function setReportLevel($reportLevel) { $this->reportLevel = $reportLevel; } /** * @return int|null */ public function getReportLevel() { return $this->reportLevel; } /** * @param bool $reportBacktrace */ public function setReportBacktrace($reportBacktrace) { $this->reportBacktrace = $reportBacktrace; } /** * @param bool $exceptionInContext */ public function setExceptionInContext($exceptionInContext) { $this->exceptionInContext = $exceptionInContext; } /** * @param string|int $reserveMemory */ public function setReserveMemory($reserveMemory) { if (is_string($reserveMemory)) { $this->reserveMemory = $this->translateMemoryLimit($reserveMemory); } elseif (is_int($reserveMemory)) { $this->reserveMemory = $reserveMemory; } } /** * @return int|null */ public function getReserveMemory() { return $this->reserveMemory; } /** * This hook function is executed from RequestProcessor before the request starts * @param \SS_HTTPRequest $request * @param \Session $session * @param \DataModel $model * @return bool * @SuppressWarnings("unused") */ public function preRequest( \SS_HTTPRequest $request, \Session $session, \DataModel $model ) { $this->registerGlobalHandlers(); return true; } /** * This hook function is executed from RequestProcessor after the request ends * @param \SS_HTTPRequest $request * @param \SS_HTTPResponse $response * @param \DataModel $model * @return bool * @SuppressWarnings("unused") */ public function postRequest( \SS_HTTPRequest $request, \SS_HTTPResponse $response, \DataModel $model ) { $this->deregisterGlobalHandlers(); return true; } /** * Registers global error handlers */ public function registerGlobalHandlers() { if (!$this->registered) { // Store the previous error handler if there was any $this->registerErrorHandler(); // Store the previous exception handler if there was any $this->registerExceptionHandler(); // If the shutdown function hasn't been registered register it if ($this->registered === null) { $this->registerFatalErrorHandler(); // If suhosin is relevant then decrease the memory_limit by the reserveMemory amount // otherwise we should be able to increase the memory by our reserveMemory amount without worry if ($this->isSuhosinRelevant()) { $this->ensureSuhosinMemory(); } } $this->registered = true; } } /** * Removes handlers we have added, and restores others if possible */ public function deregisterGlobalHandlers() { if ($this->registered) { // Restore the previous error handler if available set_error_handler( is_callable($this->errorHandler) ? $this->errorHandler : function () { } ); // Restore the previous exception handler if available set_exception_handler( is_callable($this->exceptionHandler) ? $this->exceptionHandler : function () { } ); $this->registered = false; } } /** * Registers the error handler */ protected function registerErrorHandler() { $this->errorHandler = set_error_handler(array($this, 'errorHandler'), $this->reportLevel); } /** * Registers the exception handler */ protected function registerExceptionHandler() { $this->exceptionHandler = set_exception_handler(array($this, 'exceptionHandler')); } /** * Registers the fatal error handler */ protected function registerFatalErrorHandler() { register_shutdown_function(array($this, 'fatalHandler')); } /** * Handles general errors, user, warn and notice * @param $errno * @param $errstr * @param $errfile * @param $errline * @return bool|string|void */ public function errorHandler($errno, $errstr, $errfile, $errline) { // Honour error suppression through @ if (($errorReporting = error_reporting()) === 0) { return true; } $logType = null; foreach ($this->errorLogGroups as $candidateLogType => $errorTypes) { if (in_array($errno, $errorTypes)) { $logType = $candidateLogType; break; } } if (is_null($logType)) { throw new \Exception(sprintf( "No log type found for errno '%s'", $errno )); } $exception = $this->createException($errstr, $errno, $errfile, $errline); // Log all errors regardless of type $context = array( 'file' => $errfile, 'line' => $errline ); if ($this->reportBacktrace) { $context['backtrace'] = $this->getBacktraceReporter()->getBacktrace(); } if ($this->exceptionInContext) { $context['exception'] = $exception; } $this->logger->$logType($errstr, $context); // Check the error_reporting level in comparison with the $errno (honouring the environment) // And check that $showErrors is on or the site is live if (($errno & $errorReporting) === $errno && ($this->showErrors || $this->getEnvReporter()->isLive())) { $this->getErrorReporter()->reportError($exception); } if (in_array($errno, $this->terminatingErrors)) { $this->terminate(); } // ignore the usually handling of this type of error return true; } /** * Handles uncaught exceptions * @param mixed $exception * @return string|void */ public function exceptionHandler($exception) { $context = array( 'file' => $exception->getFile(), 'line' => $exception->getLine() ); if ($this->reportBacktrace) { $context['backtrace'] = $this->getBacktraceReporter()->getBacktrace($exception); } if ($this->exceptionInContext) { $context['exception'] = $exception; } $this->logger->error( $message = 'Uncaught ' . get_class($exception) . ': ' . $exception->getMessage(), $context ); // Exceptions must be reported in general because they stop the regular display of the page if ($this->showErrors || $this->getEnvReporter()->isLive()) { $this->getErrorReporter()->reportError($exception); } } /** * Handles fatal errors * If we are registered, and there is a fatal error then log and try to gracefully handle error output * In cases where memory is exhausted increase the memory_limit to allow for logging */ public function fatalHandler() { $error = $this->getLastError(); if ($this->isRegistered() && $this->isFatalError($error)) { if (defined('FRAMEWORK_PATH')) { chdir(FRAMEWORK_PATH); } if ($this->isMemoryExhaustedError($error)) { // We can safely change the memory limit be the reserve amount because if suhosin is relevant // the memory will have been decreased prior to exhaustion $this->changeMemoryLimit($this->reserveMemory); } $exception = $this->createException( $error['message'], $error['type'], $error['file'], $error['line'] ); $context = array( 'file' => $error['file'], 'line' => $error['line'] ); if ($this->reportBacktrace) { $context['backtrace'] = $this->getBacktraceReporter()->getBacktrace(); } if ($this->exceptionInContext) { $context['exception'] = $exception; } $this->logger->critical($error['message'], $context); // Fatal errors should be reported when live as they stop the display of regular output if ($this->showErrors || $this->getEnvReporter()->isLive()) { $this->getErrorReporter()->reportError($exception); } } } /** * @param $message * @param $code * @param $filename * @param $lineno * @return \ErrorException */ protected function createException($message, $code, $filename, $lineno) { return new \ErrorException( $message, $code, 0, $filename, $lineno ); } /** * Returns whether or not the last error was fatal * @param $error * @return bool */ protected function isFatalError($error) { return $error && in_array( $error['type'], array( E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR ) ); } /** * @return array */ protected function getLastError() { return error_get_last(); } /** * Formats objects and array for logging * @param $arr * @return mixed */ protected function format($arr) { return print_r($arr, true); } /** * Returns whether or not the passed in error is a memory exhausted error * @param $error array * @return bool */ protected function isMemoryExhaustedError($error) { return isset($error['message']) && stripos($error['message'], 'memory') !== false && stripos($error['message'], 'exhausted') !== false; } /** * Change memory_limit by specified amount * @param $amount */ protected function changeMemoryLimit($amount) { ini_set( 'memory_limit', $this->getMemoryLimit() + $amount ); } /** * Translate the memory limit string to a int in bytes. * @param $memoryLimit * @return int */ protected function translateMemoryLimit($memoryLimit) { $unit = strtolower(substr($memoryLimit, -1, 1)); $memoryLimit = (int) $memoryLimit; switch ($unit) { case 'g': $memoryLimit *= 1024; // intentional case 'm': $memoryLimit *= 1024; // intentional case 'k': $memoryLimit *= 1024; // intentional } return $memoryLimit; } /** * @return int */ protected function getMemoryLimit() { return $this->translateMemoryLimit(ini_get('memory_limit')); } /** * @return int */ protected function getSuhosinMemoryLimit() { return $this->translateMemoryLimit(ini_get('suhosin.memory_limit')); } /** * Checks if suhosin is enabled and the memory_limit is closer to suhosin.memory_limit than reserveMemory * It is in this case where it is relevant to decrease the memory available to the script before it uses all * available memory so when we need to increase the memory limit we can do so * @return bool */ protected function isSuhosinRelevant() { return extension_loaded('suhosin') && $this->getSuhosinMemoryDifference() < $this->reserveMemory; } /** * Returns how close the max memory limit is to the current memory limit * @return int */ protected function getSuhosinMemoryDifference() { return $this->getSuhosinMemoryLimit() - $this->getMemoryLimit(); } /** * Set the memory_limit so we have enough to handle errors when suhosin is relevant */ protected function ensureSuhosinMemory() { ini_set( 'memory_limit', $this->getSuhosinMemoryLimit() - $this->reserveMemory ); } /** * Provides ability to stub exits in unit tests */ protected function terminate() { exit(1); } } |