Add FileLogging and FileLock classes

This commit is contained in:
netkas 2024-10-28 15:29:14 -04:00
parent f3d0412525
commit 9d0dbad846
2 changed files with 257 additions and 0 deletions

View file

@ -0,0 +1,102 @@
<?php
namespace LogLib\Classes;
use RuntimeException;
/**
* Class FileLock
*
* This class provides functionalities to safely work with file locks and ensures
* that concurrent write operations do not overwrite each other.
* It offers methods to lock, unlock, and append data to a file.
*/
class FileLock
{
private $fileHandle;
private string $filePath;
private int $retryInterval; // in microseconds
private int $confirmationInterval; // in microseconds
/**
* Constructor for FileLock.
*
* @param string $filePath Path to the file.
* @param int $retryInterval Time to wait between retries (in microseconds).
* @param int $confirmationInterval Time to wait before double confirmation (in microseconds).
*/
public function __construct(string $filePath, int $retryInterval=100000, int $confirmationInterval=50000)
{
$this->filePath = $filePath;
$this->retryInterval = $retryInterval;
$this->confirmationInterval = $confirmationInterval;
// Create the file if it doesn't exist
if (!file_exists($filePath))
{
$this->fileHandle = fopen($filePath, 'w');
fclose($this->fileHandle);
}
}
/**
* Locks the file.
*
* @throws RuntimeException if unable to open or lock the file.
*/
private function lock(): void
{
$this->fileHandle = fopen($this->filePath, 'a');
if (!$this->fileHandle)
{
throw new RuntimeException("Unable to open the file: " . $this->filePath);
}
// Keep trying to acquire the lock until it succeeds
while (!flock($this->fileHandle, LOCK_EX))
{
usleep($this->retryInterval); // Wait for the specified interval before trying again
}
// Double confirmation
usleep($this->confirmationInterval); // Wait for the specified confirmation interval
if (!flock($this->fileHandle, LOCK_EX | LOCK_NB))
{
// If the lock cannot be re-acquired, release the current lock and retry
flock($this->fileHandle, LOCK_UN);
$this->lock();
}
}
/**
* Unlocks the file after performing write operations.
*/
private function unlock(): void
{
flock($this->fileHandle, LOCK_UN); // Release the lock
fclose($this->fileHandle); // Close the file handle
}
/**
* Appends data to the file.
*
* @param string $data Data to append.
* @throws RuntimeException if unable to write to the file.
*/
public function append(string $data): void
{
$this->lock();
fwrite($this->fileHandle, $data);
$this->unlock();
}
/**
* Destructor to ensure the file handle is closed.
*/
public function __destruct()
{
if ($this->fileHandle)
{
fclose($this->fileHandle);
}
}
}

View file

@ -0,0 +1,155 @@
<?php
namespace LogLib\Handlers;
use LogLib\Classes\FileLock;
use LogLib\Classes\Utilities;
use LogLib\Classes\Validate;
use LogLib\Enums\LogHandlerType;
use LogLib\Enums\LogLevel;
use LogLib\Exceptions\LoggingException;
use LogLib\Interfaces\LogHandlerInterface;
use LogLib\Objects\Application;
use LogLib\Objects\Event;
use Throwable;
class FileLogging implements LogHandlerInterface
{
private static array $application_logs = [];
/**
* @inheritDoc
*/
public static function handle(Application $application, Event $event): void
{
if(!Validate::checkLevelType($event->getLevel(), $application->getFileLoggingLevel()))
{
return;
}
if(Validate::checkLevelType(LogLevel::DEBUG, $application->getConsoleLoggingLevel()))
{
$backtrace_output = Utilities::getTraceString($event);
$output = sprintf("[%s] [%s] [%s] %s %s" . PHP_EOL,
self::getTimestamp(), $application->getApplicationName(), $event->getLevel()->name, $backtrace_output, $event->getMessage()
);
if($event->getException() !== null)
{
$output .= self::outException($event->getException());
}
}
else if(Validate::checkLevelType(LogLevel::VERBOSE, $application->getConsoleLoggingLevel()))
{
$backtrace_output = Utilities::getTraceString($event);
$output = sprintf("[%s] [%s] %s %s" . PHP_EOL, $application->getApplicationName(), $event->getLevel()->name, $backtrace_output, $event->getMessage());
if($event->getException() !== null)
{
$output .= self::outException($event->getException());
}
}
else
{
$output = sprintf("[%s] [%s] %s" . PHP_EOL, $application->getApplicationName(), $event->getLevel()->name, $event->getMessage());
}
self::getLogger($application)->append($output);
}
public static function getType(): LogHandlerType
{
return LogHandlerType::FILE;
}
private static function getLogger(Application $application): FileLock
{
if(!isset(self::$application_logs[$application->getApplicationName()]))
{
self::$application_logs[$application->getApplicationName()] = new FileLock(self::getLogFile($application));
}
return self::$application_logs[$application->getApplicationName()];
}
private static function getLogFile(Application $application): string
{
$logging_directory = $application->getFileLoggingDirectory();
if(!is_writable($logging_directory))
{
throw new LoggingException(sprintf("Cannot write to %s due to insufficient permissions", $logging_directory));
}
if(!file_exists($logging_directory))
{
mkdir($logging_directory);
}
$logging_file = $logging_directory . DIRECTORY_SEPARATOR . Utilities::sanitizeFileName($application->getApplicationName()) . date('Y-m-d') . '.log';
if(!file_exists($logging_file))
{
touch($logging_file);
}
return $logging_file;
}
private static function getExceptionFile(Application $application, \Throwable $e): string
{
$logging_directory = $application->getFileLoggingDirectory();
if(!is_writable($logging_directory))
{
throw new LoggingException(sprintf("Cannot write to %s due to insufficient permissions", $logging_directory));
}
if(!file_exists($logging_directory))
{
mkdir($logging_directory);
}
return Utilities::sanitizeFileName($application->getApplicationName()) . '-' . Utilities::sanitizeFileName(get_class($e)) . '-' . date('d-m-Y-H-i-s') . '.json';
}
private static function getTimestamp(): string
{
return date('yd/m/y H:i');
}
private static function outException(?Throwable $exception=null): string
{
if($exception === null)
{
return '';
}
$output = '';
$trace_header = $exception->getFile() . ':' . $exception->getLine();
$trace_error = 'error: ';
$output .= $trace_header . ' ' . $trace_error . $exception->getMessage() . PHP_EOL;
$output .= sprintf('Error code: %s', $exception->getCode() . PHP_EOL);
$trace = $exception->getTrace();
if(count($trace) > 1)
{
$output .= 'Stack Trace:' . PHP_EOL;
foreach($trace as $item)
{
$output .= ' - ' . $item['file'] . ':' . $item['line'] . PHP_EOL;
}
}
if($exception->getPrevious() !== null)
{
$output .= 'Previous Exception:' . PHP_EOL;
$output .= self::outException($exception->getPrevious());
}
return $output;
}
}