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

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 should rotate (once per day)
  102.         if (null === $this->mustRotate) {
  103.             $this->mustRotate null === $this->url || !file_exists($this->url);
  104.         }
  105.         if ($this->nextRotation <= $record['datetime']) {
  106.             $this->mustRotate true;
  107.             $this->close();
  108.         }
  109.         parent::write($record);
  110.     }
  111.     /**
  112.      * Rotates the files.
  113.      */
  114.     protected function rotate(): void
  115.     {
  116.         // update filename
  117.         $this->url $this->getTimedFilename();
  118.         $this->nextRotation = new \DateTimeImmutable('tomorrow');
  119.         // skip GC of old logs if files are unlimited
  120.         if (=== $this->maxFiles) {
  121.             return;
  122.         }
  123.         $logFiles glob($this->getGlobPattern());
  124.         if (false === $logFiles) {
  125.             // failed to glob
  126.             return;
  127.         }
  128.         if ($this->maxFiles >= count($logFiles)) {
  129.             // no files to remove
  130.             return;
  131.         }
  132.         // Sorting the files by name to remove the older ones
  133.         usort($logFiles, function ($a$b) {
  134.             return strcmp($b$a);
  135.         });
  136.         foreach (array_slice($logFiles$this->maxFiles) as $file) {
  137.             if (is_writable($file)) {
  138.                 // suppress errors here as unlink() might fail if two processes
  139.                 // are cleaning up/rotating at the same time
  140.                 set_error_handler(function (int $errnostring $errstrstring $errfileint $errline): bool {
  141.                     return false;
  142.                 });
  143.                 unlink($file);
  144.                 restore_error_handler();
  145.             }
  146.         }
  147.         $this->mustRotate false;
  148.     }
  149.     protected function getTimedFilename(): string
  150.     {
  151.         $fileInfo pathinfo($this->filename);
  152.         $timedFilename str_replace(
  153.             ['{filename}''{date}'],
  154.             [$fileInfo['filename'], date($this->dateFormat)],
  155.             $fileInfo['dirname'] . '/' $this->filenameFormat
  156.         );
  157.         if (isset($fileInfo['extension'])) {
  158.             $timedFilename .= '.'.$fileInfo['extension'];
  159.         }
  160.         return $timedFilename;
  161.     }
  162.     protected function getGlobPattern(): string
  163.     {
  164.         $fileInfo pathinfo($this->filename);
  165.         $glob str_replace(
  166.             ['{filename}''{date}'],
  167.             [$fileInfo['filename'], str_replace(
  168.                 ['Y''y''m''d'],
  169.                 ['[0-9][0-9][0-9][0-9]''[0-9][0-9]''[0-9][0-9]''[0-9][0-9]'],
  170.                 $this->dateFormat)
  171.             ],
  172.             $fileInfo['dirname'] . '/' $this->filenameFormat
  173.         );
  174.         if (isset($fileInfo['extension'])) {
  175.             $glob .= '.'.$fileInfo['extension'];
  176.         }
  177.         return $glob;
  178.     }
  179. }