|
|
|
@ -11,8 +11,11 @@ use Psr\Log\InvalidArgumentException; |
|
|
|
|
|
|
|
|
|
|
|
final class Logger implements LoggerInterface |
|
|
|
final class Logger implements LoggerInterface |
|
|
|
{ |
|
|
|
{ |
|
|
|
private $handle = false; |
|
|
|
const LOGS_DIR = '/protected/logs'; |
|
|
|
|
|
|
|
const LOGS_EXT = '.log.txt'; |
|
|
|
|
|
|
|
const LOG_RETETION_DAYS = 90; // Keep 3 Months of LOGS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private $handle = false; |
|
|
|
private string $minLevel; |
|
|
|
private string $minLevel; |
|
|
|
|
|
|
|
|
|
|
|
private const LEVEL_PRIORITY = [ |
|
|
|
private const LEVEL_PRIORITY = [ |
|
|
|
@ -25,15 +28,11 @@ final class Logger implements LoggerInterface |
|
|
|
LogLevel::INFO => 200, |
|
|
|
LogLevel::INFO => 200, |
|
|
|
LogLevel::DEBUG => 100, |
|
|
|
LogLevel::DEBUG => 100, |
|
|
|
]; |
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
private const ENV_LEVEL_MAP = [ |
|
|
|
private const ENV_LEVEL_MAP = [ |
|
|
|
'production' => LogLevel::ERROR, |
|
|
|
'production' => LogLevel::ERROR, |
|
|
|
'prod' => LogLevel::ERROR, |
|
|
|
'prod' => LogLevel::ERROR, |
|
|
|
|
|
|
|
|
|
|
|
'staging' => LogLevel::WARNING, |
|
|
|
'staging' => LogLevel::WARNING, |
|
|
|
|
|
|
|
|
|
|
|
'testing' => LogLevel::NOTICE, |
|
|
|
'testing' => LogLevel::NOTICE, |
|
|
|
|
|
|
|
|
|
|
|
'development' => LogLevel::DEBUG, |
|
|
|
'development' => LogLevel::DEBUG, |
|
|
|
'dev' => LogLevel::DEBUG, |
|
|
|
'dev' => LogLevel::DEBUG, |
|
|
|
'local' => LogLevel::DEBUG, |
|
|
|
'local' => LogLevel::DEBUG, |
|
|
|
@ -43,7 +42,8 @@ final class Logger implements LoggerInterface |
|
|
|
string $filename = 'system', |
|
|
|
string $filename = 'system', |
|
|
|
int $maxCount = 1000, |
|
|
|
int $maxCount = 1000, |
|
|
|
?string $minLevel = null |
|
|
|
?string $minLevel = null |
|
|
|
) { |
|
|
|
) |
|
|
|
|
|
|
|
{ |
|
|
|
$env = self::detectEnv(); |
|
|
|
$env = self::detectEnv(); |
|
|
|
|
|
|
|
|
|
|
|
if ($minLevel === null) { |
|
|
|
if ($minLevel === null) { |
|
|
|
@ -65,7 +65,7 @@ final class Logger implements LoggerInterface |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$logDir = Requires::filterDirPath(BaseDir . '/protected/logs'); |
|
|
|
$logDir = Requires::filterDirPath(BaseDir . self::LOGS_DIR); |
|
|
|
if (!Requires::isValidFile($logDir)) { |
|
|
|
if (!Requires::isValidFile($logDir)) { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -81,10 +81,10 @@ final class Logger implements LoggerInterface |
|
|
|
if ($safeFileName === false || $safeFileName === "") { |
|
|
|
if ($safeFileName === false || $safeFileName === "") { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
$file = $logDir . '/' . $safeFileName . '.log.txt'; |
|
|
|
$file = $logDir . '/' . $safeFileName . self::LOGS_EXT; |
|
|
|
|
|
|
|
|
|
|
|
if ($maxCount > 1 && $this->getLines($file) > $maxCount) { |
|
|
|
if ($maxCount > 5 && $this->getLines($file) > $maxCount) { |
|
|
|
@unlink($file); |
|
|
|
$aLogs = $this->rotateLog($file, $safeFileName); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!file_exists($file)) { |
|
|
|
if (!file_exists($file)) { |
|
|
|
@ -100,20 +100,66 @@ final class Logger implements LoggerInterface |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$this->handle = fopen($file, 'ab'); |
|
|
|
$this->handle = fopen($file, 'ab'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($aLogs)) { |
|
|
|
|
|
|
|
$this->alert("Purged from Logs = " . $aLogs['logsDeleted']); |
|
|
|
|
|
|
|
} |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function cleanupLogs($days): int |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
// Safety check... |
|
|
|
|
|
|
|
if ($days < 2) { |
|
|
|
|
|
|
|
return 0; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
$logDir = Requires::filterDirPath(BaseDir . self::LOGS_DIR); |
|
|
|
|
|
|
|
if ($logDir === false) { |
|
|
|
|
|
|
|
return 0; // DIR unsafe... |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
$cutoff = strtotime("-$days days"); |
|
|
|
|
|
|
|
$files = glob($logDir . "/*" . self::LOGS_EXT); |
|
|
|
|
|
|
|
$count = 0; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach ($files as $file) { |
|
|
|
|
|
|
|
if (filemtime($file) < $cutoff) { |
|
|
|
|
|
|
|
if (unlink($file)) { |
|
|
|
|
|
|
|
$count++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return $count; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private function rotateLog(string $logFile, string $safeFileName): array |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
$directory = dirname($logFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create new filename with current date, hour, and minute |
|
|
|
|
|
|
|
$newFileName = sprintf( |
|
|
|
|
|
|
|
'%s_%s%s', |
|
|
|
|
|
|
|
$safeFileName, |
|
|
|
|
|
|
|
date('Y-m-d_H-i'), // Format: 2026-06-15_14-30 |
|
|
|
|
|
|
|
self::LOGS_EXT |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$newFile= $directory . DIRECTORY_SEPARATOR . $newFileName; |
|
|
|
|
|
|
|
$success = @rename($logFile, $newFile); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
$deleteCount = $this->cleanupLogs(self::LOG_RETETION_DAYS); |
|
|
|
|
|
|
|
return ['renamedSuccess' => $success, 'logsDeleted' => $deleteCount]; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* ---------------- PSR-3 Core ---------------- */ |
|
|
|
/* ---------------- PSR-3 Core ---------------- */ |
|
|
|
|
|
|
|
|
|
|
|
public function log($level, $message, array $context = []): void |
|
|
|
public function log($level, $message, array $context = [], string $details = ""): void |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!is_string($level) || !isset(self::LEVEL_PRIORITY[$level])) { |
|
|
|
if (!is_string($level) || !isset(self::LEVEL_PRIORITY[$level])) { |
|
|
|
throw new InvalidArgumentException('Invalid log level'); |
|
|
|
throw new InvalidArgumentException('Invalid log level'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
if ( |
|
|
|
self::LEVEL_PRIORITY[$level] |
|
|
|
self::LEVEL_PRIORITY[$level] < self::LEVEL_PRIORITY[$this->minLevel] |
|
|
|
< self::LEVEL_PRIORITY[$this->minLevel] |
|
|
|
|
|
|
|
) { |
|
|
|
) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -124,10 +170,17 @@ final class Logger implements LoggerInterface |
|
|
|
|
|
|
|
|
|
|
|
$message = $this->interpolate((string) $message, $context); |
|
|
|
$message = $this->interpolate((string) $message, $context); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!empty($details)) { |
|
|
|
|
|
|
|
$customeLevel = strtoupper($details); |
|
|
|
|
|
|
|
$message = "Default level was: " . strtoupper($level) . " -- " . $message; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
$customeLevel = strtoupper($level); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
$time = (new \DateTimeImmutable())->format('Y-m-d H:i:s'); |
|
|
|
$time = (new \DateTimeImmutable())->format('Y-m-d H:i:s'); |
|
|
|
fwrite( |
|
|
|
fwrite( |
|
|
|
$this->handle, |
|
|
|
$this->handle, |
|
|
|
sprintf("[%s] %s: %s\n", $time, strtoupper($level), $message) |
|
|
|
sprintf("[%s] %s: %s\n", $time, strtoupper($customeLevel), $message) |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -148,6 +201,11 @@ final class Logger implements LoggerInterface |
|
|
|
$this->log(LogLevel::CRITICAL, $message, $context); |
|
|
|
$this->log(LogLevel::CRITICAL, $message, $context); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public function SomeSystemError(string $level, $message, array $context = []): void |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
$this->log(LogLevel::ERROR, $message, $context, $level); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public function error($message, array $context = []): void |
|
|
|
public function error($message, array $context = []): void |
|
|
|
{ |
|
|
|
{ |
|
|
|
$this->log(LogLevel::ERROR, $message, $context); |
|
|
|
$this->log(LogLevel::ERROR, $message, $context); |
|
|
|
@ -174,11 +232,11 @@ final class Logger implements LoggerInterface |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* ---------------- Helpers ---------------- */ |
|
|
|
/* ---------------- Helpers ---------------- */ |
|
|
|
|
|
|
|
|
|
|
|
private static function detectEnv(): string |
|
|
|
private static function detectEnv(): string |
|
|
|
{ |
|
|
|
{ |
|
|
|
return strtolower( |
|
|
|
return strtolower( |
|
|
|
getenv('APP_ENV') |
|
|
|
getenv('APP_ENV') ?: ($_ENV['APP_ENV'] ?? 'production') |
|
|
|
?: ($_ENV['APP_ENV'] ?? 'production') |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|