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 Monolog\Logger;
15
use Monolog\Utils;
16
 
17
/**
18
 * Stores to any stream resource
19
 *
20
 * Can be used to store into php://stderr, remote and local files, etc.
21
 *
22
 * @author Jordi Boggiano <j.boggiano@seld.be>
23
 *
24
 * @phpstan-import-type FormattedRecord from AbstractProcessingHandler
25
 */
26
class StreamHandler extends AbstractProcessingHandler
27
{
28
    /** @const int */
29
    protected const MAX_CHUNK_SIZE = 2147483647;
30
    /** @const int 10MB */
31
    protected const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024;
32
    /** @var int */
33
    protected $streamChunkSize;
34
    /** @var resource|null */
35
    protected $stream;
36
    /** @var ?string */
37
    protected $url = null;
38
    /** @var ?string */
39
    private $errorMessage = null;
40
    /** @var ?int */
41
    protected $filePermission;
42
    /** @var bool */
43
    protected $useLocking;
44
    /** @var true|null */
45
    private $dirCreated = null;
46
 
47
    /**
48
     * @param resource|string $stream         If a missing path can't be created, an UnexpectedValueException will be thrown on first write
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
     * @throws \InvalidArgumentException If stream is not a resource or string
53
     */
54
    public function __construct($stream, $level = Logger::DEBUG, bool $bubble = true, ?int $filePermission = null, bool $useLocking = false)
55
    {
56
        parent::__construct($level, $bubble);
57
 
58
        if (($phpMemoryLimit = Utils::expandIniShorthandBytes(ini_get('memory_limit'))) !== false) {
59
            if ($phpMemoryLimit > 0) {
60
                // use max 10% of allowed memory for the chunk size, and at least 100KB
61
                $this->streamChunkSize = min(static::MAX_CHUNK_SIZE, max((int) ($phpMemoryLimit / 10), 100 * 1024));
62
            } else {
63
                // memory is unlimited, set to the default 10MB
64
                $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;
65
            }
66
        } else {
67
            // no memory limit information, set to the default 10MB
68
            $this->streamChunkSize = static::DEFAULT_CHUNK_SIZE;
69
        }
70
 
71
        if (is_resource($stream)) {
72
            $this->stream = $stream;
73
 
74
            stream_set_chunk_size($this->stream, $this->streamChunkSize);
75
        } elseif (is_string($stream)) {
76
            $this->url = Utils::canonicalizePath($stream);
77
        } else {
78
            throw new \InvalidArgumentException('A stream must either be a resource or a string.');
79
        }
80
 
81
        $this->filePermission = $filePermission;
82
        $this->useLocking = $useLocking;
83
    }
84
 
85
    /**
86
     * {@inheritDoc}
87
     */
88
    public function close(): void
89
    {
90
        if ($this->url && is_resource($this->stream)) {
91
            fclose($this->stream);
92
        }
93
        $this->stream = null;
94
        $this->dirCreated = null;
95
    }
96
 
97
    /**
98
     * Return the currently active stream if it is open
99
     *
100
     * @return resource|null
101
     */
102
    public function getStream()
103
    {
104
        return $this->stream;
105
    }
106
 
107
    /**
108
     * Return the stream URL if it was configured with a URL and not an active resource
109
     *
110
     * @return string|null
111
     */
112
    public function getUrl(): ?string
113
    {
114
        return $this->url;
115
    }
116
 
117
    /**
118
     * @return int
119
     */
120
    public function getStreamChunkSize(): int
121
    {
122
        return $this->streamChunkSize;
123
    }
124
 
125
    /**
126
     * {@inheritDoc}
127
     */
128
    protected function write(array $record): void
129
    {
130
        if (!is_resource($this->stream)) {
131
            $url = $this->url;
132
            if (null === $url || '' === $url) {
133
                throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().' . Utils::getRecordMessageForException($record));
134
            }
135
            $this->createDir($url);
136
            $this->errorMessage = null;
137
            set_error_handler([$this, 'customErrorHandler']);
138
            $stream = fopen($url, 'a');
139
            if ($this->filePermission !== null) {
140
                @chmod($url, $this->filePermission);
141
            }
142
            restore_error_handler();
143
            if (!is_resource($stream)) {
144
                $this->stream = null;
145
 
146
                throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened in append mode: '.$this->errorMessage, $url) . Utils::getRecordMessageForException($record));
147
            }
148
            stream_set_chunk_size($stream, $this->streamChunkSize);
149
            $this->stream = $stream;
150
        }
151
 
152
        $stream = $this->stream;
153
        if (!is_resource($stream)) {
154
            throw new \LogicException('No stream was opened yet' . Utils::getRecordMessageForException($record));
155
        }
156
 
157
        if ($this->useLocking) {
158
            // ignoring errors here, there's not much we can do about them
159
            flock($stream, LOCK_EX);
160
        }
161
 
162
        $this->streamWrite($stream, $record);
163
 
164
        if ($this->useLocking) {
165
            flock($stream, LOCK_UN);
166
        }
167
    }
168
 
169
    /**
170
     * Write to stream
171
     * @param resource $stream
172
     * @param array    $record
173
     *
174
     * @phpstan-param FormattedRecord $record
175
     */
176
    protected function streamWrite($stream, array $record): void
177
    {
178
        fwrite($stream, (string) $record['formatted']);
179
    }
180
 
181
    private function customErrorHandler(int $code, string $msg): bool
182
    {
183
        $this->errorMessage = preg_replace('{^(fopen|mkdir)\(.*?\): }', '', $msg);
184
 
185
        return true;
186
    }
187
 
188
    private function getDirFromStream(string $stream): ?string
189
    {
190
        $pos = strpos($stream, '://');
191
        if ($pos === false) {
192
            return dirname($stream);
193
        }
194
 
195
        if ('file://' === substr($stream, 0, 7)) {
196
            return dirname(substr($stream, 7));
197
        }
198
 
199
        return null;
200
    }
201
 
202
    private function createDir(string $url): void
203
    {
204
        // Do not try to create dir if it has already been tried.
205
        if ($this->dirCreated) {
206
            return;
207
        }
208
 
209
        $dir = $this->getDirFromStream($url);
210
        if (null !== $dir && !is_dir($dir)) {
211
            $this->errorMessage = null;
212
            set_error_handler([$this, 'customErrorHandler']);
213
            $status = mkdir($dir, 0777, true);
214
            restore_error_handler();
215
            if (false === $status && !is_dir($dir) && strpos((string) $this->errorMessage, 'File exists') === false) {
216
                throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and it could not be created: '.$this->errorMessage, $dir));
217
            }
218
        }
219
        $this->dirCreated = true;
220
    }
221
}