Skip to content

Instantly share code, notes, and snippets.

@shaunpalmer
Created September 11, 2024 07:08
Show Gist options
  • Save shaunpalmer/b5cef2e784fa570905467c11a284980c to your computer and use it in GitHub Desktop.
Save shaunpalmer/b5cef2e784fa570905467c11a284980c to your computer and use it in GitHub Desktop.
<?php
/**
* Configuration class to manage settings
*/
class WP_Error_Handler_Config {
private $config;
public function __construct(array $config = []) {
$this->config = array_merge([
'max_views' => 15,
'error_timeout' => 300,
'log_file' => WP_CONTENT_DIR . '/debug.log',
'log_rotation_size' => 5 * 1024 * 1024, // 5MB
], $config);
}
public function get($key) {
return $this->config[$key] ?? null;
}
}
/**
* Logging class for error logging
*/
class WP_Error_Logger {
private $config;
public function __construct(WP_Error_Handler_Config $config) {
$this->config = $config;
}
public function log_error($error) {
$backtrace = function_exists('wp_debug_backtrace_summary') ? wp_debug_backtrace_summary() : 'Backtrace not available';
$log_message = sprintf(
"[%s] %s: %s in %s on line %d\nBacktrace: %s",
$error['timestamp'],
$this->get_error_type_string($error['type']),
$error['message'],
$error['file'],
$error['line'],
$backtrace
);
$this->write_to_log($log_message);
}
private function write_to_log($message) {
$log_file = $this->config->get('log_file');
if (file_exists($log_file) && filesize($log_file) > $this->config->get('log_rotation_size')) {
rename($log_file, $log_file . '.' . date('Y-m-d-H-i-s'));
}
error_log($message . PHP_EOL, 3, $log_file);
}
private function get_error_type_string($type) {
$error_types = [
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
];
return isset($error_types[$type]) ? $error_types[$type] : 'UNKNOWN';
}
}
/**
* AdminNotices class for handling admin notices
*/
class WP_Error_Admin_Notices {
private $notices = [];
private $config;
public function __construct(WP_Error_Handler_Config $config) {
$this->config = $config;
add_action('admin_notices', [$this, 'display_admin_notices']);
}
public function queue_admin_notice($error, $type = 'warning') {
$notice = sprintf(
'<div class="notice notice-%s is-dismissible"><p>[%s] Error: %s in file %s on line %d</p></div>',
esc_attr($type),
esc_html($error['timestamp']),
esc_html($error['message']),
esc_html($error['file']),
esc_html($error['line'])
);
$this->notices[] = $notice;
}
public function display_admin_notices() {
foreach ($this->notices as $notice) {
echo $notice;
}
}
}
/**
* Main ErrorManagement class
*/
class WP_Error_Handler {
private static $instance = null;
private $config;
private $logger;
private $admin_notices;
private $error_history = [];
private $error_views = [];
private function __construct(WP_Error_Handler_Config $config) {
$this->config = $config;
$this->logger = new WP_Error_Logger($config);
$this->admin_notices = new WP_Error_Admin_Notices($config);
$this->set_error_reporting();
}
public static function get_instance(WP_Error_Handler_Config $config = null) {
if (self::$instance === null) {
self::$instance = new self($config ?? new WP_Error_Handler_Config());
}
return self::$instance;
}
private function set_error_reporting() {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
}
public function handle_error($errno, $errstr, $errfile, $errline) {
$error = [
'type' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
'timestamp' => $this->get_current_timestamp()
];
$error_key = $this->generate_error_key($error);
if (!isset($this->error_views[$error_key])) {
$this->error_views[$error_key] = 0;
}
$this->error_views[$error_key]++;
if ($this->error_views[$error_key] <= $this->config->get('max_views') && !$this->is_duplicate_error($error_key)) {
$this->logger->log_error($error);
if ($this->is_resolving($error)) {
$this->admin_notices->queue_admin_notice($error);
} else {
$this->not_resolving($error);
}
$this->error_history[$error_key] = time();
}
return true;
}
private function is_duplicate_error($error_key) {
if (isset($this->error_history[$error_key])) {
if (time() - $this->error_history[$error_key] < $this->config->get('error_timeout')) {
return true;
}
}
return false;
}
private function generate_error_key($error) {
return md5($error['type'] . $error['message'] . $error['file'] . $error['line']);
}
private function get_current_timestamp() {
return date('Y-m-d H:i:s');
}
private function is_resolving($error) {
return !in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR]);
}
private function not_resolving($error) {
$this->admin_notices->queue_admin_notice($error, 'error');
$this->trigger_recovery_mode();
}
private function trigger_recovery_mode() {
if (function_exists('wp_fatal_error_handler_enabled') && wp_fatal_error_handler_enabled()) {
wp_die(
__('There has been a critical error on this website. Please check the site admin email inbox for instructions.'),
__('WordPress Fatal Error'),
['response' => 500]
);
}
}
}
/* Usage example
$config = new WP_Error_Handler_Config([
'max_views' => 20,
'error_timeout' => 600,
]);
$error_handler = WP_Error_Handler::get_instance($config);
set_error_handler([$error_handler, 'handle_error']);
function divide_numbers($a, $b) {
if ($b == 0) {
throw new Exception("Division by zero!");
}
return $a / $b;
}
function some_functionality() {
try {
$result = divide_numbers(10, 0);
echo "Result: " . $result;
} catch (Exception $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
}
}
/# Call the function to demonstrate error handling
some_functionality();
?>
/* Usage example remains the same as before
function divide_numbers($a, $b) {
if ($b == 0) {
throw new Exception("Division by zero!");
}
return $a / $b;
}
function some_functionality() {
$error_handler = WP_Error_Handler::get_instance();
set_error_handler([$error_handler, 'handle_error']);
try {
$result = divide_numbers(10, 0);
echo "Result: " . $result;
} catch (Exception $e) {
trigger_error($e->getMessage(), E_USER_ERROR);
} finally {
restore_error_handler();
}
}
# Call the function to demonstrate error handling
some_functionality();
*/
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment