vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php line 26

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. /*
  3.  * This file is part of the Monolog package.
  4.  *
  5.  * (c) Jordi Boggiano <j.boggiano@seld.be>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Monolog\Handler;
  11. use InvalidArgumentException;
  12. use Monolog\Logger;
  13. /**
  14.  * Stores logs to files that are rotated every day and a limited number of files are kept.
  15.  *
  16.  * This rotation is only intended to be used as a workaround. Using logrotate to
  17.  * handle the rotation is strongly encouraged when you can use it.
  18.  *
  19.  * @author Christophe Coevoet <stof@notk.org>
  20.  * @author Jordi Boggiano <j.boggiano@seld.be>
  21.  */
  22. class RotatingFileHandler extends StreamHandler
  23. {
  24.     public const FILE_PER_DAY 'Y-m-d';
  25.     public const FILE_PER_MONTH 'Y-m';
  26.     public const FILE_PER_YEAR 'Y';
  27.     protected $filename;
  28.     protected $maxFiles;
  29.     protected $mustRotate;
  30.     protected $nextRotation;
  31.     protected $filenameFormat;
  32.     protected $dateFormat;
  33.     /**
  34.      * @param string     $filename
  35.      * @param int        $maxFiles       The maximal amount of files to keep (0 means unlimited)
  36.      * @param string|int $level          The minimum logging level at which this handler will be triggered
  37.      * @param bool       $bubble         Whether the messages that are handled can bubble up the stack or not
  38.      * @param int|null   $filePermission Optional file permissions (default (0644) are only for owner read/write)
  39.      * @param bool       $useLocking     Try to lock log file before doing any writes
  40.      */
  41.     public function __construct(string $filenameint $maxFiles 0$level Logger::DEBUGbool $bubble true, ?int $filePermission nullbool $useLocking false)
  42.     {
  43.         $this->filename $filename;
  44.         $this->maxFiles $maxFiles;
  45.         $this->nextRotation = new \DateTimeImmutable('tomorrow');
  46.         $this->filenameFormat '{filename}-{date}';
  47.         $this->dateFormat = static::FILE_PER_DAY;
  48.         parent::__construct($this->getTimedFilename(), $level$bubble$filePermission$useLocking);
  49.     }
  50.     /**
  51.      * {@inheritdoc}
  52.      */
  53.     public function close(): void
  54.     {
  55.         parent::close();
  56.         if (true === $this->mustRotate) {
  57.             $this->rotate();
  58.         }
  59.     }
  60.     /**
  61.      * {@inheritdoc}
  62.      */
  63.     public function reset()
  64.     {
  65.         parent::reset();
  66.         if (true === $this->mustRotate) {
  67.             $this->rotate();
  68.         }
  69.     }
  70.     public function setFilenameFormat(string $filenameFormatstring $dateFormat): self
  71.     {
  72.         if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}'$dateFormat)) {
  73.             throw new InvalidArgumentException(
  74.                 'Invalid date format - format must be one of '.
  75.                 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") '.
  76.                 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the '.
  77.                 'date formats using slashes, underscores and/or dots instead of dashes.'
  78.             );
  79.         }
  80.         if (substr_count($filenameFormat'{date}') === 0) {
  81.             throw new InvalidArgumentException(
  82.                 'Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.'
  83.             );
  84.         }
  85.         $this->filenameFormat $filenameFormat;
  86.         $this->dateFormat $dateFormat;
  87.         $this->url $this->getTimedFilename();
  88.         $this->close();
  89.         return $this;
  90.     }
  91.     /**
  92.      * {@inheritdoc}
  93.      */
  94.     protected function write(array $record): void
  95.     {
  96.         // on the first record written, if the log is new, we should rotate (once per day)
  97.         if (null === $this->mustRotate) {
  98.             $this->mustRotate = !file_exists($this->url);
  99.         }
  100.         if ($this->nextRotation <= $record['datetime']) {
  101.             $this->mustRotate true;
  102.             $this->close();
  103.         }
  104.         parent::write($record);
  105.     }
  106.     /**
  107.      * Rotates the files.
  108.      */
  109.     protected function rotate(): void
  110.     {
  111.         // update filename
  112.         $this->url $this->getTimedFilename();
  113.         $this->nextRotation = new \DateTimeImmutable('tomorrow');
  114.         // skip GC of old logs if files are unlimited
  115.         if (=== $this->maxFiles) {
  116.             return;
  117.         }
  118.         $logFiles glob($this->getGlobPattern());
  119.         if ($this->maxFiles >= count($logFiles)) {
  120.             // no files to remove
  121.             return;
  122.         }
  123.         // Sorting the files by name to remove the older ones
  124.         usort($logFiles, function ($a$b) {
  125.             return strcmp($b$a);
  126.         });
  127.         foreach (array_slice($logFiles$this->maxFiles) as $file) {
  128.             if (is_writable($file)) {
  129.                 // suppress errors here as unlink() might fail if two processes
  130.                 // are cleaning up/rotating at the same time
  131.                 set_error_handler(function (int $errnostring $errstrstring $errfileint $errline): bool {
  132.                     return false;
  133.                 });
  134.                 unlink($file);
  135.                 restore_error_handler();
  136.             }
  137.         }
  138.         $this->mustRotate false;
  139.     }
  140.     protected function getTimedFilename(): string
  141.     {
  142.         $fileInfo pathinfo($this->filename);
  143.         $timedFilename str_replace(
  144.             ['{filename}''{date}'],
  145.             [$fileInfo['filename'], date($this->dateFormat)],
  146.             $fileInfo['dirname'] . '/' $this->filenameFormat
  147.         );
  148.         if (!empty($fileInfo['extension'])) {
  149.             $timedFilename .= '.'.$fileInfo['extension'];
  150.         }
  151.         return $timedFilename;
  152.     }
  153.     protected function getGlobPattern(): string
  154.     {
  155.         $fileInfo pathinfo($this->filename);
  156.         $glob str_replace(
  157.             ['{filename}''{date}'],
  158.             [$fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'],
  159.             $fileInfo['dirname'] . '/' $this->filenameFormat
  160.         );
  161.         if (!empty($fileInfo['extension'])) {
  162.             $glob .= '.'.$fileInfo['extension'];
  163.         }
  164.         return $glob;
  165.     }
  166. }