<?php
namespace SHServ\Middleware;
/**
* SqlQueryLogger — логирует каждый SQL-запрос: текст, длительность (мс),
* и помечает медленные запросы (>1000 мс) как WARN.
*
* Работает через события ThinBuilder:
* - module:ThinBuilder.ready_sql → фиксируем SQL + стартовое время
* - module:ThinBuilder.query → вычисляем длительность, пишем лог
*
* Поддерживает gen_sql_only (orphan SQL остаётся в pending до конца запроса).
*/
class SqlQueryLogger {
protected static array $pending = [];
public function __construct() {
events() -> handler('module:ThinBuilder.ready_sql', function(Array $params) {
$sql = $params['sql'] ?? '';
if ($sql === '') {
return;
}
self::$pending[$sql] = microtime(true);
});
events() -> handler('module:ThinBuilder.query', function(Array $params) {
$sql = $params['sql'] ?? '';
$result = $params['result'] ?? null;
if ($sql === '' || !isset(self::$pending[$sql])) {
return;
}
$start = self::$pending[$sql];
unset(self::$pending[$sql]);
$duration_ms = round((microtime(true) - $start) * 1000, 3);
$level = $duration_ms > 1000 ? 'WARN' : 'INFO';
$message = $duration_ms > 1000 ? 'Slow query' : 'Query executed';
$row_count = null;
if (is_array($result)) {
$row_count = count($result);
} elseif (is_int($result) || is_numeric($result)) {
$row_count = (int) $result;
}
$context = [
'sql' => $sql,
'duration_ms' => $duration_ms,
];
if ($row_count !== null) {
$context['row_count'] = $row_count;
}
logging() -> log($level, 'php:ThinBuilder', $message, $context);
});
// Очистка orphan SQL (например, при gen_sql_only)
events() -> handler('kernel:Bootstrap.app_finished', function(Array $params) {
if (empty(self::$pending)) {
return;
}
foreach (self::$pending as $sql => $start) {
logging() -> info('php:ThinBuilder', 'Query planned but not executed', [
'sql' => $sql,
]);
}
self::$pending = [];
});
}
}