media-cloud-sync
/
includes
/
sdk
/
google
/
monolog
/
monolog
/
src
/
Monolog
/
Handler
/
RotatingFileHandler.php
RotatingFileHandler.php in Media Cloud Sync 1.4.2, at includes/sdk/google/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php
| 1 | <?php |
| 2 | |
| 3 | declare (strict_types=1); |
| 4 | /* |
| 5 | * This file is part of the Monolog package. |
| 6 | * |
| 7 | * (c) Jordi Boggiano <[email protected]> |
| 8 | * |
| 9 | * For the full copyright and license information, please view the LICENSE |
| 10 | * file that was distributed with this source code. |
| 11 | */ |
| 12 | namespace Dudlewebs\WPMCS\GCP\Monolog\Handler; |
| 13 | |
| 14 | use DateTimeZone; |
| 15 | use InvalidArgumentException; |
| 16 | use Dudlewebs\WPMCS\GCP\Monolog\Level; |
| 17 | use Dudlewebs\WPMCS\GCP\Monolog\Utils; |
| 18 | use Dudlewebs\WPMCS\GCP\Monolog\LogRecord; |
| 19 | /** |
| 20 | * Stores logs to files that are rotated every day and a limited number of files are kept. |
| 21 | * |
| 22 | * This rotation is only intended to be used as a workaround. Using logrotate to |
| 23 | * handle the rotation is strongly encouraged when you can use it. |
| 24 | * |
| 25 | * @author Christophe Coevoet <[email protected]> |
| 26 | * @author Jordi Boggiano <[email protected]> |
| 27 | */ |
| 28 | class RotatingFileHandler extends StreamHandler |
| 29 | { |
| 30 | public const FILE_PER_DAY = 'Y-m-d'; |
| 31 | public const FILE_PER_MONTH = 'Y-m'; |
| 32 | public const FILE_PER_YEAR = 'Y'; |
| 33 | protected string $filename; |
| 34 | protected int $maxFiles; |
| 35 | protected bool|null $mustRotate = null; |
| 36 | protected \DateTimeImmutable $nextRotation; |
| 37 | protected string $filenameFormat; |
| 38 | protected string $dateFormat; |
| 39 | protected DateTimeZone|null $timezone = null; |
| 40 | /** |
| 41 | * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) |
| 42 | * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) |
| 43 | * @param bool $useLocking Try to lock log file before doing any writes |
| 44 | */ |
| 45 | public function __construct(string $filename, int $maxFiles = 0, int|string|Level $level = Level::Debug, bool $bubble = \true, ?int $filePermission = null, bool $useLocking = \false, string $dateFormat = self::FILE_PER_DAY, string $filenameFormat = '{filename}-{date}', DateTimeZone|null $timezone = null) |
| 46 | { |
| 47 | $this->filename = Utils::canonicalizePath($filename); |
| 48 | $this->maxFiles = $maxFiles; |
| 49 | $this->setFilenameFormat($filenameFormat, $dateFormat); |
| 50 | $this->nextRotation = $this->getNextRotation(); |
| 51 | $this->timezone = $timezone; |
| 52 | parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); |
| 53 | } |
| 54 | /** |
| 55 | * @inheritDoc |
| 56 | */ |
| 57 | public function close() : void |
| 58 | { |
| 59 | parent::close(); |
| 60 | if (\true === $this->mustRotate) { |
| 61 | $this->rotate(); |
| 62 | } |
| 63 | } |
| 64 | /** |
| 65 | * @inheritDoc |
| 66 | */ |
| 67 | public function reset() : void |
| 68 | { |
| 69 | parent::reset(); |
| 70 | } |
| 71 | /** |
| 72 | * @return $this |
| 73 | */ |
| 74 | public function setFilenameFormat(string $filenameFormat, string $dateFormat) : self |
| 75 | { |
| 76 | $this->setDateFormat($dateFormat); |
| 77 | if (\substr_count($filenameFormat, '{date}') === 0) { |
| 78 | throw new InvalidArgumentException('Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.'); |
| 79 | } |
| 80 | $this->filenameFormat = $filenameFormat; |
| 81 | $this->url = $this->getTimedFilename(); |
| 82 | $this->close(); |
| 83 | return $this; |
| 84 | } |
| 85 | /** |
| 86 | * @inheritDoc |
| 87 | */ |
| 88 | protected function write(LogRecord $record) : void |
| 89 | { |
| 90 | // 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 |
| 91 | if (null === $this->mustRotate) { |
| 92 | $this->mustRotate = null === $this->url || !\file_exists($this->url); |
| 93 | } |
| 94 | // if the next rotation is expired, then we rotate immediately |
| 95 | if ($this->nextRotation <= $record->datetime) { |
| 96 | $this->mustRotate = \true; |
| 97 | $this->close(); |
| 98 | // triggers rotation |
| 99 | } |
| 100 | parent::write($record); |
| 101 | if (\true === $this->mustRotate) { |
| 102 | $this->close(); |
| 103 | // triggers rotation |
| 104 | } |
| 105 | } |
| 106 | /** |
| 107 | * Rotates the files. |
| 108 | */ |
| 109 | protected function rotate() : void |
| 110 | { |
| 111 | // update filename |
| 112 | $this->url = $this->getTimedFilename(); |
| 113 | $this->nextRotation = $this->getNextRotation(); |
| 114 | $this->mustRotate = \false; |
| 115 | // skip GC of old logs if files are unlimited |
| 116 | if (0 === $this->maxFiles) { |
| 117 | return; |
| 118 | } |
| 119 | $logFiles = \glob($this->getGlobPattern()); |
| 120 | if (\false === $logFiles) { |
| 121 | // failed to glob |
| 122 | return; |
| 123 | } |
| 124 | if ($this->maxFiles >= \count($logFiles)) { |
| 125 | // no files to remove |
| 126 | return; |
| 127 | } |
| 128 | // Sorting the files by name to remove the older ones |
| 129 | \usort($logFiles, function ($a, $b) { |
| 130 | return \strcmp($b, $a); |
| 131 | }); |
| 132 | $basePath = \dirname($this->filename); |
| 133 | foreach (\array_slice($logFiles, $this->maxFiles) as $file) { |
| 134 | if (\is_writable($file)) { |
| 135 | // suppress errors here as unlink() might fail if two processes |
| 136 | // are cleaning up/rotating at the same time |
| 137 | \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) : bool { |
| 138 | return \true; |
| 139 | }); |
| 140 | \unlink($file); |
| 141 | $dir = \dirname($file); |
| 142 | while ($dir !== $basePath) { |
| 143 | $entries = \scandir($dir); |
| 144 | if ($entries === \false || \count(\array_diff($entries, ['.', '..'])) > 0) { |
| 145 | break; |
| 146 | } |
| 147 | \rmdir($dir); |
| 148 | $dir = \dirname($dir); |
| 149 | } |
| 150 | \restore_error_handler(); |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | protected function getTimedFilename() : string |
| 155 | { |
| 156 | $fileInfo = \pathinfo($this->filename); |
| 157 | $timedFilename = \str_replace(['{filename}', '{date}'], [$fileInfo['filename'], (new \DateTimeImmutable(timezone: $this->timezone))->format($this->dateFormat)], ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat); |
| 158 | if (isset($fileInfo['extension'])) { |
| 159 | $timedFilename .= '.' . $fileInfo['extension']; |
| 160 | } |
| 161 | return $timedFilename; |
| 162 | } |
| 163 | protected function getGlobPattern() : string |
| 164 | { |
| 165 | $fileInfo = \pathinfo($this->filename); |
| 166 | $glob = \str_replace(['{filename}', '{date}'], [$fileInfo['filename'], \str_replace(['Y', 'y', 'm', 'd'], ['[0-9][0-9][0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]', '[0-9][0-9]'], $this->dateFormat)], ($fileInfo['dirname'] ?? '') . '/' . $this->filenameFormat); |
| 167 | if (isset($fileInfo['extension'])) { |
| 168 | $glob .= '.' . $fileInfo['extension']; |
| 169 | } |
| 170 | return $glob; |
| 171 | } |
| 172 | protected function setDateFormat(string $dateFormat) : void |
| 173 | { |
| 174 | if (0 === \preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { |
| 175 | throw new InvalidArgumentException('Invalid date format - format must be one of ' . 'RotatingFileHandler::FILE_PER_DAY ("Y-m-d"), RotatingFileHandler::FILE_PER_MONTH ("Y-m") ' . 'or RotatingFileHandler::FILE_PER_YEAR ("Y"), or you can set one of the ' . 'date formats using slashes, underscores and/or dots instead of dashes.'); |
| 176 | } |
| 177 | $this->dateFormat = $dateFormat; |
| 178 | } |
| 179 | protected function getNextRotation() : \DateTimeImmutable |
| 180 | { |
| 181 | return match (\str_replace(['/', '_', '.'], '-', $this->dateFormat)) { |
| 182 | self::FILE_PER_MONTH => (new \DateTimeImmutable('first day of next month'))->setTime(0, 0, 0), |
| 183 | self::FILE_PER_YEAR => (new \DateTimeImmutable('first day of January next year'))->setTime(0, 0, 0), |
| 184 | default => (new \DateTimeImmutable('tomorrow'))->setTime(0, 0, 0), |
| 185 | }; |
| 186 | } |
| 187 | } |
| 188 |