2021-06-29 23:10:12 -02:00
< ? php
2024-12-20 17:27:13 +01:00
namespace Vichan ;
2016-09-01 20:45:52 +01:00
2024-12-20 17:27:13 +01:00
class ErrorHandler {
2024-12-23 17:55:27 +01:00
private static array | false $error_recursion = false ;
2024-12-20 17:27:13 +01:00
private bool $include_details ;
/**
* @ var callable
*/
private mixed $logger_fn ;
2024-12-23 17:55:27 +01:00
private static function isCli () : bool {
return defined ( 'STDIN' ) || \PHP_SAPI === 'cli' || ( \stristr ( \PHP_SAPI , 'cgi' ) && \getenv ( 'TERM' ))
|| ( empty ( $_SERVER [ 'REMOTE_ADDR' ]) && ! isset ( $_SERVER [ 'HTTP_USER_AGENT' ]) && \count ( $_SERVER [ 'argv' ]) > 0 );
}
2024-12-20 17:27:13 +01:00
private static function errnoToLogLevel ( int $errno ) : int {
switch ( $errno ) {
default :
case \E_ERROR :
case \E_PARSE :
case \E_CORE_ERROR :
case \E_COMPILE_ERROR :
case \E_RECOVERABLE_ERROR :
2024-12-23 17:55:27 +01:00
return \LOG_EMERG ;
2024-12-20 17:27:13 +01:00
case \E_USER_ERROR :
2024-12-23 17:55:27 +01:00
return \LOG_ERR ;
2024-12-20 17:27:13 +01:00
case \E_WARNING :
case \E_CORE_WARNING :
case \E_COMPILE_WARNING :
case \E_USER_WARNING :
return \LOG_WARNING ;
case \E_NOTICE :
case \E_DEPRECATED :
case \E_USER_NOTICE :
case \E_USER_DEPRECATED :
return \LOG_NOTICE ;
}
}
public static function handle_error (
int $level ,
string $message ,
callable $logger ,
bool $display_details ,
2024-12-23 17:55:27 +01:00
string $file ,
int $line ,
string $trace_str ,
array $trace
2024-12-20 17:27:13 +01:00
) : void {
2024-12-23 17:55:27 +01:00
global $board , $mod , $config , $db_error ;
$newline = self :: isCli () ? '\n' : '<br>' ;
if ( self :: $error_recursion !== false ) {
list ( $o_message , $o_file , $o_line , $o_trace ) = self :: $error_recursion ;
if ( $display_details ) {
echo ( " Error recursion detected with $message at $file : $line . Backtrace: $newline $trace_str { $newline } Original error: $o_message at $o_file : $o_line . Original backtrace: $newline $o_trace " );
} else {
echo ( " Error recursion detected with $message { $newline } Original error: $o_message " );
}
exit ;
}
self :: $error_recursion = [ $message , $file , $line , $trace_str ];
// Message for the system, be it cli or error logging facility.
$sys_msg = " Error: $message at $file : $line . \n Backtrace: $trace_str\n " ;
if ( self :: isCli ()) {
// Running from CLI. Always display the details.
echo ( $sys_msg );
exit ;
}
// Log the error if we aren't running from cli.
$logger ( $level , $sys_msg );
// Recycled code.
$debug_stuff = [];
if ( $config [ 'debug' ]) {
if ( isset ( $db_error )) {
$debug_stuff = \array_combine ([ 'SQLSTATE' , 'Error code' , 'Error message' ], $db_error );
}
$debug_stuff [ 'backtrace' ] = $trace ;
2024-12-20 17:27:13 +01:00
2024-12-23 17:55:27 +01:00
// Remove the password from SQL errors.
$pw = $config [ 'db' ][ 'password' ];
$debug_callback = function ( $item ) use ( & $debug_callback , $pw ) {
if ( \is_array ( $item )) {
$item = \array_filter ( $item , $debug_callback );
}
return $item !== $pw || ! $pw ;
};
$debug_stuff = \array_map ( $debug_callback , $debug_stuff );
}
\header ( " { $_SERVER [ 'SERVER_PROTOCOL' ] } 500 Internal Server Error " );
// Error via json.
if ( isset ( $_POST [ 'json_response' ])) {
\header ( 'Content-Type: text/json; charset=utf-8' );
$data = [ 'error' => $message ];
if ( ! empty ( $config ) && $config [ 'debug' ] && ! empty ( $debug_stuff )) {
$data [ 'debug' ] = $debug_stuff ;
}
echo ( \json_encode ( $data ));
exit ;
}
// Pretty print.
die ( Element ( 'page.html' , [
'config' => $config ,
'title' => _ ( 'Error' ),
'subtitle' => _ ( 'An error has occured.' ),
'body' => Element ( 'error.html' , [
'config' => $config ,
'message' => $message ,
'mod' => $mod ,
'board' => isset ( $board ) ? $board : false ,
'debug' => \str_replace ( " \n " , ' ' , utf8tohtml ( print_r ( $debug_stuff , true )))
])
]));
2024-12-20 17:27:13 +01:00
}
private function default_logger ( int $level , string $message ) {
global $config ;
if ( isset ( $config [ 'syslog' ]) && $config [ 'syslog' ]) {
_syslog ( $level , $message );
} else {
\error_log ( $message );
}
}
2024-12-23 17:55:27 +01:00
public function __construct () {
$this -> include_details = self :: isCli ();
$this -> logger_fn = [ __CLASS__ , 'default_logger' ];
}
2024-12-20 17:27:13 +01:00
public function setIncludeDetails ( bool $include_details ) {
$this -> include_details = $include_details ;
}
public function setLogger ( ? callable $logger_fn ) {
$this -> logger_fn = $logger_fn !== null ? $logger_fn : [ __CLASS__ , 'default_logger' ];
}
public function installGlobally () {
$logger_fn = $this -> logger_fn ;
2024-12-23 17:55:27 +01:00
$include_details = $this -> include_details || self :: isCli ();
2024-12-20 17:27:13 +01:00
\set_error_handler ( function ( $errno , $errstr , $errfile , $errline ) use ( $logger_fn , $include_details ) {
$errno = \error_reporting () & $errno ;
if ( $errno !== 0 ) {
$level = self :: errnoToLogLevel ( $errno );
// https://stackoverflow.com/a/35930682
2024-12-23 17:55:27 +01:00
$e = new \Exception ();
$trace_str = $e -> getTraceAsString ();
$trace = $e -> getTrace ();
self :: handle_error ( $level , $errstr , $logger_fn , $include_details , $errfile , $errline , $trace_str , $trace );
2024-12-20 17:27:13 +01:00
}
return false ;
});
\set_exception_handler ( function ( $exception ) use ( $logger_fn , $include_details ) {
$message = $exception -> getMessage ();
$file = $exception -> getFile ();
$line = $exception -> getLine ();
2024-12-23 17:55:27 +01:00
$trace_str = $exception -> getTraceAsString ();
$trace = $exception -> getTrace ();
2024-12-20 17:27:13 +01:00
2024-12-23 17:55:27 +01:00
self :: handle_error ( \LOG_EMERG , $message , $logger_fn , $include_details , $file , $line , $trace_str , $trace );
2024-12-20 17:27:13 +01:00
});
\register_shutdown_function ( function () use ( $logger_fn , $include_details ) {
$error = \error_get_last ();
if ( $error !== null ) {
$level = self :: errnoToLogLevel ( $error [ 'type' ]);
$message = $error [ 'message' ];
$file = $error [ 'file' ];
$line = $error [ 'line' ];
2024-12-23 17:55:27 +01:00
self :: handle_error ( $level , $message , $logger_fn , $include_details , $file , $line , '<UNAVAILABLE>' , []);
2024-12-20 17:27:13 +01:00
}
});
}
}
/**
* Install a fancy error handler with the given logger .
*
* @ param ? callable $logger Called when there is to log something . Use null to use the minimal default implementation .
* The callable is invoke with two parameters , the first being the log level ( integer ), as
* listed by the syslog constants , the second is the log message ( string ) .
* Consider that $config may not have been initialized yet .
*/
function install_error_handler ( bool $basic , ? callable $logger_fn ) : void {
global $config ;
$logger_fn ? ? = function ( $level , $message ) use ( $config ) {
if ( isset ( $config [ 'syslog' ]) && $config [ 'syslog' ]) {
_syslog ( $level , $message );
} else {
\error_log ( $message );
}
};
if ( ! $basic ) {
\set_error_handler ( function ( $errno , $errstr , $errfile , $errline ) use ( $config ) {
if ( \error_reporting () & $errno ) {
$config [ 'debug' ] = true ;
\error ( " $errstr in $errfile at line $errline " );
}
return false ;
});
2016-09-01 20:45:52 +01:00
}
return false ;
}
2024-06-23 22:35:34 +02:00
function exception_handler ( $e ) {
2024-07-13 11:20:22 +02:00
error_log ( $e );
2016-09-01 20:45:52 +01:00
error ( $e -> getMessage ());
}
set_exception_handler ( 'exception_handler' );
2024-06-23 22:35:34 +02:00
function fatal_error_handler () {
2016-09-01 20:45:52 +01:00
if (( $error = error_get_last ()) && $error [ 'type' ] == E_ERROR ) {
2024-06-23 22:35:34 +02:00
error ( " Caught fatal error: { $error [ 'message' ] } in { $error [ 'file' ] } at line { $error [ 'line' ] } " );
2016-09-01 20:45:52 +01:00
}
}
register_shutdown_function ( 'fatal_error_handler' );
2021-06-29 23:10:12 -02:00
// Due to composer autoload, this isn't implicitly global anymore
global $error_recursion ;
2024-06-23 22:35:34 +02:00
$error_recursion = false ;
2016-09-01 20:45:52 +01:00
function error ( $message , $priority = true , $debug_stuff = false ) {
global $board , $mod , $config , $db_error , $error_recursion ;
2024-06-23 14:21:10 +02:00
2024-06-23 22:35:34 +02:00
if ( $error_recursion !== false ) {
die ( " Error recursion detected with $message <br>Original error: $error_recursion " );
2016-09-01 20:45:52 +01:00
}
2024-06-23 14:21:10 +02:00
2024-06-23 22:35:34 +02:00
$error_recursion = $message ;
2024-06-23 14:21:10 +02:00
2016-09-01 20:45:52 +01:00
if ( defined ( 'STDIN' )) {
// Running from CLI
2024-06-23 22:35:34 +02:00
echo ( " Error: $message\n " );
2016-09-01 20:45:52 +01:00
debug_print_backtrace ( DEBUG_BACKTRACE_IGNORE_ARGS );
die ();
}
2024-06-23 14:21:10 +02:00
2024-06-23 22:35:34 +02:00
if ( ! empty ( $config )) {
2016-09-01 20:45:52 +01:00
if ( $config [ 'syslog' ] && $priority !== false ) {
// Use LOG_NOTICE instead of LOG_ERR or LOG_WARNING because most error message are not significant.
_syslog ( $priority !== true ? $priority : LOG_NOTICE , $message );
}
if ( $config [ 'debug' ]) {
2024-06-23 22:35:34 +02:00
$debug_stuff = [];
if ( isset ( $db_error )) {
$debug_stuff = array_combine ([ 'SQLSTATE' , 'Error code' , 'Error message' ], $db_error );
2016-09-01 20:45:52 +01:00
}
$debug_stuff [ 'backtrace' ] = debug_backtrace ();
$pw = $config [ 'db' ][ 'password' ];
2024-06-23 15:19:51 +02:00
$debug_callback = function ( $item ) use ( & $debug_callback , $pw ) {
2016-09-01 20:45:52 +01:00
if ( is_array ( $item )) {
2024-06-23 15:19:51 +02:00
$item = array_filter ( $item , $debug_callback );
2016-09-01 20:45:52 +01:00
}
2024-06-23 22:35:34 +02:00
return $item !== $pw || ! $pw ;
2016-09-01 20:45:52 +01:00
};
$debug_stuff = array_map ( $debug_callback , $debug_stuff );
}
}
if ( isset ( $_POST [ 'json_response' ])) {
header ( 'Content-Type: text/json; charset=utf-8' );
2024-06-23 22:35:34 +02:00
$data = [ 'error' => $message ];
if ( ! empty ( $config ) && $config [ 'debug' ]) {
$data [ 'debug' ] = $debug_stuff ;
2016-09-01 20:45:52 +01:00
}
print json_encode ( $data );
exit ();
}
2024-06-23 22:35:34 +02:00
header ( " { $_SERVER [ 'SERVER_PROTOCOL' ] } 500 Internal Server Error " );
2024-06-23 14:21:10 +02:00
2024-06-23 22:35:34 +02:00
die ( Element ( 'page.html' , [
2016-09-01 20:45:52 +01:00
'config' => $config ,
'title' => _ ( 'Error' ),
'subtitle' => _ ( 'An error has occured.' ),
'body' => Element ( 'error.html' , array (
'config' => $config ,
'message' => $message ,
'mod' => $mod ,
'board' => isset ( $board ) ? $board : false ,
2024-12-23 17:55:27 +01:00
'debug' => \str_replace ( " \n " , ' ' , utf8tohtml ( \print_r ( $debug_stuff , true )))
2016-09-01 20:45:52 +01:00
))
2024-06-23 22:35:34 +02:00
]));
2016-09-01 20:45:52 +01:00
}