Newer
Older
smart-home-server / server / SHServ / Middleware / SqlQueryLogger.php
<?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 = [];
		});
	}
}