Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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