<?php
namespace SHServ\Middleware;
/**
* RequestLogger — логирует начало и конец каждого HTTP-запроса (или CLI-команды).
*
* Регистрируется через события Fury:
* - Bootstrap.app_starting → начало запроса
* - CallControl.leading_call → имя вызываемого action
* - Bootstrap.app_finished → конец запроса
*
* Fallback через register_shutdown_function на случай exit/die.
*/
class RequestLogger {
protected static ?float $start_time = null;
protected static bool $completed = false;
protected static ?string $action_name = null;
public function __construct() {
global $argv;
$is_cli = isset($argv);
if ($is_cli) {
$this -> register_cli_handlers($argv);
} else {
$this -> register_http_handlers();
}
}
// ============================================================
// HTTP
// ============================================================
protected function register_http_handlers(): void {
self::$start_time = microtime(true);
self::$completed = false;
$uri = $_SERVER['REQUEST_URI'] ?? '';
if (strpos($uri, '?') !== false) {
list($path, $query) = explode('?', $uri, 2);
} else {
$path = $uri;
$query = '';
}
logging() -> info('php:RequestLogger', 'Request started', [
'method' => $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN',
'path' => $path,
'query' => $query,
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
'ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'content_length' => $_SERVER['CONTENT_LENGTH'] ?? 0,
'content_type' => $_SERVER['CONTENT_TYPE'] ?? '',
]);
events() -> handler('kernel:CallControl.leading_call', function(Array $params) {
$action = is_string($params['action'] ?? null)
? $params['action']
: 'anon';
self::$action_name = $action;
logging() -> debug('php:RequestLogger', 'Action dispatch', [
'action' => $action,
'type' => $params['type'] ?? '',
]);
});
events() -> handler('kernel:Bootstrap.app_finished', function(Array $params) {
self::$completed = true;
$this -> log_request_end();
});
register_shutdown_function(function() {
if (self::$completed) {
return;
}
logging() -> warn('php:RequestLogger', 'Request aborted or exited before app_finished', [
'duration_ms' => self::$start_time !== null
? round((microtime(true) - self::$start_time) * 1000, 3)
: null,
'aborted' => true,
]);
});
}
protected function log_request_end(): void {
$duration = self::$start_time !== null
? round((microtime(true) - self::$start_time) * 1000, 3)
: null;
$code = http_response_code();
if ($code === 0 || $code === false) {
$code = 200;
}
logging() -> info('php:RequestLogger', 'Request completed', [
'duration_ms' => $duration,
'memory_peak_mb' => round(memory_get_peak_usage(true) / 1024 / 1024, 2),
'status_code' => $code,
'action' => self::$action_name,
]);
}
// ============================================================
// CLI
// ============================================================
protected function register_cli_handlers(array $argv): void {
self::$start_time = microtime(true);
logging() -> info('php:RequestLogger', 'Console command started', [
'argv' => $argv,
]);
register_shutdown_function(function() use ($argv) {
$duration = self::$start_time !== null
? round((microtime(true) - self::$start_time) * 1000, 3)
: null;
logging() -> info('php:RequestLogger', 'Console command finished', [
'argv' => $argv,
'duration_ms' => $duration,
]);
});
}
}