mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-27 00:59:18 +02:00
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:
105
app/src/Logger.php
Normal file
105
app/src/Logger.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Level;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Central logger factory — provides named Monolog channel instances
|
||||
* backed by rotating JSON-line log files.
|
||||
*
|
||||
* Channels:
|
||||
* - 'app' → replaces AppLogger
|
||||
* - 'admin' → replaces AdminLogger file output
|
||||
* - 'error' → replaces ErrorHandler logging
|
||||
* - 'audit' → replaces Audit file shadow (DB writes stay in Audit)
|
||||
*
|
||||
* Usage:
|
||||
* Logger::get('app')->info('message', [...]);
|
||||
* Logger::get('admin')->warning('message', [...]);
|
||||
*/
|
||||
class Logger
|
||||
{
|
||||
/** @var array<string, LoggerInterface> */
|
||||
private static array $channels = [];
|
||||
|
||||
/**
|
||||
* Get (or lazily create) a named Monolog channel.
|
||||
*/
|
||||
public static function get(string $channel): LoggerInterface
|
||||
{
|
||||
if (!isset(self::$channels[$channel])) {
|
||||
self::$channels[$channel] = self::create($channel);
|
||||
}
|
||||
return self::$channels[$channel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Monolog channel with rotating JSON file handler.
|
||||
*
|
||||
* Falls back to NullHandler if the log directory is not writable
|
||||
* (e.g. CLI scripts on a machine that doesn't have the production path).
|
||||
*/
|
||||
private static function create(string $channel): \Monolog\Logger
|
||||
{
|
||||
$logDir = self::logDir();
|
||||
|
||||
if (!is_dir($logDir) && !@mkdir($logDir, 0755, true) && !is_dir($logDir)) {
|
||||
// Directory can't be created — use null handler (graceful degradation)
|
||||
$logger = new \Monolog\Logger($channel);
|
||||
$logger->pushHandler(new NullHandler());
|
||||
return $logger;
|
||||
}
|
||||
|
||||
try {
|
||||
$handler = new RotatingFileHandler(
|
||||
$logDir . '/' . $channel . '.log',
|
||||
30, // keep 30 days of logs
|
||||
self::level()
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
// Can't open log file — use null handler
|
||||
$logger = new \Monolog\Logger($channel);
|
||||
$logger->pushHandler(new NullHandler());
|
||||
return $logger;
|
||||
}
|
||||
|
||||
// Pass-through formatter: the facades (AppLogger, AdminLogger, etc.)
|
||||
// construct their own JSON lines and pass them as the log message.
|
||||
// %message% preserves the existing JSON format contract exactly.
|
||||
$handler->setFormatter(new LineFormatter("%message%\n", null, true));
|
||||
|
||||
$logger = new \Monolog\Logger($channel);
|
||||
$logger->pushHandler($handler);
|
||||
|
||||
return $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the LOG_LEVEL env var with sensible defaults.
|
||||
*/
|
||||
private static function level(): Level
|
||||
{
|
||||
$level = strtoupper(getenv('LOG_LEVEL') ?: '');
|
||||
|
||||
// Default: WARNING in production (always set in .env), DEBUG otherwise
|
||||
if ($level === '') {
|
||||
return php_sapi_name() === 'cli-server' ? Level::Debug : Level::Warning;
|
||||
}
|
||||
|
||||
return Level::fromName($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the log directory.
|
||||
*/
|
||||
private static function logDir(): string
|
||||
{
|
||||
if (defined('STORAGE_ROOT')) {
|
||||
return STORAGE_ROOT . '/logs';
|
||||
}
|
||||
return __DIR__ . '/../storage/logs';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user