Integrate Monolog: replace four logging systems with single PSR-3 factory

- Add monolog/monolog dependency (^3.10)  
- Create app/Logger.php central factory with channels: app, admin, error, audit
- Each channel gets RotatingFileHandler (30-day retention) with pass-through LineFormatter
  preserving existing JSON format contracts
- Rewrite AppLogger as thin facade delegating to Logger::get('app')
- Rewrite ErrorHandler::log() to delegate to Logger::get('error')
- Rewrite AdminLogger file output to delegate to Logger::get('admin'), keep DB writes
- Add Monolog file shadow to Audit via Logger::get('audit') (Option A per monolog-plan)
- Log level controlled by LOG_LEVEL env var (defaults: DEBUG in cli-server, WARNING otherwise)
- Graceful NullHandler fallback when log directory is not writable
- Update SystemController LOG_FILES: remove php_error, add app/admin/error/audit
- JSON app logs parsed to readable one-liners in the log viewer
- Remove nginx config tab (parametres + fragment + template + css)
- Friendly empty-state message when app log files don't exist yet (notYet)
- PHP tail fallback when exec() unavailable
- All 228 PHPUnit tests pass, no call sites changed
This commit is contained in:
Pontoporeia
2026-05-20 02:16:17 +02:00
parent a6e0aa5887
commit ae66c2baad
19 changed files with 662 additions and 433 deletions

View File

@@ -34,6 +34,7 @@ class Audit
?array $oldData = null,
?array $newData = null
): void {
// DB write is the primary path — best-effort, never crash.
try {
$stmt = $db->getConnection()->prepare(
'INSERT INTO audit_log (actor, action, table_name, record_id, old_data, new_data)
@@ -49,7 +50,33 @@ class Audit
]);
} catch (\Throwable $e) {
// Audit logging is best-effort — never crash the app over it.
error_log('[Audit] write failed: ' . $e->getMessage());
error_log('[Audit] DB write failed: ' . $e->getMessage());
}
// File shadow — structured JSON-line log for debuggability
// (Option A from monolog-plan: keep Audit DB logic as-is, add file trace)
try {
$entry = [
'timestamp' => date('c'),
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'cli',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'actor' => $actor,
'action' => $action,
'table' => $tableName,
'record_id' => $recordId,
];
if ($oldData !== null) {
$entry['old_data'] = $oldData;
}
if ($newData !== null) {
$entry['new_data'] = $newData;
}
$line = json_encode($entry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
Logger::get('audit')->info($line);
} catch (\Throwable $e) {
// File shadow is also best-effort
error_log('[Audit] file shadow write failed: ' . $e->getMessage());
}
}