Files
profile/inc/realtimesqllogger.php
Profile Profile ed4d79b706 add files inc
2026-03-09 20:51:08 +03:00

511 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Real-time SQL Query Logger with PRE-execution tracking
*/
class RealtimeSqlLogger {
private static $instance;
private $log_file;
private $request_id;
private $start_time;
private $pending_queries = [];
private $log_enabled = true;
private $current_location = 'unknown';
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->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 '<div style="background:#1e1e1e;padding:20px;margin:20px;border:1px solid #444;font-family:monospace;font-size:11px;max-height:400px;overflow:auto;color:#fff;">';
echo '<h3 style="color:#fff;">🎯 SQL Debug Log (Live)</h3>';
echo '<pre style="color:#ccc;line-height:1.4;">' . htmlspecialchars($log_content) . '</pre>';
echo '</div>';
}
}