49    private ThreadSafeArray $buffer;
 
   50    private bool $syncFlush = 
false;
 
   51    private bool $shutdown = 
false;
 
   53    public function __construct(
 
   54        private string $logFile,
 
   55        private ?
string $archiveDir,
 
   56        private readonly 
int $maxFileSize = 32 * 1024 * 1024 
 
   58        $this->buffer = 
new ThreadSafeArray();
 
   59        touch($this->logFile);
 
   60        if($this->archiveDir !== 
null && !@mkdir($this->archiveDir) && !is_dir($this->archiveDir)){
 
   61            throw new \RuntimeException(
"Unable to create archive directory: " . (
 
   62                is_file($this->archiveDir) ? 
"it already exists and is not a directory" : 
"permission denied"));
 
   66    public function write(
string $line) : 
void{
 
   67        $this->
synchronized(
function() use ($line) : 
void{
 
   68            $this->buffer[] = $line;
 
   73    public function syncFlushBuffer() : 
void{
 
   74        $this->
synchronized(
function() : 
void{
 
   75            $this->syncFlush = 
true;
 
   78        $this->
synchronized(
function() : 
void{
 
   79            while($this->syncFlush){
 
   85    public function shutdown() : 
void{
 
   86        $this->
synchronized(
function() : 
void{
 
   87            $this->shutdown = 
true;
 
   94    private function openLogFile(
string $file, 
int &$size){
 
   95        $logResource = fopen($file, 
"ab");
 
   96        if(!is_resource($logResource)){
 
   97            throw new \RuntimeException(
"Couldn't open log file");
 
   99        $stat = fstat($logResource);
 
  103        $size = $stat[
'size'];
 
  111    private function archiveLogFile($logResource, 
int &$size, 
string $archiveDir){
 
  112        fclose($logResource);
 
  117        $date = date(
"Y-m-d\TH.i.s");
 
  118        $baseName = pathinfo($this->logFile, PATHINFO_FILENAME);
 
  119        $extension = pathinfo($this->logFile, PATHINFO_EXTENSION);
 
  122            $fileName = 
"$baseName.$date.$i.$extension";
 
  123            $out = $this->archiveDir . 
"/" . $fileName;
 
  125        }
while(file_exists($out));
 
  129        rename($this->logFile, $out);
 
  131        $logResource = $this->openLogFile($this->logFile, $size);
 
  132        fwrite($logResource, 
"--- Starting new log file - old log file archived as $fileName ---\n");
 
  137    private function logFileReadyToArchive(
int $size) : 
bool{
 
  138        return $size >= $this->maxFileSize;
 
  144    private function writeLogStream(&$logResource, 
int &$size, ?
string $archiveDir) : 
void{
 
  145        while(($chunk = $this->buffer->shift()) !== 
null){
 
  146            fwrite($logResource, $chunk);
 
  147            $size += strlen($chunk);
 
  148            if($archiveDir !== 
null && $this->logFileReadyToArchive($size)){
 
  149                $logResource = $this->archiveLogFile($logResource, $size, $archiveDir);
 
  153        $this->
synchronized(
function() : 
void{
 
  154            if($this->syncFlush){
 
  155                $this->syncFlush = 
false;
 
  161    public function run() : 
void{
 
  163        $logResource = $this->openLogFile($this->logFile, $size);
 
  164        $archiveDir = $this->archiveDir;
 
  165        if($archiveDir !== 
null && $this->logFileReadyToArchive($size)){
 
  166            $logResource = $this->archiveLogFile($logResource, $size, $archiveDir);
 
  169        while(!$this->shutdown){
 
  170            $this->writeLogStream($logResource, $size, $archiveDir);
 
  171            $this->
synchronized(
function() : 
void{
 
  172                if(!$this->shutdown && !$this->syncFlush){
 
  178        $this->writeLogStream($logResource, $size, $archiveDir);
 
  180        fclose($logResource);