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

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