log_file = WP_CONTENT_DIR . '/sql-debug.log'; $this->request_id = uniqid('req_', true); $this->start_time = microtime(true); $this->init_log_file(); $this->init(); } private function init_log_file() { if (!is_writable(WP_CONTENT_DIR)) { $this->log_enabled = false; error_log('Директория wp-content недоступна для записи'); return; } if (!file_exists($this->log_file)) { if (@touch($this->log_file)) { @chmod($this->log_file, 0644); } } // Очищаем файл при каждой инициализации @file_put_contents($this->log_file, ''); } private function init() { if (!$this->log_enabled) return; // Трекинг местоположения в WordPress $this->setup_location_tracking(); // Перехватываем запрос ДО выполнения add_filter('query', [$this, 'log_query_before_execution'], 1, 1); // Логируем результат выполнения add_filter('query', [$this, 'log_query_after_execution'], 9999, 2); // Логируем ошибки запросов add_action('wpdb_error', [$this, 'log_query_error'], 10, 2); add_action('shutdown', [$this, 'log_pending_queries'], 9999); } /** * Настраиваем трекинг местоположения в WordPress */ private function setup_location_tracking() { // Основные хуки WordPress для определения местоположения $location_hooks = [ 'plugins_loaded' => 'PLUGINS_LOADED', 'setup_theme' => 'SETUP_THEME', 'after_setup_theme' => 'AFTER_SETUP_THEME', 'init' => 'INIT', 'wp_loaded' => 'WP_LOADED', 'parse_query' => 'PARSE_QUERY', 'pre_get_posts' => 'PRE_GET_POSTS', 'wp' => 'WP_MAIN_QUERY', 'template_redirect' => 'TEMPLATE_REDIRECT', 'get_header' => 'HEADER', 'wp_head' => 'WP_HEAD', 'loop_start' => 'LOOP_START', 'the_post' => 'THE_POST', 'loop_end' => 'LOOP_END', 'get_sidebar' => 'SIDEBAR', 'get_footer' => 'FOOTER', 'wp_footer' => 'WP_FOOTER', 'shutdown' => 'SHUTDOWN' ]; foreach ($location_hooks as $hook => $location) { add_action($hook, function() use ($location) { $this->current_location = $location; $this->log_message("📍 LOCATION: {$location}", 'LOCATION', false); }, 1); } // Специальные хуки для контента add_action('the_content', function($content) { $this->current_location = 'THE_CONTENT'; return $content; }); add_action('the_title', function($title) { $this->current_location = 'THE_TITLE'; return $title; }); add_action('the_excerpt', function($excerpt) { $this->current_location = 'THE_EXCERPT'; return $excerpt; }); } /** * Перехватываем запрос ДО выполнения */ public function log_query_before_execution($query) { if (!$this->log_enabled) return $query; $trimmed = trim($query); if (empty($trimmed)) return $query; $query_hash = md5($query . microtime(true)); $backtrace_info = $this->get_detailed_caller_info(); // Сохраняем запрос как pending $this->pending_queries[$query_hash] = [ 'query' => $query, 'start_time' => microtime(true), 'backtrace' => $backtrace_info['short'], 'detailed_backtrace' => $backtrace_info['full'], 'component' => $this->get_component_info($backtrace_info['file']), 'location' => $this->current_location, 'status' => 'PENDING' ]; $this->log_message( "🚦 QUERY QUEUED: " . $this->shorten_query($query), 'SQL-QUEUE', false ); $this->log_message( " 📍 Source: " . $backtrace_info['short'], 'SQL-QUEUE', false ); $this->log_message( " 🏷️ Component: " . $this->pending_queries[$query_hash]['component'], 'SQL-QUEUE', false ); $this->log_message( " 🏠 Location: " . $this->current_location, 'SQL-QUEUE', false ); return $query; } /** * Логируем запрос ПОСЛЕ выполнения */ public function log_query_after_execution($query, $result = null) { if (!$this->log_enabled) return $query; $trimmed = trim($query); if (empty($trimmed)) return $query; $query_hash = md5($query . microtime(true)); $execution_time = 0; $caller_info = ''; $component_info = ''; $location_info = ''; // Находим соответствующий pending запрос foreach ($this->pending_queries as $hash => $pending) { if ($pending['query'] === $query) { $execution_time = microtime(true) - $pending['start_time']; $this->pending_queries[$hash]['status'] = 'COMPLETED'; $this->pending_queries[$hash]['execution_time'] = $execution_time; $caller_info = $pending['backtrace']; $component_info = $pending['component']; $location_info = $pending['location']; break; } } $status = $result === false ? 'FAILED' : 'COMPLETED'; $time_ms = round($execution_time * 1000, 2); $this->log_message( "✅ QUERY {$status}: {$time_ms}ms - " . $this->shorten_query($query), $status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE', false ); $this->log_message( " 📍 Source: " . $caller_info, $status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE', false ); $this->log_message( " 🏷️ Component: " . $component_info, $status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE', false ); $this->log_message( " 🏠 Location: " . $location_info, $status === 'FAILED' ? 'SQL-ERROR' : 'SQL-DONE', false ); // Для медленных запросов добавляем дополнительную информацию if ($time_ms > 100 && $status !== 'FAILED') { $this->log_message( " ⚠️ SLOW QUERY: {$time_ms}ms - consider optimization", 'SQL-SLOW', false ); } return $query; } /** * Логируем ошибки запросов */ public function log_query_error($error, $query) { if (!$this->log_enabled) return; $this->log_message( "❌ QUERY ERROR: " . $error, 'SQL-ERROR', false ); $this->log_message( " 💥 Failed query: " . $this->shorten_query($query), 'SQL-ERROR', false ); // Находим информацию о вызывающем коде для ошибочного запроса foreach ($this->pending_queries as $pending) { if ($pending['query'] === $query) { $this->log_message( " 📍 Source: " . $pending['backtrace'], 'SQL-ERROR', false ); $this->log_message( " 🏷️ Component: " . $pending['component'], 'SQL-ERROR', false ); $this->log_message( " 🏠 Location: " . $pending['location'], 'SQL-ERROR', false ); break; } } } /** * Логируем зависшие запросы */ public function log_pending_queries() { if (!$this->log_enabled || empty($this->pending_queries)) return; $current_time = microtime(true); $hung_queries = 0; foreach ($this->pending_queries as $hash => $query_info) { if ($query_info['status'] === 'PENDING') { $hang_time = round(($current_time - $query_info['start_time']) * 1000, 2); $hung_queries++; $this->log_message( "⚠️ HUNG QUERY: {$hang_time}ms - " . $this->shorten_query($query_info['query']), 'SQL-HUNG', false ); $this->log_message( " 📍 Source: " . $query_info['backtrace'], 'SQL-HUNG', false ); $this->log_message( " 🏷️ Component: " . $query_info['component'], 'SQL-HUNG', false ); $this->log_message( " 🏠 Location: " . $query_info['location'], 'SQL-HUNG', false ); $this->log_message( " 🔍 Full backtrace:\n" . $query_info['detailed_backtrace'], 'SQL-HUNG', false ); } } if ($hung_queries > 0) { $this->log_message( "🔴 Found {$hung_queries} hung queries!", 'SQL-HUNG', false ); } // Логируем статистику по всем запросам $this->log_query_statistics(); } /** * Логируем статистику выполнения запросов */ private function log_query_statistics() { $total_queries = count($this->pending_queries); $completed_queries = 0; $failed_queries = 0; $total_time = 0; $queries_by_location = []; $queries_by_component = []; foreach ($this->pending_queries as $query) { // Статистика по местоположению $location = $query['location']; if (!isset($queries_by_location[$location])) { $queries_by_location[$location] = 0; } $queries_by_location[$location]++; // Статистика по компонентам $component = $query['component']; if (!isset($queries_by_component[$component])) { $queries_by_component[$component] = 0; } $queries_by_component[$component]++; if ($query['status'] === 'COMPLETED') { $completed_queries++; $total_time += $query['execution_time'] ?? 0; } elseif ($query['status'] === 'FAILED') { $failed_queries++; } } $avg_time = $completed_queries > 0 ? round(($total_time / $completed_queries) * 1000, 2) : 0; $this->log_message( "📊 STATS: Total: {$total_queries} | Completed: {$completed_queries} | " . "Failed: {$failed_queries} | Avg: {$avg_time}ms", 'STATS', false ); // Статистика по местоположению if (!empty($queries_by_location)) { $this->log_message("📊 QUERIES BY LOCATION:", 'STATS', false); arsort($queries_by_location); foreach ($queries_by_location as $location => $count) { $this->log_message(" {$location}: {$count} queries", 'STATS', false); } } // Статистика по компонентам if (!empty($queries_by_component)) { $this->log_message("📊 QUERIES BY COMPONENT:", 'STATS', false); arsort($queries_by_component); foreach ($queries_by_component as $component => $count) { $this->log_message(" {$component}: {$count} queries", 'STATS', false); } } } private function log_message($message, $level = 'INFO', $check_enabled = true) { if ($check_enabled && !$this->log_enabled) return; $timestamp = microtime(true); $elapsed = round(($timestamp - $this->start_time) * 1000, 2); $log = sprintf( "[%s] %s | %6.2fms | %-12s | %s\n", date('H:i:s'), substr($this->request_id, -6), $elapsed, $level, $message ); @file_put_contents($this->log_file, $log, FILE_APPEND | LOCK_EX); } /** * Получаем детальную информацию о вызывающем коде */ private function get_detailed_caller_info() { $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 15); $short_info = 'unknown'; $full_info = ''; $file_path = ''; foreach ($backtrace as $index => $trace) { if (isset($trace['file']) && !str_contains($trace['file'], 'wp-includes') && !str_contains($trace['file'], 'wp-admin')) { $file = basename($trace['file']); $line = $trace['line'] ?? 'unknown'; $function = $trace['function'] ?? 'unknown'; if ($short_info === 'unknown') { $short_info = "{$file}:{$line} ({$function})"; $file_path = $trace['file']; } $full_info .= sprintf("#%d %s(%d): %s()\n", $index, $trace['file'], $trace['line'] ?? 0, $function ); } } return [ 'short' => $short_info, 'full' => $full_info ?: 'No backtrace available', 'file' => $file_path ]; } /** * Определяем компонент (плагин/тема) по пути файла */ private function get_component_info($file_path) { if (empty($file_path)) return 'unknown'; $abspath = ABSPATH; // Определяем тип компонента if (str_contains($file_path, WP_PLUGIN_DIR)) { $relative_path = str_replace(WP_PLUGIN_DIR . '/', '', $file_path); $parts = explode('/', $relative_path); return 'Plugin: ' . $parts[0]; } elseif (str_contains($file_path, get_template_directory())) { $theme = wp_get_theme(); return 'Theme: ' . $theme->get('Name'); } elseif (str_contains($file_path, get_stylesheet_directory())) { $theme = wp_get_theme(); return 'Child Theme: ' . $theme->get('Name'); } elseif (str_contains($file_path, $abspath . 'wp-content/mu-plugins')) { return 'MU-Plugin'; } elseif (str_contains($file_path, $abspath . 'wp-content')) { return 'Other (wp-content)'; } elseif (str_contains($file_path, $abspath)) { return 'WordPress Core'; } return 'External'; } private function shorten_query($query, $length = 120) { $trimmed = trim($query); if (strlen($trimmed) <= $length) return $trimmed; return substr($trimmed, 0, $length) . '...'; } } // Инициализируем логгер //if (defined('WP_DEBUG') && WP_DEBUG) { RealtimeSqlLogger::get_instance(); //} // Функция для просмотра текущего состояния add_action('wp_footer', 'show_sql_debug_info', 9999); function show_sql_debug_info() { if (!current_user_can('administrator')) return; $log_file = WP_CONTENT_DIR . '/sql-debug.log'; if (file_exists($log_file)) { $log_content = file_get_contents($log_file); echo '
'; echo '

🎯 SQL Debug Log (Live)

'; echo '
' . htmlspecialchars($log_content) . '
'; echo '
'; } }