0, 'DEBUG' => 1, 'INFO' => 2, 'WARNING' => 3, 'ERROR' => 4, 'FATAL' => 5, ); // Custom fatal error handler $_fatal_error_handler = null; /** * Log a message * @param string $level The message level (key of $_log_levels) * @param string $message The message to log * @return true */ function logging($level, $message) { global $log_file, $_log_file_fd, $_log_levels, $log_level, $argv, $_fatal_error_handler, $auth_user; if (!array_key_exists($level, $_log_levels)) $level = 'INFO'; $level_id = $_log_levels[$level]; $log_level_id = $_log_levels[$log_level]; if ($level_id < $log_level_id) return true; if(is_null($_log_file_fd)) { $_log_file_fd = fopen($log_file, 'a'); } // If more than 2 arguments passed, format message using sprintf if (func_num_args() > 2) { $message = call_user_func_array( 'sprintf', array_merge(array($message), array_slice(func_get_args(), 2)) ); } if (php_sapi_name() == "cli") { $msg = implode(' - ', array( date('Y/m/d H:i:s'), basename($argv[0]), $level, $message ))."\n"; } else { $msg = array( date('Y/m/d H:i:s'), $_SERVER['REQUEST_URI'], $_SERVER['REMOTE_ADDR'], ); if (isset($auth_user)) $msg[] = ($auth_user['username']?$auth_user['username']:'anonymous'); $msg[] = $level; $msg[] = $message; $msg = implode(' - ', $msg)."\n"; } fwrite($_log_file_fd, $msg); if ($level == 'FATAL') if (!is_null($_fatal_error_handler)) call_user_func($_fatal_error_handler, $message); elseif (function_exists('fatal_error')) fatal_error($message); else die("\n$message\n\n"); elseif (php_sapi_name() == "cli") echo $msg; return true; } // Set default log level (if not defined or invalid) $default_log_level = 'WARNING'; if (!isset($log_level)) { $log_level = $default_log_level; } elseif (!array_key_exists($log_level, $_log_levels)) { $invalid_value = $log_level; $log_level = $default_log_level; logging( $log_level, "Invalid log level value found in configuration (%s). ". "Set as default (%s).", $invalid_value, $log_level); } /** * Register a contextual fatal error handler * @param null|callable $handler The fatal error handler (set as null to reset) * @return void */ function register_fatal_error_handler($handler) { // @phpstan-ignore-next-line if ($handler && !is_callable($handler)) logging('FATAL', 'Fatal handler provided is not callable !'); global $_fatal_error_handler; $_fatal_error_handler = ($handler?$handler:null); } /** * Change of current log file * @param string $file The new log file path * @return bool */ function change_log_file($file) { global $log_file, $_log_file_fd; if ($file == $log_file) return True; if ($_log_file_fd) { fclose($_log_file_fd); $_log_file_fd = false; } $log_file = $file; return True; } /* ******************************************************************************* * Handle exception logging ******************************************************************************* */ /** * Get the current backtrace * @param int $ignore_last The number of last levels to ignore * @return string */ function get_debug_backtrace_context($ignore_last=0) { $traces = debug_backtrace(); // Also ignore this function it self $ignore_last++; if (!is_array($traces) || count($traces) <= $ignore_last) return ""; $msg = array(); for ($i=$ignore_last; $i < count($traces); $i++) { $trace = array("#$i"); if (isset($traces[$i]['file'])) $trace[] = $traces[$i]['file'].(isset($traces[$i]['line'])?":".$traces[$i]['line']:""); // @phpstan-ignore-next-line if (isset($traces[$i]['class']) && isset($traces[$i]['function'])) $trace[] = implode(" ", array( $traces[$i]['class'], $traces[$i]['type'], $traces[$i]['function']. "()")); elseif (isset($traces[$i]['function'])) $trace[] = $traces[$i]['function']. "()"; $msg[] = implode(" - ", $trace); } return implode("\n", $msg); } /** * Log an exception * @param Throwable $exception * @param string|null $prefix The prefix of the log message * (optional, default: "An exception occured") * @return void */ function log_exception($exception, $prefix=null) { if (function_exists('log_in_sentry')) log_in_sentry($exception); // If more than 2 arguments passed, format prefix message using sprintf if ($prefix && func_num_args() > 2) { $prefix = call_user_func_array( 'sprintf', array_merge(array($prefix), array_slice(func_get_args(), 2)) ); } logging( "ERROR", "%s:\n%s\n## %s:%d : %s", ($prefix?$prefix:"An exception occured"), get_debug_backtrace_context(1), $exception->getFile(), $exception->getLine(), $exception->getMessage()); } set_exception_handler('log_exception'); /* ******************************************************************************* * Handle PHP error logging ******************************************************************************* */ /** * Convert PHP error number to the corresponding label * @param int $errno * @return string */ function errno2type($errno) { $constants = get_defined_constants(); if (is_array($constants)) foreach($constants as $label => $value) if ($value == $errno && preg_match('/^E_(.*)$/', $label, $m)) return $m[1]; return 'UNKNOWN ERROR #'.$errno; } /** * Log a PHP error * Note: method design to be used as callable by set_error_handler() * @param int $errno The error number * @param string $errstr The error message * @param string $errfile The filename that the error was raised in * @param int $errline The line number where the error was raised * @return false Return false to let the normal error handler continues. */ function log_php_error($errno, $errstr, $errfile, $errline) { $msg = sprintf( "A PHP error occured : [%s] %s\nFile : %s (line : %d)", errno2type($errno), $errstr, $errfile, $errline ); logging("ERROR", $msg); if (function_exists('log_php_error_in_sentry')) log_php_error_in_sentry($errno, $msg); return False; } if (isset($log_php_errors_levels)) set_error_handler('log_php_error', $log_php_errors_levels); elseif (in_array($log_level, array('DEBUG', 'TRACE'))) set_error_handler('log_php_error', E_ALL & ~E_STRICT); else set_error_handler('log_php_error', E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); # vim: tabstop=2 shiftwidth=2 softtabstop=2 expandtab