| | |
| | | * |
| | | * @return bool Whether it gets logged successfully |
| | | */ |
| | | public function log(string $component, string $message, array $context = [], string $severity = 'error'):bool |
| | | { |
| | | try { |
| | | // Normal queue-based logging |
| | | JVB()->queue()->queueOperation( |
| | | 'error_log', |
| | | get_current_user_id(), |
| | | [ |
| | | 'component' => $component, |
| | | 'message' => $message, |
| | | 'context' => $context, |
| | | 'severity' => $severity |
| | | ], |
| | | ['priority' => 'high'] |
| | | ); |
| | | public function log(string $component, string $message, array $context = [], string $severity = 'error'): array |
| | | { |
| | | try { |
| | | $table = $this->wpdb->prefix . BASE . 'error_log'; |
| | | |
| | | // Validate severity |
| | | if (!array_key_exists($severity, $this->error_levels)) { |
| | | $severity = 'error'; |
| | | } |
| | | |
| | | // Immediate notification for critical errors |
| | | if ($severity === 'critical') { |
| | | $this->notifyAdmin($component, $message, $context); |
| | | } |
| | | return true; |
| | | } catch (Exception $e) { |
| | | error_log("[edmonton.ink Error] Failed to log error: " . $e->getMessage()); |
| | | return false; |
| | | } |
| | | } |
| | | // Extract info |
| | | $error_type = sanitize_text_field($context['error_type'] ?? $component); |
| | | $method = isset($context['method']) ? sanitize_text_field($context['method']) : null; |
| | | $page_url = isset($context['url']) ? esc_url_raw($context['url']) : null; |
| | | $user_id = get_current_user_id(); |
| | | $user_was_logged_in = $user_id > 0 || (!empty($context['isLoggedIn'])); |
| | | |
| | | // Determine source from context |
| | | $source = isset($context['source']) ? $context['source'] : |
| | | (isset($context['url']) ? 'frontend' : 'backend'); |
| | | |
| | | $result = $this->wpdb->insert( |
| | | $table, |
| | | [ |
| | | 'error_type' => $error_type, |
| | | 'component' => $component, |
| | | 'method' => $method, |
| | | 'page_url' => $page_url, |
| | | 'message' => sanitize_textarea_field($message), |
| | | 'context' => json_encode($context), |
| | | 'severity' => $severity, |
| | | 'user_id' => $user_id ?: null, |
| | | 'user_was_logged_in' => $user_was_logged_in ? 1 : 0, |
| | | 'source' => $source, |
| | | 'created_at' => current_time('mysql') |
| | | ], |
| | | ['%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%s'] |
| | | ); |
| | | |
| | | if ($result === false) { |
| | | error_log("[ErrorHandler] Database insert failed: " . $this->wpdb->last_error); |
| | | return ['success' => false, 'message' => $this->wpdb->last_error]; |
| | | } |
| | | |
| | | if ($severity === 'critical') { |
| | | $this->checkErrorThreshold($error_type, $component); |
| | | } |
| | | |
| | | return ['success' => true, 'id' => $this->wpdb->insert_id]; |
| | | |
| | | } catch (Exception $e) { |
| | | error_log("[ErrorHandler Exception] " . $e->getMessage()); |
| | | return ['success' => false, 'message' => $e->getMessage()]; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @param string $component What class or function logs the error |
| | |
| | | $subject = "[edmonton.ink Critical Error] {$component}"; |
| | | $body = "Error: {$message}\n\nContext: " . print_r($context, true); |
| | | |
| | | return jvbMail($admin_email, $subject, $body); |
| | | return JVB()->email()->sendEmail($admin_email, $subject, $body); |
| | | } |
| | | |
| | | /** |
| | | * Gather summary of the most important errors |
| | | * @param ?string $start_date Defaults to today |
| | | * @param ?string $end_date Defaults to today |
| | | * @return array |
| | | */ |
| | | protected function gatherErrorSummary():array |
| | | { |
| | | $yesterday = date('Y-m-d H:i:s', strtotime('-24 hours')); |
| | | public function gatherErrorSummary(?string $start_date = null, ?string $end_date = null): array |
| | | { |
| | | $table = $this->wpdb->prefix . BASE . 'error_log'; |
| | | |
| | | // Get most frequent errors |
| | | $frequent_errors = $this->wpdb->get_results($this->wpdb->prepare( |
| | | "SELECT error_type, component, message, COUNT(*) as count |
| | | FROM {$this->tableName} |
| | | WHERE created_at > %s |
| | | GROUP BY error_type, component, message |
| | | ORDER BY count DESC |
| | | LIMIT 20", |
| | | $yesterday |
| | | )); |
| | | if (!$start_date) { |
| | | $start_date = gmdate('Y-m-d 00:00:00', strtotime('-1 day')); |
| | | } |
| | | if (!$end_date) { |
| | | $end_date = gmdate('Y-m-d 23:59:59'); |
| | | } |
| | | |
| | | // Get most recent critical errors |
| | | $critical_errors = $this->wpdb->get_results($this->wpdb->prepare( |
| | | "SELECT * FROM {$this->tableName} |
| | | WHERE severity = 'critical' AND created_at > %s |
| | | ORDER BY created_at DESC |
| | | LIMIT 5", |
| | | $yesterday |
| | | )); |
| | | // Most frequent error patterns (deduplicated by component/method/message) |
| | | $frequent = $this->wpdb->get_results($this->wpdb->prepare( |
| | | "SELECT |
| | | component, |
| | | method, |
| | | error_type, |
| | | message, |
| | | severity, |
| | | source, |
| | | COUNT(*) as count, |
| | | SUM(CASE WHEN user_was_logged_in = 1 THEN 1 ELSE 0 END) as logged_in_count, |
| | | SUM(CASE WHEN user_was_logged_in = 0 THEN 1 ELSE 0 END) as logged_out_count, |
| | | MIN(created_at) as first_seen, |
| | | MAX(created_at) as last_seen |
| | | FROM {$table} |
| | | WHERE created_at BETWEEN %s AND %s |
| | | GROUP BY component, method, error_type, message, severity, source |
| | | ORDER BY count DESC, severity DESC |
| | | LIMIT 10", |
| | | $start_date, |
| | | $end_date |
| | | )); |
| | | |
| | | return [ |
| | | 'frequent' => $frequent_errors, |
| | | 'critical' => $critical_errors |
| | | ]; |
| | | } |
| | | // Critical errors |
| | | $critical = $this->wpdb->get_results($this->wpdb->prepare( |
| | | "SELECT |
| | | component, |
| | | method, |
| | | error_type, |
| | | message, |
| | | source, |
| | | COUNT(*) as count, |
| | | SUM(CASE WHEN user_was_logged_in = 1 THEN 1 ELSE 0 END) as logged_in_count, |
| | | SUM(CASE WHEN user_was_logged_in = 0 THEN 1 ELSE 0 END) as logged_out_count, |
| | | MIN(created_at) as first_seen, |
| | | MAX(created_at) as last_seen |
| | | FROM {$table} |
| | | WHERE created_at BETWEEN %s AND %s AND severity = 'critical' |
| | | GROUP BY component, method, error_type, message, source |
| | | ORDER BY count DESC |
| | | LIMIT 5", |
| | | $start_date, |
| | | $end_date |
| | | )); |
| | | |
| | | // Overall stats |
| | | $stats = $this->wpdb->get_row($this->wpdb->prepare( |
| | | "SELECT |
| | | COUNT(*) as total_errors, |
| | | COUNT(DISTINCT CONCAT(component, '-', COALESCE(method, ''), '-', error_type)) as unique_error_types, |
| | | SUM(CASE WHEN user_was_logged_in = 1 THEN 1 ELSE 0 END) as logged_in_errors, |
| | | SUM(CASE WHEN user_was_logged_in = 0 THEN 1 ELSE 0 END) as logged_out_errors, |
| | | SUM(CASE WHEN source = 'frontend' THEN 1 ELSE 0 END) as frontend_errors, |
| | | SUM(CASE WHEN source = 'backend' THEN 1 ELSE 0 END) as backend_errors, |
| | | SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_count, |
| | | SUM(CASE WHEN severity = 'error' THEN 1 ELSE 0 END) as error_count, |
| | | SUM(CASE WHEN severity = 'warning' THEN 1 ELSE 0 END) as warning_count |
| | | FROM {$table} |
| | | WHERE created_at BETWEEN %s AND %s", |
| | | $start_date, |
| | | $end_date |
| | | )); |
| | | |
| | | return [ |
| | | 'frequent' => $frequent, |
| | | 'critical' => $critical, |
| | | 'stats' => $stats, |
| | | 'date_range' => ['start' => $start_date, 'end' => $end_date] |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * Send daily error summary email to administrator |
| | |
| | | $body .= "View detailed error logs in the dashboard: {$admin_url}\n\n"; |
| | | |
| | | // Send the email |
| | | $sent = jvbMail($admin_email, $subject, $body, 'ERROR SUMMARY'); |
| | | $sent = JVB()->email()->sendEmail($admin_email, $subject, $body, 'ERROR SUMMARY'); |
| | | |
| | | // Log that summary was sent |
| | | if ($sent) { |
| | |
| | | |
| | | } |
| | | |
| | | |
| | | |
| | | protected function buildParams(WP_REST_Request $request):array { |
| | | $allowedSeverity = [ |
| | | 'all', |