diff --git a/app/public/admin/parametres.php b/app/public/admin/parametres.php index 583eaa4..107234c 100644 --- a/app/public/admin/parametres.php +++ b/app/public/admin/parametres.php @@ -53,17 +53,24 @@ if ($activeTab === 'status' || !array_key_exists($activeTab, SystemController::L $activeTab = 'app'; } +// Optional date selection (YYYY-MM-DD) for daily app logs. +$selectedDate = $_GET['date'] ?? null; +if ($selectedDate !== null && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $selectedDate)) { + $selectedDate = null; +} + $selectedN = isset($_GET['n']) ? (int) $_GET['n'] : 100; if (!in_array($selectedN, SystemController::ALLOWED_LINES, true)) { $selectedN = 100; } -$logData = $_controller->getLogData($activeTab, $selectedN); +$logData = $_controller->getLogData($activeTab, $selectedN, $selectedDate); $logLines = $logData['lines']; $logError = $logData['error']; $logFileMeta = $logData['meta']; $logIsJson = $logData['isJson'] ?? false; $notYet = $logData['notYet'] ?? false; +$logDates = SystemController::listLogDates($activeTab); $collapsed = $_COOKIE['sys_collapsed'] ?? null; $statusInitiallyCollapsed = $collapsed === '1'; diff --git a/app/public/admin/system-fragment.php b/app/public/admin/system-fragment.php index 682a180..b8f4ad1 100644 --- a/app/public/admin/system-fragment.php +++ b/app/public/admin/system-fragment.php @@ -33,6 +33,12 @@ if (!in_array($selectedN, SystemController::ALLOWED_LINES, true)) { $selectedN = 100; } +// Optional date selection (YYYY-MM-DD) for daily app logs. +$selectedDate = $_GET['date'] ?? null; +if ($selectedDate !== null && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $selectedDate)) { + $selectedDate = null; +} + header('Content-Type: text/html; charset=utf-8'); header('X-Robots-Tag: noindex'); @@ -42,11 +48,12 @@ $_cache = new SystemCache($_db->getPDO()); $_controller = new SystemController($_db, $_cache); // ── Render ───────────────────────────────────────────────────────────────── -$logData = $_controller->getLogData($activeTab, $selectedN); +$logData = $_controller->getLogData($activeTab, $selectedN, $selectedDate); $logLines = $logData['lines']; $logError = $logData['error']; $logFileMeta = $logData['meta']; $logIsJson = $logData['isJson'] ?? false; $notYet = $logData['notYet'] ?? false; +$logDates = SystemController::listLogDates($activeTab); include APP_ROOT . '/templates/admin/partials/system-log-panel.php'; diff --git a/app/src/AdminLogger.php b/app/src/AdminLogger.php index e63cfc2..42d6a90 100644 --- a/app/src/AdminLogger.php +++ b/app/src/AdminLogger.php @@ -3,8 +3,8 @@ /** * Admin audit logger. * - * Writes JSON-lines to /var/log/xamxam.log (production) or - * storage/logs/admin.log (dev / cli-server). + * Writes JSON-lines to /var/log/xamxam/xamxam-admin-YYYY-MM-DD.log (production) + * or storage/logs/xamxam-admin.log (dev / cli-server). * * Each entry: timestamp, actor (admin IP/UA), action, resource, status, context. * diff --git a/app/src/Controllers/SystemController.php b/app/src/Controllers/SystemController.php index 59e835b..dae7891 100644 --- a/app/src/Controllers/SystemController.php +++ b/app/src/Controllers/SystemController.php @@ -26,25 +26,39 @@ class SystemController 'admin' => ['label' => 'Admin — actions', 'path' => null, 'json' => true], 'error' => ['label' => 'Erreurs — application', 'path' => null, 'json' => true], 'audit' => ['label' => 'Audit — données', 'path' => null, 'json' => true], - 'nginx_access' => ['label' => 'nginx — accès', 'path' => '/var/log/nginx/xamxam_access.log', 'json' => false], - 'nginx_error' => ['label' => 'nginx — erreurs','path' => '/var/log/nginx/xamxam_error.log', 'json' => false], + 'nginx_access' => ['label' => 'nginx — accès', 'path' => '/var/log/nginx/xamxam-nginx-access.log', 'json' => false], + 'nginx_error' => ['label' => 'nginx — erreurs','path' => '/var/log/nginx/xamxam-nginx-error.log', 'json' => false], ]; /** - * Resolve a log file path — app logs live under STORAGE_ROOT, system logs - * have hard-coded paths (only valid in production). + * Resolve a log file path — app logs live under /var/log/xamxam in + * production (and storage/logs in dev/cli-server); nginx logs have + * hard-coded paths (only valid in production). + * + * A $date (YYYY-MM-DD) selects a specific retained daily file for app + * channels; when null the most recent file is returned. */ - private static function resolveLogPath(string $tab): string + private static function resolveLogPath(string $tab, ?string $date = null): string { $def = self::LOG_FILES[$tab]; if ($def['path'] !== null) { return $def['path']; } - // App logs: storage/logs/{channel}.log (Monolog RotatingFileHandler uses - // this as base name; the current log is always at {channel}-YYYY-MM-DD.log) - $dir = defined('STORAGE_ROOT') ? STORAGE_ROOT . '/logs' : APP_ROOT . '/storage/logs'; + // App logs: /var/log/xamxam/xamxam-{channel}.log (production) or + // storage/logs/xamxam-{channel}.log (dev / cli-server). Monolog + // RotatingFileHandler uses this as base name; the current log is always + // at xamxam-{channel}-YYYY-MM-DD.log. + $dir = php_sapi_name() === 'cli-server' + ? APP_ROOT . '/storage/logs' + : '/var/log/xamxam'; + $base = $dir . '/xamxam-' . $tab; + + // Explicit date selection (validated YYYY-MM-DD) + if ($date !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { + return $base . '-' . $date . '.log'; + } + // Find the most recent dated log file for this channel - $base = $dir . '/' . $tab; $dated = glob($base . '-20[0-9][0-9]-[0-9][0-9]-[0-9][0-9].log'); if (!empty($dated)) { rsort($dated); // newest first @@ -54,6 +68,49 @@ class SystemController return $base . '.log'; } + /** + * List the retained daily log files (dates) for an app channel. + * + * Returns [['date' => 'YYYY-MM-DD', 'label' => '...'], …] newest-first. + * Empty array for system logs (nginx) which have fixed names. + */ + public static function listLogDates(string $tab): array + { + $def = self::LOG_FILES[$tab]; + if (($def['json'] ?? false) !== true) { + return []; // nginx logs are fixed-name, not daily + } + + $dir = php_sapi_name() === 'cli-server' + ? APP_ROOT . '/storage/logs' + : '/var/log/xamxam'; + $base = $dir . '/xamxam-' . $tab; + $dated = glob($base . '-20[0-9][0-9]-[0-9][0-9]-[0-9][0-9].log'); + if (empty($dated)) { + return []; + } + + $dates = []; + foreach ($dated as $path) { + if (preg_match('/(\d{4}-\d{2}-\d{2})\.log$/', $path, $m)) { + $dates[] = $m[1]; + } + } + rsort($dates); // newest first + + $out = []; + foreach ($dates as $d) { + $isToday = $d === date('Y-m-d'); + $out[] = [ + 'date' => $d, + 'label' => $isToday + ? 'Aujourd\'hui (' . date('d/m/Y', strtotime($d)) . ')' + : date('d/m/Y', strtotime($d)), + ]; + } + return $out; + } + public const ALLOWED_LINES = [50, 100, 200, 500]; // ── TTLs ────────────────────────────────────────────────────────────────── @@ -157,11 +214,13 @@ class SystemController /** * Read and return data for a log tab. * + * @param string|null $date YYYY-MM-DD to select a specific daily file + * (app channels); null = most recent. * @return array{lines: ?array, error: ?string, meta: ?array} */ - public function getLogData(string $tab, int $n): array + public function getLogData(string $tab, int $n, ?string $date = null): array { - $logPath = self::resolveLogPath($tab); + $logPath = self::resolveLogPath($tab, $date); $isJson = self::LOG_FILES[$tab]['json'] ?? false; $error = null; $rawLines = null; diff --git a/app/src/Logger.php b/app/src/Logger.php index 32cc0b3..20befe5 100644 --- a/app/src/Logger.php +++ b/app/src/Logger.php @@ -55,7 +55,7 @@ class Logger try { $handler = new RotatingFileHandler( - $logDir . '/' . $channel . '.log', + $logDir . '/xamxam-' . $channel . '.log', 30, // keep 30 days of logs self::level() ); @@ -99,12 +99,18 @@ class Logger /** * Resolve the log directory. + * + * Production: /var/log/xamxam/ (standard system log root, provisioned by + * setup-server.sh; owned by www-data:xamxam). Dev (cli-server): the + * app's local storage/logs/ so no root provisioning is needed. */ private static function logDir(): string { - if (defined('STORAGE_ROOT')) { - return STORAGE_ROOT . '/logs'; + // cli-server (dev) → app-local storage/logs; everything else (php-fpm + // production) → the standard system log directory. + if (php_sapi_name() === 'cli-server') { + return __DIR__ . '/../storage/logs'; } - return __DIR__ . '/../storage/logs'; + return '/var/log/xamxam'; } } diff --git a/app/storage/README.md b/app/storage/README.md index 43740b1..5342799 100644 --- a/app/storage/README.md +++ b/app/storage/README.md @@ -24,7 +24,7 @@ just migrate # run pending migrations | `schema.sql` | Full, fully-migrated schema + seed data (regenerated from the local DB) | | `backups/` | `just backup-snapshot` hot-backups (`*.db.gz`) | | `cache/` | Runtime cache (rate limits, etc.) | -| `logs/` | Runtime logs (admin, audit) — **outside the webroot** | +| `logs/` | Dev-only runtime logs (cli-server); production logs live in `/var/log/xamxam/` | | `covers/` | Cover images | | `theses/`, `tfe/`, `tmp/` | Uploaded files / staging | diff --git a/app/templates/admin/parametres.php b/app/templates/admin/parametres.php index 8898b3a..2551e54 100644 --- a/app/templates/admin/parametres.php +++ b/app/templates/admin/parametres.php @@ -386,8 +386,8 @@
Affiché le = date('d/m/Y à H:i:s') ?> — - Rafraîchir — - Forcer actualisation + Rafraîchir — + Forcer actualisation