logs: standardise log filenames to xamxam-{service}-{date}.log

This commit is contained in:
Pontoporeia
2026-08-24 11:34:34 +02:00
parent 7b6d79c133
commit d2cef85966
25 changed files with 442 additions and 83 deletions
+8 -1
View File
@@ -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';
+8 -1
View File
@@ -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';
+2 -2
View File
@@ -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.
*
+70 -11
View File
@@ -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;
+10 -4
View File
@@ -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';
}
}
+1 -1
View File
@@ -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 |
+5 -5
View File
@@ -386,8 +386,8 @@
<p class="sys-refresh-note">
Affiché le <?= date('d/m/Y à H:i:s') ?> —
<a href="?tab=<?= htmlspecialchars($activeTab) ?>&amp;n=<?= $selectedN ?>">Rafraîchir</a> —
<a href="?tab=<?= htmlspecialchars($activeTab) ?>&amp;n=<?= $selectedN ?>&amp;refresh=1">Forcer actualisation</a>
<a href="?tab=<?= htmlspecialchars($activeTab) ?>&amp;date=<?= htmlspecialchars($selectedDate ?? '') ?>&amp;n=<?= $selectedN ?>">Rafraîchir</a> —
<a href="?tab=<?= htmlspecialchars($activeTab) ?>&amp;date=<?= htmlspecialchars($selectedDate ?? '') ?>&amp;n=<?= $selectedN ?>&amp;refresh=1">Forcer actualisation</a>
</p>
<div class="sys-status-header">
@@ -460,11 +460,11 @@
<nav class="sys-tabs" aria-label="Journaux et configuration">
<?php foreach (SystemController::LOG_FILES as $key => $def): ?>
<a href="?tab=<?= htmlspecialchars($key) ?>&amp;n=<?= $selectedN ?>"
<a href="?tab=<?= htmlspecialchars($key) ?>&amp;date=<?= htmlspecialchars($selectedDate ?? '') ?>&amp;n=<?= $selectedN ?>"
class="sys-tab <?= $activeTab === $key ? 'active' : '' ?>"
hx-get="/admin/system-fragment.php?tab=<?= htmlspecialchars($key) ?>&amp;n=<?= $selectedN ?>"
hx-get="/admin/system-fragment.php?tab=<?= htmlspecialchars($key) ?>&amp;date=<?= htmlspecialchars($selectedDate ?? '') ?>&amp;n=<?= $selectedN ?>"
hx-target="#sys-tab-panel"
hx-push-url="?tab=<?= htmlspecialchars($key) ?>&amp;n=<?= $selectedN ?>"
hx-push-url="?tab=<?= htmlspecialchars($key) ?>&amp;date=<?= htmlspecialchars($selectedDate ?? '') ?>&amp;n=<?= $selectedN ?>"
hx-swap="innerHTML"
hx-indicator="#sys-tab-panel"
data-tab="<?= htmlspecialchars($key) ?>"
@@ -4,7 +4,7 @@
hx-swap="innerHTML"
hx-indicator="#sys-tab-panel"
hx-trigger="change"
hx-vals='{"tab":"<?= htmlspecialchars($activeTab) ?>"}'>
hx-vals='{"tab":"<?= htmlspecialchars($activeTab) ?>","date":"<?= htmlspecialchars($selectedDate ?? '') ?>"}'>
<label for="lines-select">Afficher</label>
<select id="lines-select" name="n" aria-label="Nombre de lignes">
<?php foreach (SystemController::ALLOWED_LINES as $opt): ?>
@@ -12,6 +12,23 @@
<?php endforeach; ?>
</select>
</form>
<?php if (!empty($logDates)): ?>
<form id="date-form" hx-get="/admin/system-fragment.php"
hx-target="#sys-tab-panel"
hx-swap="innerHTML"
hx-indicator="#sys-tab-panel"
hx-trigger="change"
hx-vals='{"tab":"<?= htmlspecialchars($activeTab) ?>","n":<?= $selectedN ?>}'>
<label for="date-select">Jour</label>
<select id="date-select" name="date" aria-label="Jour du journal">
<?php foreach ($logDates as $d): ?>
<option value="<?= htmlspecialchars($d['date']) ?>" <?= ($selectedDate ?? null) === $d['date'] ? 'selected' : '' ?>><?= htmlspecialchars($d['label']) ?></option>
<?php endforeach; ?>
</select>
</form>
<?php endif; ?>
<?php if ($logLines !== null && count($logLines) > 0): ?>
<span class="log-count-badge"><?= count($logLines) ?> ligne(s)</span>
<?php endif; ?>