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 12:28:31 +02:00
parent a6e0aa5887
commit ae66c2baad
19 changed files with 662 additions and 433 deletions
+16 -16
View File
@@ -154,7 +154,7 @@ class ErrorHandler
}
/**
* Write a structured error log entry.
* Write a structured error log entry — delegates to Monolog 'error' channel.
*
* @param string $context e.g. 'thesis_edit', 'partage_submit', 'csv_import'
* @param \Throwable $e
@@ -162,22 +162,22 @@ class ErrorHandler
*/
public static function log(string $context, \Throwable $e, array $extra = []): void
{
$parts = [
'context=' . $context,
'exception=' . get_class($e),
'message=' . $e->getMessage(),
$entry = [
'timestamp' => date('c'),
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'context' => $context,
'exception' => get_class($e),
'message' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
];
foreach ($extra as $k => $v) {
if (is_scalar($v) || $v === null) {
$parts[] = $k . '=' . json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} elseif (is_array($v)) {
$parts[] = $k . '=' . json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} else {
$parts[] = $k . '=' . gettype($v);
}
}
$parts[] = 'trace=' . $e->getTraceAsString();
error_log(implode(' | ', $parts));
if (!empty($extra)) {
$entry['extra'] = $extra;
}
$line = json_encode($entry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
Logger::get('error')->error($line);
}
}