← All changes
|
includes/sdk/google/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php
+84
-60
1.2.10
→
1.4.2
View file →
| @@ -8,13 +8,15 @@ | ||
| 8 | 8 | * |
| 9 | 9 | * For the full copyright and license information, please view the LICENSE |
| 10 | 10 | * file that was distributed with this source code. |
| 11 | 11 | */ |
| 12 | -namespace Dudlewebs\WPMCS\Monolog\Handler; | |
| 12 | +namespace Dudlewebs\WPMCS\GCP\Monolog\Handler; | |
| 13 | 13 | |
| 14 | +use DateTimeZone; | |
| 14 | 15 | use InvalidArgumentException; |
| 15 | -use Dudlewebs\WPMCS\Monolog\Logger; | |
| 16 | -use Dudlewebs\WPMCS\Monolog\Utils; | |
| 16 | +use Dudlewebs\WPMCS\GCP\Monolog\Level; | |
| 17 | +use Dudlewebs\WPMCS\GCP\Monolog\Utils; | |
| 18 | +use Dudlewebs\WPMCS\GCP\Monolog\LogRecord; | |
| 17 | 19 | /** |
| 18 | 20 | * Stores logs to files that are rotated every day and a limited number of files are kept. |
| 19 | 21 | * |
| 20 | 22 | * This rotation is only intended to be used as a workaround. Using logrotate to |
| @@ -27,39 +29,33 @@ | ||
| 27 | 29 | { |
| 28 | 30 | public const FILE_PER_DAY = 'Y-m-d'; |
| 29 | 31 | public const FILE_PER_MONTH = 'Y-m'; |
| 30 | 32 | public const FILE_PER_YEAR = 'Y'; |
| 31 | - /** @var string */ | |
| 32 | - protected $filename; | |
| 33 | - /** @var int */ | |
| 34 | - protected $maxFiles; | |
| 35 | - /** @var bool */ | |
| 36 | - protected $mustRotate; | |
| 37 | - /** @var \DateTimeImmutable */ | |
| 38 | - protected $nextRotation; | |
| 39 | - /** @var string */ | |
| 40 | - protected $filenameFormat; | |
| 41 | - /** @var string */ | |
| 42 | - protected $dateFormat; | |
| 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; | |
| 43 | 40 | /** |
| 44 | - * @param string $filename | |
| 45 | - * @param int $maxFiles The maximal amount of files to keep (0 means unlimited) | |
| 46 | - * @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write) | |
| 47 | - * @param bool $useLocking Try to lock log file before doing any writes | |
| 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 | |
| 48 | 44 | */ |
| 49 | - public function __construct(string $filename, int $maxFiles = 0, $level = Logger::DEBUG, bool $bubble = \true, ?int $filePermission = null, bool $useLocking = \false) | |
| 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) | |
| 50 | 46 | { |
| 51 | 47 | $this->filename = Utils::canonicalizePath($filename); |
| 52 | 48 | $this->maxFiles = $maxFiles; |
| 53 | - $this->nextRotation = new \DateTimeImmutable('tomorrow'); | |
| 54 | - $this->filenameFormat = '{filename}-{date}'; | |
| 55 | - $this->dateFormat = static::FILE_PER_DAY; | |
| 49 | + $this->setFilenameFormat($filenameFormat, $dateFormat); | |
| 50 | + $this->nextRotation = $this->getNextRotation(); | |
| 51 | + $this->timezone = $timezone; | |
| 56 | 52 | parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking); |
| 57 | 53 | } |
| 58 | 54 | /** |
| 59 | - * {@inheritDoc} | |
| 55 | + * @inheritDoc | |
| 60 | 56 | */ |
| 61 | - public function close(): void | |
| 57 | + public function close() : void | |
| 62 | 58 | { |
| 63 | 59 | parent::close(); |
| 64 | 60 | if (\true === $this->mustRotate) { |
| 65 | 61 | $this->rotate(); |
| @@ -65,99 +61,127 @@ | ||
| 65 | 61 | $this->rotate(); |
| 66 | 62 | } |
| 67 | 63 | } |
| 68 | 64 | /** |
| 69 | - * {@inheritDoc} | |
| 65 | + * @inheritDoc | |
| 70 | 66 | */ |
| 71 | - public function reset() | |
| 67 | + public function reset() : void | |
| 72 | 68 | { |
| 73 | 69 | parent::reset(); |
| 74 | - if (\true === $this->mustRotate) { | |
| 75 | - $this->rotate(); | |
| 76 | - } | |
| 77 | 70 | } |
| 78 | - public function setFilenameFormat(string $filenameFormat, string $dateFormat): self | |
| 71 | + /** | |
| 72 | + * @return $this | |
| 73 | + */ | |
| 74 | + public function setFilenameFormat(string $filenameFormat, string $dateFormat) : self | |
| 79 | 75 | { |
| 80 | - if (!preg_match('{^[Yy](([/_.-]?m)([/_.-]?d)?)?$}', $dateFormat)) { | |
| 81 | - 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.'); | |
| 82 | - } | |
| 83 | - if (substr_count($filenameFormat, '{date}') === 0) { | |
| 76 | + $this->setDateFormat($dateFormat); | |
| 77 | + if (\substr_count($filenameFormat, '{date}') === 0) { | |
| 84 | 78 | throw new InvalidArgumentException('Invalid filename format - format must contain at least `{date}`, because otherwise rotating is impossible.'); |
| 85 | 79 | } |
| 86 | 80 | $this->filenameFormat = $filenameFormat; |
| 87 | - $this->dateFormat = $dateFormat; | |
| 88 | 81 | $this->url = $this->getTimedFilename(); |
| 89 | 82 | $this->close(); |
| 90 | 83 | return $this; |
| 91 | 84 | } |
| 92 | 85 | /** |
| 93 | - * {@inheritDoc} | |
| 86 | + * @inheritDoc | |
| 94 | 87 | */ |
| 95 | - protected function write(array $record): void | |
| 88 | + protected function write(LogRecord $record) : void | |
| 96 | 89 | { |
| 97 | - // on the first record written, if the log is new, we should rotate (once per day) | |
| 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 | |
| 98 | 91 | if (null === $this->mustRotate) { |
| 99 | - $this->mustRotate = null === $this->url || !file_exists($this->url); | |
| 92 | + $this->mustRotate = null === $this->url || !\file_exists($this->url); | |
| 100 | 93 | } |
| 101 | - if ($this->nextRotation <= $record['datetime']) { | |
| 94 | + // if the next rotation is expired, then we rotate immediately | |
| 95 | + if ($this->nextRotation <= $record->datetime) { | |
| 102 | 96 | $this->mustRotate = \true; |
| 103 | 97 | $this->close(); |
| 98 | + // triggers rotation | |
| 104 | 99 | } |
| 105 | 100 | parent::write($record); |
| 101 | + if (\true === $this->mustRotate) { | |
| 102 | + $this->close(); | |
| 103 | + // triggers rotation | |
| 104 | + } | |
| 106 | 105 | } |
| 107 | 106 | /** |
| 108 | 107 | * Rotates the files. |
| 109 | 108 | */ |
| 110 | - protected function rotate(): void | |
| 109 | + protected function rotate() : void | |
| 111 | 110 | { |
| 112 | 111 | // update filename |
| 113 | 112 | $this->url = $this->getTimedFilename(); |
| 114 | - $this->nextRotation = new \DateTimeImmutable('tomorrow'); | |
| 113 | + $this->nextRotation = $this->getNextRotation(); | |
| 114 | + $this->mustRotate = \false; | |
| 115 | 115 | // skip GC of old logs if files are unlimited |
| 116 | 116 | if (0 === $this->maxFiles) { |
| 117 | 117 | return; |
| 118 | 118 | } |
| 119 | - $logFiles = glob($this->getGlobPattern()); | |
| 119 | + $logFiles = \glob($this->getGlobPattern()); | |
| 120 | 120 | if (\false === $logFiles) { |
| 121 | 121 | // failed to glob |
| 122 | 122 | return; |
| 123 | 123 | } |
| 124 | - if ($this->maxFiles >= count($logFiles)) { | |
| 124 | + if ($this->maxFiles >= \count($logFiles)) { | |
| 125 | 125 | // no files to remove |
| 126 | 126 | return; |
| 127 | 127 | } |
| 128 | 128 | // Sorting the files by name to remove the older ones |
| 129 | - usort($logFiles, function ($a, $b) { | |
| 130 | - return strcmp($b, $a); | |
| 129 | + \usort($logFiles, function ($a, $b) { | |
| 130 | + return \strcmp($b, $a); | |
| 131 | 131 | }); |
| 132 | - foreach (array_slice($logFiles, $this->maxFiles) as $file) { | |
| 133 | - if (is_writable($file)) { | |
| 132 | + $basePath = \dirname($this->filename); | |
| 133 | + foreach (\array_slice($logFiles, $this->maxFiles) as $file) { | |
| 134 | + if (\is_writable($file)) { | |
| 134 | 135 | // suppress errors here as unlink() might fail if two processes |
| 135 | 136 | // are cleaning up/rotating at the same time |
| 136 | - set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline): bool { | |
| 137 | - return \false; | |
| 137 | + \set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) : bool { | |
| 138 | + return \true; | |
| 138 | 139 | }); |
| 139 | - unlink($file); | |
| 140 | - restore_error_handler(); | |
| 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(); | |
| 141 | 151 | } |
| 142 | 152 | } |
| 143 | - $this->mustRotate = \false; | |
| 144 | 153 | } |
| 145 | - protected function getTimedFilename(): string | |
| 154 | + protected function getTimedFilename() : string | |
| 146 | 155 | { |
| 147 | - $fileInfo = pathinfo($this->filename); | |
| 148 | - $timedFilename = str_replace(['{filename}', '{date}'], [$fileInfo['filename'], date($this->dateFormat)], $fileInfo['dirname'] . '/' . $this->filenameFormat); | |
| 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); | |
| 149 | 158 | if (isset($fileInfo['extension'])) { |
| 150 | 159 | $timedFilename .= '.' . $fileInfo['extension']; |
| 151 | 160 | } |
| 152 | 161 | return $timedFilename; |
| 153 | 162 | } |
| 154 | - protected function getGlobPattern(): string | |
| 163 | + protected function getGlobPattern() : string | |
| 155 | 164 | { |
| 156 | - $fileInfo = pathinfo($this->filename); | |
| 157 | - $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); | |
| 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); | |
| 158 | 167 | if (isset($fileInfo['extension'])) { |
| 159 | 168 | $glob .= '.' . $fileInfo['extension']; |
| 160 | 169 | } |
| 161 | 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 | + }; | |
| 162 | 186 | } |
| 163 | 187 | } |